diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
commit | 74aa0bc6779af38018a03fd2cf4419fe85917904 (patch) | |
tree | 9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/providers/ipa | |
parent | Initial commit. (diff) | |
download | sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.tar.xz sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.zip |
Adding upstream version 2.9.4.upstream/2.9.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/providers/ipa')
65 files changed, 33244 insertions, 0 deletions
diff --git a/src/providers/ipa/ipa_access.c b/src/providers/ipa/ipa_access.c new file mode 100644 index 0000000..205ebe3 --- /dev/null +++ b/src/providers/ipa/ipa_access.c @@ -0,0 +1,787 @@ +/* + SSSD + + IPA Backend Module -- Access control + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <security/pam_modules.h> + +#include "util/util.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_access.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_access.h" +#include "providers/ipa/ipa_hosts.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ipa/ipa_hbac_rules.h" +#include "providers/ipa/ipa_rules_common.h" + +/* External logging function for HBAC. */ +void hbac_debug_messages(const char *file, int line, + const char *function, + enum hbac_debug_level level, + const char *fmt, ...) +{ + int loglevel; + va_list ap; + + switch(level) { + case HBAC_DBG_FATAL: + loglevel = SSSDBG_FATAL_FAILURE; + break; + case HBAC_DBG_ERROR: + loglevel = SSSDBG_OP_FAILURE; + break; + case HBAC_DBG_WARNING: + loglevel = SSSDBG_MINOR_FAILURE; + break; + case HBAC_DBG_INFO: + loglevel = SSSDBG_CONF_SETTINGS; + break; + case HBAC_DBG_TRACE: + loglevel = SSSDBG_TRACE_INTERNAL; + break; + default: + loglevel = SSSDBG_UNRESOLVED; + break; + } + + va_start(ap, fmt); + sss_vdebug_fn(file, line, function, loglevel, 0, fmt, ap); + va_end(ap); +} + +enum hbac_result { + HBAC_ALLOW = 1, + HBAC_DENY, + HBAC_NOT_APPLICABLE +}; + +enum check_result { + RULE_APPLICABLE = 0, + RULE_NOT_APPLICABLE, + RULE_ERROR +}; + +struct ipa_fetch_hbac_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct sdap_id_ctx *sdap_ctx; + struct ipa_access_ctx *access_ctx; + struct sdap_id_op *sdap_op; + struct dp_option *ipa_options; + + struct sdap_search_base **search_bases; + + /* Hosts */ + struct ipa_common_entries *hosts; + struct sysdb_attrs *ipa_host; + + /* Rules */ + struct ipa_common_entries *rules; + + /* Services */ + struct ipa_common_entries *services; +}; + +static errno_t ipa_fetch_hbac_retry(struct tevent_req *req); +static void ipa_fetch_hbac_connect_done(struct tevent_req *subreq); +static errno_t ipa_fetch_hbac_hostinfo(struct tevent_req *req); +static void ipa_fetch_hbac_hostinfo_done(struct tevent_req *subreq); +static void ipa_fetch_hbac_services_done(struct tevent_req *subreq); +static void ipa_fetch_hbac_rules_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_fetch_hbac_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_access_ctx *access_ctx) +{ + struct ipa_fetch_hbac_state *state; + struct tevent_req *req; + time_t now, refresh_interval; + bool offline; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_fetch_hbac_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->access_ctx = access_ctx; + state->sdap_ctx = access_ctx->sdap_ctx; + state->ipa_options = access_ctx->ipa_options; + state->search_bases = access_ctx->hbac_search_bases; + state->hosts = talloc_zero(state, struct ipa_common_entries); + if (state->hosts == NULL) { + ret = ENOMEM; + goto immediately; + } + state->services = talloc_zero(state, struct ipa_common_entries); + if (state->hosts == NULL) { + ret = ENOMEM; + goto immediately; + } + state->rules = talloc_zero(state, struct ipa_common_entries); + if (state->rules == NULL) { + ret = ENOMEM; + goto immediately; + } + + if (state->search_bases == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No HBAC search base found.\n"); + ret = EINVAL; + goto immediately; + } + + state->sdap_op = sdap_id_op_create(state, state->sdap_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto immediately; + } + + offline = be_is_offline(be_ctx); + DEBUG(SSSDBG_TRACE_ALL, "Connection status is [%s].\n", + offline ? "offline" : "online"); + + refresh_interval = dp_opt_get_int(state->ipa_options, IPA_HBAC_REFRESH); + now = time(NULL); + + if (offline || now < access_ctx->last_update + refresh_interval) { + DEBUG(SSSDBG_TRACE_FUNC, "Performing cached HBAC evaluation\n"); + ret = EOK; + goto immediately; + } + + ret = ipa_fetch_hbac_retry(req); + if (ret != EAGAIN) { + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t ipa_fetch_hbac_retry(struct tevent_req *req) +{ + struct ipa_fetch_hbac_state *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct ipa_fetch_hbac_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed: " + "%d(%s)\n", ret, strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, ipa_fetch_hbac_connect_done, req); + + return EAGAIN; +} + +static void ipa_fetch_hbac_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + if (dp_error == DP_ERR_OFFLINE) { + ret = EOK; + goto done; + } + + ret = ipa_fetch_hbac_hostinfo(req); + if (ret == EAGAIN) { + return; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_fetch_hbac_hostinfo(struct tevent_req *req) +{ + struct ipa_fetch_hbac_state *state; + struct tevent_req *subreq; + const char *hostname; + bool srchost; + + state = tevent_req_data(req, struct ipa_fetch_hbac_state); + + srchost = dp_opt_get_bool(state->ipa_options, IPA_HBAC_SUPPORT_SRCHOST); + if (srchost) { + /* Support srchost + * -> we don't want any particular host, + * we want all hosts + */ + hostname = NULL; + + /* THIS FEATURE IS DEPRECATED */ + DEBUG(SSSDBG_MINOR_FAILURE, "WARNING: Using deprecated option " + "ipa_hbac_support_srchost.\n"); + sss_log(SSS_LOG_NOTICE, "WARNING: Using deprecated option " + "ipa_hbac_support_srchost.\n"); + } else { + hostname = dp_opt_get_string(state->ipa_options, IPA_HOSTNAME); + } + + subreq = ipa_host_info_send(state, state->ev, + sdap_id_op_handle(state->sdap_op), + state->sdap_ctx->opts, hostname, + state->access_ctx->host_map, + state->access_ctx->hostgroup_map, + state->access_ctx->host_search_bases); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_fetch_hbac_hostinfo_done, req); + + return EAGAIN; +} + +static void ipa_fetch_hbac_hostinfo_done(struct tevent_req *subreq) +{ + struct ipa_fetch_hbac_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + int dp_error; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_fetch_hbac_state); + + ret = ipa_host_info_recv(subreq, state, + &state->hosts->entry_count, + &state->hosts->entries, + &state->hosts->group_count, + &state->hosts->groups); + state->hosts->entry_subdir = HBAC_HOSTS_SUBDIR; + state->hosts->group_subdir = HBAC_HOSTGROUPS_SUBDIR; + talloc_zfree(subreq); + + if (ret != EOK) { + /* Only call sdap_id_op_done in case of an error to trigger a + * failover. In general changing the tevent_req layout would be better + * so that all searches are in another sub-request so that we can + * error out at any step and the parent request can call + * sdap_id_op_done just once. */ + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = ipa_fetch_hbac_retry(req); + if (ret != EAGAIN) { + goto done; + } + return; + } + goto done; + } + + subreq = ipa_hbac_service_info_send(state, state->ev, + sdap_id_op_handle(state->sdap_op), + state->sdap_ctx->opts, + state->search_bases); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ipa_fetch_hbac_services_done, req); + + return; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static void ipa_fetch_hbac_services_done(struct tevent_req *subreq) +{ + struct ipa_fetch_hbac_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_fetch_hbac_state); + + ret = ipa_hbac_service_info_recv(subreq, state, + &state->services->entry_count, + &state->services->entries, + &state->services->group_count, + &state->services->groups); + state->services->entry_subdir = HBAC_SERVICES_SUBDIR; + state->services->group_subdir = HBAC_SERVICEGROUPS_SUBDIR; + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + /* Get the ipa_host attrs */ + ret = ipa_get_host_attrs(state->ipa_options, + state->hosts->entry_count, + state->hosts->entries, + &state->ipa_host); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not locate IPA host.\n"); + goto done; + } + + subreq = ipa_hbac_rule_info_send(state, state->ev, + sdap_id_op_handle(state->sdap_op), + state->sdap_ctx->opts, + state->search_bases, + state->ipa_host); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ipa_fetch_hbac_rules_done, req); + + return; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static void ipa_fetch_hbac_rules_done(struct tevent_req *subreq) +{ + struct ipa_fetch_hbac_state *state = NULL; + struct tevent_req *req = NULL; + int dp_error; + errno_t ret; + bool found; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_fetch_hbac_state); + + ret = ipa_hbac_rule_info_recv(subreq, state, + &state->rules->entry_count, + &state->rules->entries); + state->rules->entry_subdir = HBAC_RULES_SUBDIR; + talloc_zfree(subreq); + if (ret == ENOENT) { + /* Set ret to EOK so we can safely call sdap_id_op_done. */ + found = false; + ret = EOK; + } else if (ret == EOK) { + found = true; + } else { + goto done; + } + + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = ipa_fetch_hbac_retry(req); + if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (found == false) { + /* No rules were found that apply to this host. */ + ret = ipa_common_purge_rules(state->be_ctx->domain, + HBAC_RULES_SUBDIR); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to remove HBAC rules\n"); + goto done; + } + + ret = ENOENT; + goto done; + } + + ret = ipa_common_save_rules(state->be_ctx->domain, + state->hosts, state->services, state->rules, + &state->access_ctx->last_update); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save HBAC rules\n"); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_fetch_hbac_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t ipa_hbac_evaluate_rules(struct be_ctx *be_ctx, + struct dp_option *ipa_options, + struct pam_data *pd) +{ + TALLOC_CTX *tmp_ctx; + struct hbac_ctx hbac_ctx; + struct hbac_rule **hbac_rules; + struct hbac_eval_req *eval_req; + enum hbac_eval_result result; + struct hbac_info *info = NULL; + const char **attrs_get_cached_rules; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + hbac_ctx.be_ctx = be_ctx; + hbac_ctx.ipa_options = ipa_options; + hbac_ctx.pd = pd; + + /* Get HBAC rules from the sysdb */ + attrs_get_cached_rules = hbac_get_attrs_to_get_cached_rules(tmp_ctx); + if (attrs_get_cached_rules == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "hbac_get_attrs_to_get_cached_rules() failed\n"); + ret = ENOMEM; + goto done; + } + ret = ipa_common_get_cached_rules(tmp_ctx, be_ctx->domain, + IPA_HBAC_RULE, HBAC_RULES_SUBDIR, + attrs_get_cached_rules, + &hbac_ctx.rule_count, &hbac_ctx.rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not retrieve rules from the cache\n"); + goto done; + } + + ret = hbac_ctx_to_rules(tmp_ctx, &hbac_ctx, &hbac_rules, &eval_req); + if (ret == EPERM) { + DEBUG(SSSDBG_CRIT_FAILURE, + "DENY rules detected. Denying access to all users\n"); + ret = ERR_ACCESS_DENIED; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not construct HBAC rules\n"); + goto done; + } + + hbac_enable_debug(hbac_debug_messages); + + result = hbac_evaluate(hbac_rules, eval_req, &info); + if (result == HBAC_EVAL_ALLOW) { + DEBUG(SSSDBG_MINOR_FAILURE, "Access granted by HBAC rule [%s]\n", + info->rule_name); + ret = EOK; + goto done; + } else if (result == HBAC_EVAL_ERROR) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error [%s] occurred in rule [%s]\n", + hbac_error_string(info->code), info->rule_name); + ret = EIO; + goto done; + } else if (result == HBAC_EVAL_OOM) { + DEBUG(SSSDBG_CRIT_FAILURE, "Insufficient memory\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "Access denied by HBAC rules\n"); + ret = ERR_ACCESS_DENIED; + +done: + hbac_free_info(info); + talloc_free(tmp_ctx); + return ret; +} + +struct ipa_pam_access_handler_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct ipa_access_ctx *access_ctx; + struct pam_data *pd; +}; + +static void ipa_pam_access_handler_sdap_done(struct tevent_req *subreq); +static void ipa_pam_access_handler_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_pam_access_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_access_ctx *access_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct ipa_pam_access_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_pam_access_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + state->ev = params->ev; + state->be_ctx = params->be_ctx; + state->access_ctx = access_ctx; + + subreq = sdap_access_send(state, params->ev, params->be_ctx, + params->domain, access_ctx->sdap_access_ctx, + access_ctx->sdap_ctx->conn, pd); + if (subreq == NULL) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_pam_access_handler_sdap_done, req); + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ipa_pam_access_handler_sdap_done(struct tevent_req *subreq) +{ + struct ipa_pam_access_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_access_handler_state); + + ret = sdap_access_recv(subreq); + talloc_free(subreq); + switch (ret) { + case EOK: + case ERR_PASSWORD_EXPIRED_WARN: + /* Account wasn't locked. Continue below to HBAC processing. */ + state->pd->pam_status = PAM_SUCCESS; + break; + case ERR_PASSWORD_EXPIRED_RENEW: + state->pd->pam_status = PAM_NEW_AUTHTOK_REQD; + break; + case ERR_ACCESS_DENIED: + case ERR_PASSWORD_EXPIRED_REJECT: + /* Account was locked or password expired. */ + state->pd->pam_status = PAM_PERM_DENIED; + goto done; + case ERR_ACCOUNT_EXPIRED: + state->pd->pam_status = PAM_ACCT_EXPIRED; + goto done; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Error retrieving access check result " + "[%d]: %s.\n", ret, sss_strerror(ret)); + state->pd->pam_status = PAM_SYSTEM_ERR; + break; + } + + subreq = ipa_fetch_hbac_send(state, state->ev, state->be_ctx, + state->access_ctx); + if (subreq == NULL) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + /* The callback function will not overwrite pam_status in case of + * success. Because of that, pam_status must be set to the desired + * value in advance. */ + tevent_req_set_callback(subreq, ipa_pam_access_handler_done, req); + + return; + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static void ipa_pam_access_handler_done(struct tevent_req *subreq) +{ + struct ipa_pam_access_handler_state *state; + struct tevent_req *req; + int preset_pam_status; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_access_handler_state); + + ret = ipa_fetch_hbac_recv(subreq); + talloc_free(subreq); + + if (ret == ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "No HBAC rules found, denying access\n"); + state->pd->pam_status = PAM_PERM_DENIED; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to fetch HBAC rules [%d]: %s\n", + ret, sss_strerror(ret)); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + /* ipa_hbac_evaluate_rules() could overwrite state->pd->pam_status but + we don't want that. Save the previous value and set it back in case + of succcess. */ + preset_pam_status = state->pd->pam_status; + ret = ipa_hbac_evaluate_rules(state->be_ctx, + state->access_ctx->ipa_options, state->pd); + if (ret == EOK) { + state->pd->pam_status = preset_pam_status; + } else if (ret == ERR_ACCESS_DENIED) { + state->pd->pam_status = PAM_PERM_DENIED; + } else { + state->pd->pam_status = PAM_SYSTEM_ERR; + } +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +ipa_pam_access_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct ipa_pam_access_handler_state *state = NULL; + + state = tevent_req_data(req, struct ipa_pam_access_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} + +struct ipa_refresh_access_rules_state { + int dummy; +}; + +static void ipa_refresh_access_rules_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_refresh_access_rules_send(TALLOC_CTX *mem_ctx, + struct ipa_access_ctx *access_ctx, + void *no_input_data, + struct dp_req_params *params) +{ + struct ipa_refresh_access_rules_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + DEBUG(SSSDBG_TRACE_FUNC, "Refreshing HBAC rules\n"); + + req = tevent_req_create(mem_ctx, &state, + struct ipa_refresh_access_rules_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + subreq = ipa_fetch_hbac_send(state, params->ev, params->be_ctx, access_ctx); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + tevent_req_post(req, params->ev); + return req; + } + + tevent_req_set_callback(subreq, ipa_refresh_access_rules_done, req); + + return req; +} + +static void ipa_refresh_access_rules_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = ipa_fetch_hbac_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t ipa_refresh_access_rules_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + void **_no_output_data) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ipa/ipa_access.h b/src/providers/ipa/ipa_access.h new file mode 100644 index 0000000..9cec0d1 --- /dev/null +++ b/src/providers/ipa/ipa_access.h @@ -0,0 +1,76 @@ +/* + SSSD + + IPA Backend Module -- Access control + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef _IPA_ACCESS_H_ +#define _IPA_ACCESS_H_ + +#include "providers/ldap/ldap_common.h" + +enum ipa_access_mode { + IPA_ACCESS_DENY = 0, + IPA_ACCESS_ALLOW +}; + +struct ipa_access_ctx { + struct sdap_id_ctx *sdap_ctx; + struct dp_option *ipa_options; + time_t last_update; + struct sdap_access_ctx *sdap_access_ctx; + + struct sdap_attr_map *host_map; + struct sdap_attr_map *hostgroup_map; + struct sdap_search_base **host_search_bases; + struct sdap_search_base **hbac_search_bases; +}; + +struct hbac_ctx { + struct be_ctx *be_ctx; + struct dp_option *ipa_options; + struct pam_data *pd; + size_t rule_count; + struct sysdb_attrs **rules; +}; + +struct tevent_req * +ipa_pam_access_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_access_ctx *access_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +ipa_pam_access_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +struct tevent_req * +ipa_refresh_access_rules_send(TALLOC_CTX *mem_ctx, + struct ipa_access_ctx *access_ctx, + void *no_input_data, + struct dp_req_params *params); + +errno_t ipa_refresh_access_rules_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + void **_no_output_data); + +#endif /* _IPA_ACCESS_H_ */ diff --git a/src/providers/ipa/ipa_auth.c b/src/providers/ipa/ipa_auth.c new file mode 100644 index 0000000..1d61a10 --- /dev/null +++ b/src/providers/ipa/ipa_auth.c @@ -0,0 +1,478 @@ +/* + SSSD + + IPA Backend Module -- Authentication + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <security/pam_modules.h> + +#include "util/util.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/ipa/ipa_auth.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_config.h" + +struct get_password_migration_flag_state { + struct tevent_context *ev; + struct sdap_id_op *sdap_op; + struct sdap_id_ctx *sdap_id_ctx; + struct fo_server *srv; + char *ipa_realm; + bool password_migration; +}; + +static void get_password_migration_flag_auth_done(struct tevent_req *subreq); +static void get_password_migration_flag_done(struct tevent_req *subreq); + +static struct tevent_req *get_password_migration_flag_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sdap_id_ctx *sdap_id_ctx, + char *ipa_realm) +{ + int ret; + struct tevent_req *req, *subreq; + struct get_password_migration_flag_state *state; + + if (sdap_id_ctx == NULL || ipa_realm == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing parameter.\n"); + return NULL; + } + + req = tevent_req_create(memctx, &state, + struct get_password_migration_flag_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->sdap_id_ctx = sdap_id_ctx; + state->srv = NULL; + state->password_migration = false; + state->ipa_realm = ipa_realm; + + state->sdap_op = sdap_id_op_create(state, + state->sdap_id_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed.\n"); + goto fail; + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (!subreq) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: %d(%s).\n", + ret, strerror(ret)); + goto fail; + } + + tevent_req_set_callback(subreq, get_password_migration_flag_auth_done, req); + + return req; + +fail: + talloc_zfree(req); + return NULL; +} + +static void get_password_migration_flag_auth_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_password_migration_flag_state *state = tevent_req_data(req, + struct get_password_migration_flag_state); + int ret, dp_error; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No IPA server is available, cannot get the " + "migration flag while offline\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect to IPA server: [%d](%s)\n", + ret, strerror(ret)); + } + + tevent_req_error(req, ret); + return; + } + + subreq = ipa_get_config_send(state, state->ev, + sdap_id_op_handle(state->sdap_op), + state->sdap_id_ctx->opts, state->ipa_realm, + NULL, NULL, NULL); + + tevent_req_set_callback(subreq, get_password_migration_flag_done, req); +} + +static void get_password_migration_flag_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_password_migration_flag_state *state = tevent_req_data(req, + struct get_password_migration_flag_state); + int ret; + struct sysdb_attrs *reply = NULL; + const char *value = NULL; + + ret = ipa_get_config_recv(subreq, state, &reply); + talloc_zfree(subreq); + if (ret) { + DEBUG(SSSDBG_IMPORTANT_INFO, "Unable to retrieve migration flag " + "from IPA server"); + goto done; + } + + ret = sysdb_attrs_get_string(reply, IPA_CONFIG_MIGRATION_ENABLED, &value); + if (ret == EOK && strcasecmp(value, "true") == 0) { + state->password_migration = true; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } +} + +static int get_password_migration_flag_recv(struct tevent_req *req, + bool *password_migration) +{ + struct get_password_migration_flag_state *state = tevent_req_data(req, + struct get_password_migration_flag_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *password_migration = state->password_migration; + return EOK; +} + +struct ipa_pam_auth_handler_state { + struct tevent_context *ev; + struct ipa_auth_ctx *auth_ctx; + struct be_ctx *be_ctx; + struct pam_data *pd; + struct sss_domain_info *dom; +}; + +static void ipa_pam_auth_handler_krb5_done(struct tevent_req *subreq); +static void ipa_pam_auth_handler_flag_done(struct tevent_req *subreq); +static void ipa_pam_auth_handler_connect_done(struct tevent_req *subreq); +static void ipa_pam_auth_handler_auth_done(struct tevent_req *subreq); +static void ipa_pam_auth_handler_retry_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_pam_auth_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_auth_ctx *auth_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct ipa_pam_auth_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_pam_auth_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + state->ev = params->ev; + state->auth_ctx = auth_ctx; + state->be_ctx = params->be_ctx; + state->dom = find_domain_by_name(state->be_ctx->domain, + state->pd->domain, + true); + if (state->dom == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown domain %s\n", state->pd->domain); + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + pd->pam_status = PAM_SYSTEM_ERR; + + subreq = krb5_auth_queue_send(state, params->ev, params->be_ctx, + pd, auth_ctx->krb5_auth_ctx); + if (subreq == NULL) { + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_pam_auth_handler_krb5_done, req); + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ipa_pam_auth_handler_krb5_done(struct tevent_req *subreq) +{ + struct ipa_pam_auth_handler_state *state; + struct tevent_req *req; + int dp_err; + char *realm; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_auth_handler_state); + + state->pd->pam_status = PAM_SYSTEM_ERR; + ret = krb5_auth_queue_recv(subreq, &state->pd->pam_status, &dp_err); + talloc_free(subreq); + if (ret != EOK && state->pd->pam_status != PAM_CRED_ERR) { + DEBUG(SSSDBG_OP_FAILURE, "KRB5 auth failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (dp_err != DP_ERR_OK) { + goto done; + } + + if (state->pd->cmd == SSS_PAM_AUTHENTICATE + && state->pd->pam_status == PAM_CRED_ERR + && !IS_SUBDOMAIN(state->dom)) { + realm = dp_opt_get_string(state->auth_ctx->ipa_options, IPA_KRB5_REALM); + subreq = get_password_migration_flag_send(state, state->ev, + state->auth_ctx->sdap_id_ctx, + realm); + if (subreq == NULL) { + goto done; + } + + tevent_req_set_callback(subreq, ipa_pam_auth_handler_flag_done, req); + return; + } + + /* PAM_CRED_ERR is used to indicate to the IPA provider that trying + * password migration would make sense. From this point on it isn't + * necessary to keep this status, so it can be translated to PAM_AUTH_ERR. + */ + if (state->pd->pam_status == PAM_CRED_ERR) { + state->pd->pam_status = PAM_AUTH_ERR; + } + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static void ipa_pam_auth_handler_flag_done(struct tevent_req *subreq) +{ + struct ipa_pam_auth_handler_state *state; + struct sdap_auth_ctx *sdap_auth_ctx; + bool password_migration = false; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_auth_handler_state); + + ret = get_password_migration_flag_recv(subreq, &password_migration); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to get password migration flag " + "[%d]: %s\n", ret, sss_strerror(ret)); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + if (password_migration) { + sdap_auth_ctx = state->auth_ctx->sdap_auth_ctx; + subreq = sdap_cli_connect_send(state, state->ev, + sdap_auth_ctx->opts, + sdap_auth_ctx->be, + sdap_auth_ctx->service, + true, CON_TLS_ON, true); + if (subreq == NULL) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + tevent_req_set_callback(subreq, ipa_pam_auth_handler_connect_done, req); + return; + } + + /* PAM_CRED_ERR is used to indicate to the IPA provider that trying + * password migration would make sense. From this point on it isn't + * necessary to keep this status, so it can be translated to PAM_AUTH_ERR. + */ + if (state->pd->pam_status == PAM_CRED_ERR) { + state->pd->pam_status = PAM_AUTH_ERR; + } + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static void ipa_pam_auth_handler_connect_done(struct tevent_req *subreq) +{ + struct ipa_pam_auth_handler_state *state; + struct tevent_req *req; + struct sdap_handle *sh = NULL; + const char *attrs[] = {SYSDB_ORIG_DN, NULL}; + struct ldb_message *msg; + const char *dn; + int timeout; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_auth_handler_state); + + state->pd->pam_status = PAM_SYSTEM_ERR; + + ret = sdap_cli_connect_recv(subreq, state, NULL, &sh, NULL); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot connect to LDAP server to perform " + "migration [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Assuming Kerberos password is missing, " + "starting password migration.\n"); + + ret = sysdb_search_user_by_name(state, state->be_ctx->domain, + state->pd->user, attrs, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_name failed.\n"); + goto done; + } + + dn = ldb_msg_find_attr_as_string(msg, SYSDB_ORIG_DN, NULL); + if (dn == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Missing original DN for user [%s].\n", + state->pd->user); + goto done; + } + + timeout = dp_opt_get_int(state->auth_ctx->sdap_auth_ctx->opts->basic, + SDAP_OPT_TIMEOUT); + + subreq = sdap_auth_send(state, state->ev, sh, NULL, NULL, dn, + state->pd->authtok, timeout); + if (subreq == NULL) { + goto done; + } + + tevent_req_set_callback(subreq, ipa_pam_auth_handler_auth_done, req); + return; + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static void ipa_pam_auth_handler_auth_done(struct tevent_req *subreq) +{ + struct ipa_pam_auth_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_auth_handler_state); + + ret = sdap_auth_recv(subreq, state, NULL); + + talloc_free(subreq); + switch (ret) { + case EOK: + break; + case ERR_AUTH_DENIED: + case ERR_AUTH_FAILED: + case ERR_PASSWORD_EXPIRED: + /* TODO: do we need to handle expired passwords? */ + DEBUG(SSSDBG_MINOR_FAILURE, "LDAP authentication failed, " + "password migration not possible.\n"); + state->pd->pam_status = PAM_CRED_INSUFFICIENT; + goto done; + default: + DEBUG(SSSDBG_OP_FAILURE, "auth_send request failed.\n"); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "LDAP authentication succeeded, " + "trying Kerberos authentication again.\n"); + + subreq = krb5_auth_queue_send(state, state->ev, state->be_ctx, state->pd, + state->auth_ctx->krb5_auth_ctx); + if (subreq == NULL) { + goto done; + } + + tevent_req_set_callback(subreq, ipa_pam_auth_handler_retry_done, req); + return; + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static void ipa_pam_auth_handler_retry_done(struct tevent_req *subreq) +{ + struct ipa_pam_auth_handler_state *state; + struct tevent_req *req; + int dp_err; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_auth_handler_state); + + ret = krb5_auth_queue_recv(subreq, &state->pd->pam_status, &dp_err); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "krb5_auth_recv request failed.\n"); + state->pd->pam_status = PAM_SYSTEM_ERR; + } + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +ipa_pam_auth_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct ipa_pam_auth_handler_state *state = NULL; + + state = tevent_req_data(req, struct ipa_pam_auth_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/ipa/ipa_auth.h b/src/providers/ipa/ipa_auth.h new file mode 100644 index 0000000..53666eb --- /dev/null +++ b/src/providers/ipa/ipa_auth.h @@ -0,0 +1,42 @@ +/* + SSSD + + IPA Backend Module -- Authentication + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef _IPA_AUTH_H_ +#define _IPA_AUTH_H_ + +#include "providers/backend.h" +#include "providers/ipa/ipa_common.h" + +struct tevent_req * +ipa_pam_auth_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_auth_ctx *auth_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +ipa_pam_auth_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +#endif /* _IPA_AUTH_H_ */ diff --git a/src/providers/ipa/ipa_autofs.c b/src/providers/ipa/ipa_autofs.c new file mode 100644 index 0000000..29814f7 --- /dev/null +++ b/src/providers/ipa/ipa_autofs.c @@ -0,0 +1,65 @@ +/* + SSSD + + IPA Provider Initialization functions + + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2009 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "util/child_common.h" +#include "providers/ipa/ipa_common.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/ipa/ipa_id.h" +#include "providers/ipa/ipa_auth.h" +#include "providers/ipa/ipa_access.h" +#include "providers/ipa/ipa_dyndns.h" +#include "providers/ipa/ipa_selinux.h" + +errno_t ipa_autofs_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_method *dp_methods) +{ + int ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing autofs IPA back end\n"); + + ret = ipa_get_autofs_options(id_ctx->ipa_options, + sysdb_ctx_get_ldb(be_ctx->domain->sysdb), + be_ctx->cdb, + be_ctx->conf_path, &id_ctx->sdap_id_ctx->opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot get IPA autofs options\n"); + return ret; + } + + dp_set_method(dp_methods, DPM_AUTOFS_ENUMERATE, + sdap_autofs_enumerate_handler_send, sdap_autofs_enumerate_handler_recv, id_ctx->sdap_id_ctx, + struct sdap_id_ctx, struct dp_autofs_data, dp_no_output); + + dp_set_method(dp_methods, DPM_AUTOFS_GET_MAP, + sdap_autofs_get_map_handler_send, sdap_autofs_get_map_handler_recv, id_ctx->sdap_id_ctx, + struct sdap_id_ctx, struct dp_autofs_data, dp_no_output); + + dp_set_method(dp_methods, DPM_AUTOFS_GET_ENTRY, + sdap_autofs_get_entry_handler_send, sdap_autofs_get_entry_handler_recv, id_ctx->sdap_id_ctx, + struct sdap_id_ctx, struct dp_autofs_data, dp_no_output); + + return ret; +} diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c new file mode 100644 index 0000000..01c835c --- /dev/null +++ b/src/providers/ipa/ipa_common.c @@ -0,0 +1,1370 @@ +/* + SSSD + + IPA Provider Common Functions + + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2009 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <netdb.h> +#include <ctype.h> +#include <arpa/inet.h> +#include <ldb.h> + +#include "db/sysdb_selinux.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_dyndns.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/be_dyndns.h" +#include "util/sss_krb5.h" +#include "db/sysdb_services.h" +#include "db/sysdb_autofs.h" + +#include "providers/ipa/ipa_opts.h" +#include "providers/data_provider/dp_private.h" + +int ipa_get_options(TALLOC_CTX *memctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct sss_domain_info *dom, + struct ipa_options **_opts) +{ + struct ipa_options *opts; + char *domain; + char *server; + char *realm; + char *ipa_hostname; + int ret; + char hostname[HOST_NAME_MAX + 1]; + + opts = talloc_zero(memctx, struct ipa_options); + if (!opts) return ENOMEM; + + ret = dp_get_options(opts, cdb, conf_path, + ipa_basic_opts, + IPA_OPTS_BASIC, + &opts->basic); + if (ret != EOK) { + goto done; + } + + domain = dp_opt_get_string(opts->basic, IPA_DOMAIN); + if (!domain) { + ret = dp_opt_set_string(opts->basic, IPA_DOMAIN, dom->name); + if (ret != EOK) { + goto done; + } + domain = dom->name; + } + + server = dp_opt_get_string(opts->basic, IPA_SERVER); + if (!server) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No ipa server set, will use service discovery!\n"); + } + + ipa_hostname = dp_opt_get_string(opts->basic, IPA_HOSTNAME); + if (ipa_hostname == NULL) { + ret = gethostname(hostname, sizeof(hostname)); + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "gethostname failed [%d][%s].\n", ret, + strerror(ret)); + goto done; + } + hostname[HOST_NAME_MAX] = '\0'; + DEBUG(SSSDBG_TRACE_ALL, "Setting ipa_hostname to [%s].\n", hostname); + ret = dp_opt_set_string(opts->basic, IPA_HOSTNAME, hostname); + if (ret != EOK) { + goto done; + } + } + + /* First check whether the realm has been manually specified */ + realm = dp_opt_get_string(opts->basic, IPA_KRB5_REALM); + if (!realm) { + /* No explicit krb5_realm, use the IPA domain, transform to upper-case */ + realm = get_uppercase_realm(opts, domain); + if (!realm) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(opts->basic, IPA_KRB5_REALM, + realm); + if (ret != EOK) { + goto done; + } + } + + ret = EOK; + *_opts = opts; + +done: + if (ret != EOK) { + talloc_zfree(opts); + } + return ret; +} + +static errno_t ipa_parse_search_base(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + struct dp_option *opts, int class, + struct sdap_search_base ***_search_bases) +{ + const char *class_name; + char *unparsed_base; + + *_search_bases = NULL; + + switch (class) { + case IPA_HBAC_SEARCH_BASE: + class_name = "IPA_HBAC"; + break; + case IPA_SELINUX_SEARCH_BASE: + class_name = "IPA_SELINUX"; + break; + case IPA_SUBDOMAINS_SEARCH_BASE: + class_name = "IPA_SUBDOMAINS"; + break; + case IPA_MASTER_DOMAIN_SEARCH_BASE: + class_name = "IPA_MASTER_DOMAIN"; + break; + case IPA_RANGES_SEARCH_BASE: + class_name = "IPA_RANGES"; + break; + case IPA_VIEWS_SEARCH_BASE: + class_name = "IPA_VIEWS"; + break; + case IPA_DESKPROFILE_SEARCH_BASE: + class_name = "IPA_DESKPROFILE"; + break; + case IPA_SUBID_RANGES_SEARCH_BASE: + class_name = "IPA_SUBID_RANGES"; + break; + default: + DEBUG(SSSDBG_CONF_SETTINGS, + "Unknown search base type: [%d]\n", class); + class_name = "UNKNOWN"; + /* Non-fatal */ + break; + } + + unparsed_base = dp_opt_get_string(opts, class); + if (!unparsed_base || unparsed_base[0] == '\0') return ENOENT; + + return common_parse_search_base(mem_ctx, unparsed_base, ldb, + class_name, NULL, + _search_bases); +} + +int ipa_get_id_options(struct ipa_options *ipa_opts, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + struct sdap_options **_opts) +{ + TALLOC_CTX *tmpctx; + char *basedn; + char *realm; + char *value; + int ret; + int i; + bool server_mode; + struct ldb_context *ldb; + + ldb = sysdb_ctx_get_ldb(dp->be_ctx->domain->sysdb); + + tmpctx = talloc_new(ipa_opts); + if (!tmpctx) { + return ENOMEM; + } + + ipa_opts->id = talloc_zero(ipa_opts, struct sdap_options); + if (!ipa_opts->id) { + ret = ENOMEM; + goto done; + } + ipa_opts->id->dp = dp; + + ret = sdap_domain_add(ipa_opts->id, + ipa_opts->id_ctx->sdap_id_ctx->be->domain, + NULL); + if (ret != EOK) { + goto done; + } + + /* get sdap options */ + ret = dp_get_options(ipa_opts->id, cdb, conf_path, + ipa_def_ldap_opts, + SDAP_OPTS_BASIC, + &ipa_opts->id->basic); + if (ret != EOK) { + goto done; + } + + /* sssd-ipa can't use simple bind, ignore option that potentially can be set + * for sssd-ldap in the same domain + */ + ret = dp_opt_set_string(ipa_opts->id->basic, + SDAP_DEFAULT_AUTHTOK_TYPE, NULL); + if (ret != EOK) { + goto done; + } + + ret = domain_to_basedn(tmpctx, + dp_opt_get_string(ipa_opts->basic, IPA_KRB5_REALM), + &basedn); + if (ret != EOK) { + goto done; + } + + if (NULL == dp_opt_get_string(ipa_opts->id->basic, SDAP_SEARCH_BASE)) { + /* FIXME: get values by querying IPA */ + /* set search base */ + value = talloc_asprintf(tmpctx, "cn=accounts,%s", basedn); + if (!value) { + ret = ENOMEM; + goto done; + } + ret = dp_opt_set_string(ipa_opts->id->basic, + SDAP_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->id->basic, SDAP_SEARCH_BASE)); + } + ret = sdap_parse_search_base(ipa_opts->id, ldb, ipa_opts->id->basic, + SDAP_SEARCH_BASE, + &ipa_opts->id->sdom->search_bases); + if (ret != EOK) goto done; + + /* set krb realm */ + if (NULL == dp_opt_get_string(ipa_opts->id->basic, SDAP_KRB5_REALM)) { + realm = dp_opt_get_string(ipa_opts->basic, IPA_KRB5_REALM); + value = talloc_strdup(tmpctx, realm); + if (value == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + ret = dp_opt_set_string(ipa_opts->id->basic, + SDAP_KRB5_REALM, value); + if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_KRB5_REALM].opt_name, + dp_opt_get_string(ipa_opts->id->basic, SDAP_KRB5_REALM)); + } + + ret = sdap_set_sasl_options(ipa_opts->id, + dp_opt_get_string(ipa_opts->basic, + IPA_HOSTNAME), + dp_opt_get_string(ipa_opts->id->basic, + SDAP_KRB5_REALM), + dp_opt_get_string(ipa_opts->id->basic, + SDAP_KRB5_KEYTAB)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set the SASL-related options\n"); + goto done; + } + + /* fix schema to IPAv1 for now */ + ipa_opts->id->schema_type = SDAP_SCHEMA_IPA_V1; + + /* set user/group search bases if they are not specified */ + if (NULL == dp_opt_get_string(ipa_opts->id->basic, + SDAP_USER_SEARCH_BASE)) { + ret = dp_opt_set_string(ipa_opts->id->basic, SDAP_USER_SEARCH_BASE, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_SEARCH_BASE)); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_USER_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_USER_SEARCH_BASE)); + } + ret = sdap_parse_search_base(ipa_opts->id, ldb, ipa_opts->id->basic, + SDAP_USER_SEARCH_BASE, + &ipa_opts->id->sdom->user_search_bases); + if (ret != EOK) goto done; + + /* In server mode we need to search both cn=accounts,$SUFFIX and + * cn=trusts,$SUFFIX to allow trusted domain object accounts to be found. + * If cn=trusts,$SUFFIX is missing in the user search bases, add one + */ + server_mode = dp_opt_get_bool(ipa_opts->basic, IPA_SERVER_MODE); + if (server_mode != false) { + /* bases is not NULL at this point already */ + struct sdap_search_base **bases = ipa_opts->id->sdom->user_search_bases; + struct sdap_search_base *new_base = NULL; + + for (i = 0; bases[i] != NULL; i++) { + if (strcasestr(bases[i]->basedn, "cn=trusts,") != NULL) { + break; + } + } + if (NULL == bases[i]) { + /* no cn=trusts in the base, add a new one */ + char *new_dn = talloc_asprintf(bases, + "cn=trusts,%s", + basedn); + if (NULL == new_dn) { + ret = ENOMEM; + goto done; + } + + ret = sdap_create_search_base(bases, ldb, new_dn, + LDAP_SCOPE_SUBTREE, + "(objectClass=ipaIDObject)", + &new_base); + if (ret != EOK) { + goto done; + } + + bases = talloc_realloc(ipa_opts->id, + ipa_opts->id->sdom->user_search_bases, + struct sdap_search_base*, + i + 2); + + if (NULL == bases) { + ret = ENOMEM; + goto done; + } + + bases[i] = new_base; + bases[i+1] = NULL; + ipa_opts->id->sdom->user_search_bases = bases; + + DEBUG(SSSDBG_TRACE_FUNC, + "Option %s expanded to cover cn=trusts base\n", + ipa_opts->id->basic[SDAP_USER_SEARCH_BASE].opt_name); + } + } + + if (NULL == dp_opt_get_string(ipa_opts->id->basic, + SDAP_GROUP_SEARCH_BASE)) { + ret = dp_opt_set_string(ipa_opts->id->basic, SDAP_GROUP_SEARCH_BASE, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_SEARCH_BASE)); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_GROUP_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_GROUP_SEARCH_BASE)); + } + ret = sdap_parse_search_base(ipa_opts->id, ldb, ipa_opts->id->basic, + SDAP_GROUP_SEARCH_BASE, + &ipa_opts->id->sdom->group_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->id->basic, + SDAP_NETGROUP_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=ng,cn=alt,%s", basedn); + if (!value) { + ret = ENOMEM; + goto done; + } + ret = dp_opt_set_string(ipa_opts->id->basic, SDAP_NETGROUP_SEARCH_BASE, + value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_NETGROUP_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_NETGROUP_SEARCH_BASE)); + } + ret = sdap_parse_search_base(ipa_opts->id, ldb, ipa_opts->id->basic, + SDAP_NETGROUP_SEARCH_BASE, + &ipa_opts->id->sdom->netgroup_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->id->basic, + SDAP_HOST_SEARCH_BASE)) { + + value = dp_opt_get_string(ipa_opts->basic, IPA_HOST_SEARCH_BASE); + if (!value) { + value = dp_opt_get_string(ipa_opts->id->basic, SDAP_SEARCH_BASE); + } + + ret = dp_opt_set_string(ipa_opts->id->basic, SDAP_HOST_SEARCH_BASE, + value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_HOST_SEARCH_BASE].opt_name, + value); + } + ret = sdap_parse_search_base(ipa_opts->id->basic, ldb, ipa_opts->id->basic, + SDAP_HOST_SEARCH_BASE, + &ipa_opts->id->sdom->host_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_HBAC_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=hbac,%s", basedn); + if (!value) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_HBAC_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->basic[IPA_HBAC_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_HBAC_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts->basic, ldb, ipa_opts->basic, + IPA_HBAC_SEARCH_BASE, + &ipa_opts->hbac_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_SELINUX_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=selinux,%s", basedn); + if (!value) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_SELINUX_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->basic[IPA_SELINUX_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_SELINUX_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts->basic, ldb, ipa_opts->basic, + IPA_SELINUX_SEARCH_BASE, + &ipa_opts->selinux_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_DESKPROFILE_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=desktop-profile,%s", basedn); + if (!value) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_DESKPROFILE_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->basic[IPA_DESKPROFILE_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_DESKPROFILE_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts->basic, ldb, ipa_opts->basic, + IPA_DESKPROFILE_SEARCH_BASE, + &ipa_opts->deskprofile_search_bases); + if (ret != EOK) goto done; + +#ifdef BUILD_SUBID + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_SUBID_RANGES_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=subids,%s", + ipa_opts->id->sdom->search_bases[0]->basedn); + if (!value) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_SUBID_RANGES_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->basic[IPA_SUBID_RANGES_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_SUBID_RANGES_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts->basic, ldb, ipa_opts->basic, + IPA_SUBID_RANGES_SEARCH_BASE, + &ipa_opts->id->sdom->subid_ranges_search_bases); + if (ret != EOK) goto done; + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_subid_map, + SDAP_OPTS_SUBID_RANGE, + &ipa_opts->id->subid_map); + if (ret != EOK) { + goto done; + } +#endif + + value = dp_opt_get_string(ipa_opts->id->basic, SDAP_DEREF); + if (value != NULL) { + ret = deref_string_to_val(value, &i); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to verify ldap_deref option.\n"); + goto done; + } + } + + if (NULL == dp_opt_get_string(ipa_opts->id->basic, + SDAP_SERVICE_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=ipservices,%s", + dp_opt_get_string(ipa_opts->id->basic, + SDAP_SEARCH_BASE)); + if (!value) { + ret = ENOMEM; + goto done; + } + ret = dp_opt_set_string(ipa_opts->id->basic, + SDAP_SERVICE_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_SERVICE_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_SERVICE_SEARCH_BASE)); + } + ret = sdap_parse_search_base(ipa_opts->id, ldb, ipa_opts->id->basic, + SDAP_SERVICE_SEARCH_BASE, + &ipa_opts->id->sdom->service_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_SUBDOMAINS_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=trusts,%s", basedn); + if (value == NULL) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_SUBDOMAINS_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->basic[IPA_SUBDOMAINS_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_SUBDOMAINS_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts, ldb, ipa_opts->basic, + IPA_SUBDOMAINS_SEARCH_BASE, + &ipa_opts->subdomains_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_MASTER_DOMAIN_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=ad,cn=etc,%s", basedn); + if (value == NULL) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_MASTER_DOMAIN_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->basic[IPA_MASTER_DOMAIN_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_MASTER_DOMAIN_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts, ldb, ipa_opts->basic, + IPA_MASTER_DOMAIN_SEARCH_BASE, + &ipa_opts->master_domain_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_RANGES_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=ranges,cn=etc,%s", basedn); + if (value == NULL) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_RANGES_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->basic[IPA_RANGES_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_RANGES_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts, ldb, ipa_opts->basic, + IPA_RANGES_SEARCH_BASE, + &ipa_opts->ranges_search_bases); + if (ret != EOK) goto done; + + if (NULL == dp_opt_get_string(ipa_opts->basic, + IPA_VIEWS_SEARCH_BASE)) { + value = talloc_asprintf(tmpctx, "cn=views,cn=accounts,%s", basedn); + if (value == NULL) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->basic, IPA_VIEWS_SEARCH_BASE, value); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->basic[IPA_VIEWS_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->basic, + IPA_VIEWS_SEARCH_BASE)); + } + ret = ipa_parse_search_base(ipa_opts, ldb, ipa_opts->basic, + IPA_VIEWS_SEARCH_BASE, + &ipa_opts->views_search_bases); + if (ret != EOK) goto done; + + ret = sdap_get_map(ipa_opts->id, cdb, conf_path, + ipa_attr_map, + SDAP_AT_GENERAL, + &ipa_opts->id->gen_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_user_map, + SDAP_OPTS_USER, + &ipa_opts->id->user_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_extend_map_with_list(ipa_opts->id, ipa_opts->id, + SDAP_USER_EXTRA_ATTRS, + ipa_opts->id->user_map, + SDAP_OPTS_USER, + &ipa_opts->id->user_map, + &ipa_opts->id->user_map_cnt); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_group_map, + SDAP_OPTS_GROUP, + &ipa_opts->id->group_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_netgroup_map, + IPA_OPTS_NETGROUP, + &ipa_opts->id->netgroup_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_host_map, + SDAP_OPTS_HOST, + &ipa_opts->id->host_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_hostgroup_map, + IPA_OPTS_HOSTGROUP, + &ipa_opts->hostgroup_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_service_map, + SDAP_OPTS_SERVICES, + &ipa_opts->id->service_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_selinux_user_map, + IPA_OPTS_SELINUX_USERMAP, + &ipa_opts->selinuxuser_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_view_map, + IPA_OPTS_VIEW, + &ipa_opts->view_map); + if (ret != EOK) { + goto done; + } + + ret = sdap_get_map(ipa_opts->id, + cdb, conf_path, + ipa_override_map, + IPA_OPTS_OVERRIDE, + &ipa_opts->override_map); + if (ret != EOK) { + goto done; + } + + ret = EOK; + *_opts = ipa_opts->id; + +done: + talloc_zfree(tmpctx); + if (ret != EOK) { + talloc_zfree(ipa_opts->id); + } + return ret; +} + +int ipa_get_auth_options(struct ipa_options *ipa_opts, + struct confdb_ctx *cdb, + const char *conf_path, + struct dp_option **_opts) +{ + char *value; + char *copy = NULL; + int ret; + + ipa_opts->auth = talloc_zero(ipa_opts, struct dp_option); + if (ipa_opts->auth == NULL) { + ret = ENOMEM; + goto done; + } + + /* get krb5 options */ + ret = dp_get_options(ipa_opts, cdb, conf_path, + ipa_def_krb5_opts, + KRB5_OPTS, &ipa_opts->auth); + if (ret != EOK) { + goto done; + } + + /* If there is no KDC, try the deprecated krb5_kdcip option, too */ + /* FIXME - this can be removed in a future version */ + ret = krb5_try_kdcip(cdb, conf_path, ipa_opts->auth, KRB5_KDC); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_krb5_try_kdcip failed.\n"); + goto done; + } + + /* set krb realm */ + if (NULL == dp_opt_get_string(ipa_opts->auth, KRB5_REALM)) { + value = dp_opt_get_string(ipa_opts->basic, IPA_KRB5_REALM); + if (!value) { + ret = ENOMEM; + goto done; + } + copy = talloc_strdup(ipa_opts->auth, value); + if (copy == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + ret = dp_opt_set_string(ipa_opts->auth, KRB5_REALM, copy); + if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + ipa_opts->auth[KRB5_REALM].opt_name, + dp_opt_get_string(ipa_opts->auth, KRB5_REALM)); + } + + /* If krb5_fast_principal was not set explicitly, default to + * host/$client_hostname@REALM + */ + value = dp_opt_get_string(ipa_opts->auth, KRB5_FAST_PRINCIPAL); + if (value == NULL) { + value = talloc_asprintf(ipa_opts->auth, "host/%s@%s", + dp_opt_get_string(ipa_opts->basic, + IPA_HOSTNAME), + dp_opt_get_string(ipa_opts->auth, + KRB5_REALM)); + if (value == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->auth, KRB5_FAST_PRINCIPAL, + value); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot set %s!\n", + ipa_opts->auth[KRB5_FAST_PRINCIPAL].opt_name); + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->auth[KRB5_FAST_PRINCIPAL].opt_name, value); + } + + /* Set flag that controls whether we want to write the + * kdcinfo files at all + */ + ipa_opts->service->krb5_service->write_kdcinfo = \ + dp_opt_get_bool(ipa_opts->auth, KRB5_USE_KDCINFO); + DEBUG(SSSDBG_CONF_SETTINGS, "Option %s set to %s\n", + ipa_opts->auth[KRB5_USE_KDCINFO].opt_name, + ipa_opts->service->krb5_service->write_kdcinfo ? "true" : "false"); + if (ipa_opts->service->krb5_service->write_kdcinfo) { + sss_krb5_parse_lookahead( + dp_opt_get_string(ipa_opts->auth, KRB5_KDCINFO_LOOKAHEAD), + &ipa_opts->service->krb5_service->lookahead_primary, + &ipa_opts->service->krb5_service->lookahead_backup); + } + + *_opts = ipa_opts->auth; + ret = EOK; + +done: + talloc_free(copy); + if (ret != EOK) { + talloc_zfree(ipa_opts->auth); + } + return ret; +} + +static void ipa_resolve_callback(void *private_data, struct fo_server *server) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct ipa_service *service; + struct resolv_hostent *srvaddr; + struct sockaddr *sockaddr; + char *new_uri; + const char *srv_name; + socklen_t sockaddr_len; + int ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed\n"); + return; + } + + service = talloc_get_type(private_data, struct ipa_service); + if (!service) { + DEBUG(SSSDBG_CRIT_FAILURE, "FATAL: Bad private_data\n"); + talloc_free(tmp_ctx); + return; + } + + srvaddr = fo_get_server_hostent(server); + if (!srvaddr) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No hostent available for server (%s)\n", + fo_get_server_str_name(server)); + talloc_free(tmp_ctx); + return; + } + + sockaddr = resolv_get_sockaddr_address(tmp_ctx, srvaddr, LDAP_PORT, &sockaddr_len); + if (sockaddr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "resolv_get_sockaddr_address failed.\n"); + talloc_free(tmp_ctx); + return; + } + + srv_name = fo_get_server_name(server); + if (srv_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not get server host name\n"); + talloc_free(tmp_ctx); + return; + } + + new_uri = talloc_asprintf(service, "ldap://%s", srv_name); + if (!new_uri) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to copy URI ...\n"); + talloc_free(tmp_ctx); + return; + } + DEBUG(SSSDBG_TRACE_FUNC, "Constructed uri '%s'\n", new_uri); + + /* free old one and replace with new one */ + talloc_zfree(service->sdap->uri); + service->sdap->uri = new_uri; + talloc_zfree(service->sdap->sockaddr); + service->sdap->sockaddr = talloc_steal(service, sockaddr); + service->sdap->sockaddr_len = sockaddr_len; + + if (service->krb5_service->write_kdcinfo) { + ret = write_krb5info_file_from_fo_server(service->krb5_service, + server, + true, + SSS_KRB5KDC_FO_SRV, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "write to %s/kdcinfo.%s failed, authentication might fail.\n", + PUBCONF_PATH, service->krb5_service->realm); + } + } + + talloc_free(tmp_ctx); +} + +static errno_t _ipa_servers_init(struct be_ctx *ctx, + struct ipa_service *service, + struct ipa_options *options, + const char *servers, + bool primary) +{ + TALLOC_CTX *tmp_ctx; + char **list = NULL; + char *ipa_domain; + int ret = 0; + int i; + int j; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + /* split server parm into a list */ + ret = split_on_separator(tmp_ctx, servers, ',', true, true, &list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse server list!\n"); + goto done; + } + + for (j = 0; list[j]; j++) { + if (resolv_is_address(list[j])) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "ipa_server [%s] is detected as IP address, " + "this can cause GSSAPI problems\n", list[j]); + } + } + + /* now for each one add a new server to the failover service */ + for (i = 0; list[i]; i++) { + + talloc_steal(service, list[i]); + + if (be_fo_is_srv_identifier(list[i])) { + if (!primary) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to add server [%s] to failover service: " + "SRV resolution only allowed for primary servers!\n", + list[i]); + continue; + } + + ipa_domain = dp_opt_get_string(options->basic, IPA_DOMAIN); + ret = be_fo_add_srv_server(ctx, "IPA", "ldap", ipa_domain, + BE_FO_PROTO_TCP, false, NULL); + if (ret) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add server\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Added service lookup for service IPA\n"); + continue; + } + + /* It could be ipv6 address in square brackets. Remove + * the brackets if needed. */ + ret = remove_ipv6_brackets(list[i]); + if (ret != EOK) { + goto done; + } + + ret = be_fo_add_server(ctx, "IPA", list[i], 0, NULL, primary); + if (ret && ret != EEXIST) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to add server\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Added Server %s\n", list[i]); + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +static inline errno_t +ipa_primary_servers_init(struct be_ctx *ctx, struct ipa_service *service, + struct ipa_options *options, const char *servers) +{ + return _ipa_servers_init(ctx, service, options, servers, true); +} + +static inline errno_t +ipa_backup_servers_init(struct be_ctx *ctx, struct ipa_service *service, + struct ipa_options *options, const char *servers) +{ + return _ipa_servers_init(ctx, service, options, servers, false); +} + +static int ipa_user_data_cmp(void *ud1, void *ud2) +{ + return strcasecmp((char*) ud1, (char*) ud2); +} + +int ipa_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *primary_servers, + const char *backup_servers, + struct ipa_options *options, + struct ipa_service **_service) +{ + TALLOC_CTX *tmp_ctx; + struct ipa_service *service; + char *realm; + int ret; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + realm = dp_opt_get_string(options->basic, IPA_KRB5_REALM); + if (!realm) { + DEBUG(SSSDBG_CRIT_FAILURE, "No Kerberos realm set\n"); + ret = EINVAL; + goto done; + } + + service = talloc_zero(tmp_ctx, struct ipa_service); + if (!service) { + ret = ENOMEM; + goto done; + } + service->sdap = talloc_zero(service, struct sdap_service); + if (!service->sdap) { + ret = ENOMEM; + goto done; + } + + service->krb5_service = krb5_service_new(service, ctx, + "IPA", realm, + true, /* The configured value */ + 0, /* will be set later when */ + 0); /* the auth provider is set up */ + + if (!service->krb5_service) { + ret = ENOMEM; + goto done; + } + + ret = be_fo_add_service(ctx, "IPA", ipa_user_data_cmp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create failover service!\n"); + goto done; + } + + service->sdap->name = talloc_strdup(service, "IPA"); + if (!service->sdap->name) { + ret = ENOMEM; + goto done; + } + + service->sdap->kinit_service_name = service->krb5_service->name; + + if (!primary_servers) { + DEBUG(SSSDBG_CONF_SETTINGS, + "No primary servers defined, using service discovery\n"); + primary_servers = BE_SRV_IDENTIFIER; + } + + ret = ipa_primary_servers_init(ctx, service, options, primary_servers); + if (ret != EOK) { + goto done; + } + + if (backup_servers) { + ret = ipa_backup_servers_init(ctx, service, options, backup_servers); + if (ret != EOK) { + goto done; + } + } + + ret = be_fo_service_add_callback(memctx, ctx, "IPA", + ipa_resolve_callback, service); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add failover callback!\n"); + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + *_service = talloc_steal(memctx, service); + } + talloc_zfree(tmp_ctx); + return ret; +} + +int ipa_get_autofs_options(struct ipa_options *ipa_opts, + struct ldb_context *ldb, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_options **_opts) +{ + TALLOC_CTX *tmp_ctx; + char *basedn; + char *autofs_base; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = domain_to_basedn(tmp_ctx, + dp_opt_get_string(ipa_opts->basic, IPA_KRB5_REALM), + &basedn); + if (ret != EOK) { + goto done; + } + + if (NULL == dp_opt_get_string(ipa_opts->id->basic, + SDAP_AUTOFS_SEARCH_BASE)) { + + autofs_base = talloc_asprintf(tmp_ctx, "cn=%s,cn=automount,%s", + dp_opt_get_string(ipa_opts->basic, + IPA_AUTOMOUNT_LOCATION), + basedn); + if (!autofs_base) { + ret = ENOMEM; + goto done; + } + + ret = dp_opt_set_string(ipa_opts->id->basic, + SDAP_AUTOFS_SEARCH_BASE, + autofs_base); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Option %s set to %s\n", + ipa_opts->id->basic[SDAP_AUTOFS_SEARCH_BASE].opt_name, + dp_opt_get_string(ipa_opts->id->basic, + SDAP_AUTOFS_SEARCH_BASE)); + } + + ret = sdap_parse_search_base(ipa_opts->id, ldb, ipa_opts->id->basic, + SDAP_AUTOFS_SEARCH_BASE, + &ipa_opts->id->sdom->autofs_search_bases); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Could not parse autofs search base\n"); + goto done; + } + + ret = sdap_get_map(ipa_opts->id, cdb, conf_path, + ipa_autofs_mobject_map, + SDAP_OPTS_AUTOFS_MAP, + &ipa_opts->id->autofs_mobject_map); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not get autofs map object attribute map\n"); + goto done; + } + + ret = sdap_get_map(ipa_opts->id, cdb, conf_path, + ipa_autofs_entry_map, + SDAP_OPTS_AUTOFS_ENTRY, + &ipa_opts->id->autofs_entry_map); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not get autofs entry object attribute map\n"); + goto done; + } + + *_opts = ipa_opts->id; + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t ipa_get_dyndns_options(struct be_ctx *be_ctx, + struct ipa_options *ctx) +{ + errno_t ret; + char *val; + bool update; + int ttl; + + ret = be_nsupdate_init(ctx, be_ctx, ipa_dyndns_opts, &ctx->dyndns_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot initialize IPA dyndns opts [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (ctx->basic == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "IPA basic options not (yet) " + "initialized, cannot copy legacy options\n"); + return EOK; + } + + /* Reuse legacy option values */ + ret = confdb_get_string(be_ctx->cdb, ctx, be_ctx->conf_path, + "ipa_dyndns_update", NULL, &val); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get the value of %s\n", + "ipa_dyndns_update"); + /* Not fatal */ + } else if (ret == EOK && val) { + if (strcasecmp(val, "FALSE") == 0) { + update = false; + } else if (strcasecmp(val, "TRUE") == 0) { + update = true; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "ipa_dyndns_update value is not a boolean!\n"); + talloc_free(val); + return EINVAL; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "Deprecation warning: The option %s is " + "deprecated and should not be used in favor of %s\n", + "ipa_dyndns_update", "dyndns_update"); + + ret = dp_opt_set_bool(ctx->dyndns_ctx->opts, + DP_OPT_DYNDNS_UPDATE, update); + talloc_free(val); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set option value\n"); + return ret; + } + } + + ret = confdb_get_int(be_ctx->cdb, be_ctx->conf_path, + "ipa_dyndns_ttl", -1, &ttl); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get the value of %s\n", + "ipa_dyndns_ttl"); + /* Not fatal */ + } else if (ret == EOK && ttl != -1) { + DEBUG(SSSDBG_MINOR_FAILURE, "Deprecation warning: The option %s is " + "deprecated and should not be used in favor of %s\n", + "ipa_dyndns_ttl", "dyndns_ttl"); + + ret = dp_opt_set_int(ctx->dyndns_ctx->opts, DP_OPT_DYNDNS_TTL, ttl); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set option value\n"); + return ret; + } + } + + /* Reuse legacy option values */ + ret = confdb_get_string(be_ctx->cdb, ctx, be_ctx->conf_path, + "ipa_dyndns_iface", NULL, &val); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get the value of %s\n", + "ipa_dyndns_iface"); + /* Not fatal */ + } else if (ret == EOK && val) { + DEBUG(SSSDBG_MINOR_FAILURE, "Deprecation warning: The option %s is " + "deprecated and should not be used in favor of %s\n", + "ipa_dyndns_iface", "dyndns_iface"); + + ret = dp_opt_set_string(ctx->dyndns_ctx->opts, + DP_OPT_DYNDNS_IFACE, val); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set option value\n"); + return ret; + } + } + + return EOK; +} + +errno_t ipa_get_host_attrs(struct dp_option *ipa_options, + size_t host_count, + struct sysdb_attrs **hosts, + struct sysdb_attrs **_ipa_host) +{ + const char *ipa_hostname; + const char *hostname; + errno_t ret; + + *_ipa_host = NULL; + ipa_hostname = dp_opt_get_cstring(ipa_options, IPA_HOSTNAME); + if (ipa_hostname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing ipa_hostname, this should never happen.\n"); + ret = EINVAL; + goto done; + } + + for (size_t i = 0; i < host_count; i++) { + ret = sysdb_attrs_get_string(hosts[i], SYSDB_FQDN, &hostname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not locate IPA host\n"); + goto done; + } + + if (strcasecmp(hostname, ipa_hostname) == 0) { + *_ipa_host = hosts[i]; + break; + } + } + + if (*_ipa_host == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not locate IPA host\n"); + ret = EINVAL; + goto done; + } + + ret = EOK; + +done: + return ret; +} diff --git a/src/providers/ipa/ipa_common.h b/src/providers/ipa/ipa_common.h new file mode 100644 index 0000000..82b9622 --- /dev/null +++ b/src/providers/ipa/ipa_common.h @@ -0,0 +1,326 @@ +/* + SSSD + + IPA Common utility code + + Copyright (C) Simo Sorce <ssorce@redhat.com> 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef _IPA_COMMON_H_ +#define _IPA_COMMON_H_ + +#include "util/util.h" +#include "confdb/confdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/krb5/krb5_common.h" +#include "providers/ad/ad_common.h" +#include "providers/ad/ad_srv.h" + +#define IPA_CN "cn" +#define IPA_TRUSTED_DOMAIN_SID "ipaNTTrustedDomainSID" +#define IPA_RANGE_TYPE "ipaRangeType" +#define IPA_BASE_ID "ipaBaseID" +#define IPA_ID_RANGE_SIZE "ipaIDRangeSize" +#define IPA_BASE_RID "ipaBaseRID" +#define IPA_SECONDARY_BASE_RID "ipaSecondaryBaseRID" +#define IPA_ID_RANGE_MPG "ipaAutoPrivateGroups" + +struct ipa_service { + struct sdap_service *sdap; + struct krb5_service *krb5_service; +}; + +struct ipa_init_ctx; + +enum ipa_basic_opt { + IPA_DOMAIN = 0, + IPA_SERVER, + IPA_BACKUP_SERVER, + IPA_HOSTNAME, + IPA_HBAC_SEARCH_BASE, + IPA_HOST_SEARCH_BASE, /* only used if ldap_host_search_base is not set */ + IPA_SELINUX_SEARCH_BASE, + IPA_SUBDOMAINS_SEARCH_BASE, + IPA_MASTER_DOMAIN_SEARCH_BASE, + IPA_KRB5_REALM, + IPA_HBAC_REFRESH, + IPA_SELINUX_REFRESH, + IPA_HBAC_SUPPORT_SRCHOST, + IPA_AUTOMOUNT_LOCATION, + IPA_RANGES_SEARCH_BASE, + IPA_ENABLE_DNS_SITES, + IPA_SERVER_MODE, + IPA_VIEWS_SEARCH_BASE, + IPA_KRB5_CONFD_PATH, + IPA_DESKPROFILE_SEARCH_BASE, + IPA_DESKPROFILE_REFRESH, + IPA_DESKPROFILE_REQUEST_INTERVAL, + IPA_SUBID_RANGES_SEARCH_BASE, + IPA_ACCESS_ORDER, + + IPA_OPTS_BASIC /* opts counter */ +}; + +enum ipa_netgroup_attrs { + IPA_OC_NETGROUP = 0, + IPA_AT_NETGROUP_NAME, + IPA_AT_NETGROUP_MEMBER, + IPA_AT_NETGROUP_MEMBER_OF, + IPA_AT_NETGROUP_MEMBER_USER, + IPA_AT_NETGROUP_MEMBER_HOST, + IPA_AT_NETGROUP_EXTERNAL_HOST, + IPA_AT_NETGROUP_DOMAIN, + IPA_AT_NETGROUP_UUID, + + IPA_OPTS_NETGROUP /* attrs counter */ +}; + +enum ipa_hostgroup_attrs { + IPA_OC_HOSTGROUP = 0, + IPA_AT_HOSTGROUP_NAME, + IPA_AT_HOSTGROUP_MEMBER_OF, + IPA_AT_HOSTGROUP_UUID, + + IPA_OPTS_HOSTGROUP /* attrs counter */ +}; + +enum ipa_selinux_usermap_attrs { + IPA_OC_SELINUX_USERMAP = 0, + IPA_AT_SELINUX_USERMAP_NAME, + IPA_AT_SELINUX_USERMAP_MEMBER_USER, + IPA_AT_SELINUX_USERMAP_MEMBER_HOST, + IPA_AT_SELINUX_USERMAP_SEE_ALSO, + IPA_AT_SELINUX_USERMAP_SELINUX_USER, + IPA_AT_SELINUX_USERMAP_ENABLED, + IPA_AT_SELINUX_USERMAP_USERCAT, + IPA_AT_SELINUX_USERMAP_HOSTCAT, + IPA_AT_SELINUX_USERMAP_UUID, + + IPA_OPTS_SELINUX_USERMAP /* attrs counter */ +}; + +enum ipa_view_attrs { + IPA_OC_VIEW = 0, + IPA_AT_VIEW_NAME, + + IPA_OPTS_VIEW +}; + +enum ipa_override_attrs { + IPA_OC_OVERRIDE = 0, + IPA_AT_OVERRIDE_ANCHOR_UUID, + IPA_OC_OVERRIDE_USER, + IPA_OC_OVERRIDE_GROUP, + IPA_AT_OVERRIDE_USER_NAME, + IPA_AT_OVERRIDE_UID_NUMBER, + IPA_AT_OVERRIDE_USER_GID_NUMBER, + IPA_AT_OVERRIDE_GECOS, + IPA_AT_OVERRIDE_HOMEDIR, + IPA_AT_OVERRIDE_SHELL, + IPA_AT_OVERRIDE_GROUP_NAME, + IPA_AT_OVERRIDE_GROUP_GID_NUMBER, + IPA_AT_OVERRIDE_USER_SSH_PUBLIC_KEY, + IPA_AT_OVERRIDE_USER_CERT, + IPA_AT_OVERRIDE_OBJECTCLASS, + + IPA_OPTS_OVERRIDE +}; + +enum ipa_sudorule_attrs { + IPA_OC_SUDORULE = 0, + IPA_AT_SUDORULE_NAME, + IPA_AT_SUDORULE_UUID, + IPA_AT_SUDORULE_ENABLED, + IPA_AT_SUDORULE_OPTION, + IPA_AT_SUDORULE_RUNASUSER, + IPA_AT_SUDORULE_RUNASGROUP, + IPA_AT_SUDORULE_ALLOWCMD, + IPA_AT_SUDORULE_DENYCMD, + IPA_AT_SUDORULE_HOST, + IPA_AT_SUDORULE_USER, + IPA_AT_SUDORULE_NOTAFTER, + IPA_AT_SUDORULE_NOTBEFORE, + IPA_AT_SUDORULE_SUDOORDER, + IPA_AT_SUDORULE_CMDCATEGORY, + IPA_AT_SUDORULE_HOSTCATEGORY, + IPA_AT_SUDORULE_USERCATEGORY, + IPA_AT_SUDORULE_RUNASUSERCATEGORY, + IPA_AT_SUDORULE_RUNASGROUPCATEGORY, + IPA_AT_SUDORULE_RUNASEXTUSER, + IPA_AT_SUDORULE_RUNASEXTGROUP, + IPA_AT_SUDORULE_RUNASEXTUSERGROUP, + IPA_AT_SUDORULE_EXTUSER, + IPA_AT_SUDORULE_ENTRYUSN, + + IPA_OPTS_SUDORULE +}; + +enum ipa_sudocmdgroup_attrs { + IPA_OC_SUDOCMDGROUP = 0, + IPA_AT_SUDOCMDGROUP_UUID, + IPA_AT_SUDOCMDGROUP_NAME, + IPA_AT_SUDOCMDGROUP_MEMBER, + IPA_AT_SUDOCMDGROUP_ENTRYUSN, + + IPA_OPTS_SUDOCMDGROUP +}; + +enum ipa_sudocmd_attrs { + IPA_OC_SUDOCMD = 0, + IPA_AT_SUDOCMD_UUID, + IPA_AT_SUDOCMD_CMD, + IPA_AT_SUDOCMD_MEMBEROF, + + IPA_OPTS_SUDOCMD +}; + +enum ipa_cli_ad_subdom_attrs { + IPA_CLI_AD_SERVER, + IPA_CLI_AD_SITE, + + IPA_OPTS_CLI_AD_SUBDOM +}; + +struct ipa_auth_ctx { + struct krb5_ctx *krb5_auth_ctx; + struct sdap_id_ctx *sdap_id_ctx; + struct sdap_auth_ctx *sdap_auth_ctx; + struct dp_option *ipa_options; +}; + +/* In server mode, each subdomain corresponds to an AD context */ + +struct ipa_id_ctx { + struct sdap_id_ctx *sdap_id_ctx; + struct ipa_options *ipa_options; + + char *view_name; + /* Only used with server mode */ + struct ipa_server_mode_ctx *server_mode; +}; + +struct ipa_options { + struct dp_option *basic; + + struct sdap_attr_map *hostgroup_map; + struct sdap_attr_map *selinuxuser_map; + struct sdap_attr_map *view_map; + struct sdap_attr_map *override_map; + + struct sdap_search_base **hbac_search_bases; + struct sdap_search_base **selinux_search_bases; + struct sdap_search_base **subdomains_search_bases; + struct sdap_search_base **master_domain_search_bases; + struct sdap_search_base **ranges_search_bases; + struct sdap_search_base **views_search_bases; + struct sdap_search_base **deskprofile_search_bases; + struct ipa_service *service; + + /* id provider */ + struct sdap_options *id; + struct ipa_id_ctx *id_ctx; + struct be_resolv_ctx *be_res; + struct be_nsupdate_ctx *dyndns_ctx; + + /* auth and chpass provider */ + struct dp_option *auth; + struct ipa_auth_ctx *auth_ctx; +}; + +#define IPA_RANGE_LOCAL "ipa-local" +#define IPA_RANGE_AD_TRUST "ipa-ad-trust" +#define IPA_RANGE_AD_TRUST_POSIX "ipa-ad-trust-posix" + +/* options parsers */ +int ipa_get_options(TALLOC_CTX *memctx, + struct confdb_ctx *cdb, + const char *conf_path, + struct sss_domain_info *dom, + struct ipa_options **_opts); + +int ipa_get_id_options(struct ipa_options *ipa_opts, + struct confdb_ctx *cdb, + const char *conf_path, + struct data_provider *dp, + struct sdap_options **_opts); + +int ipa_get_auth_options(struct ipa_options *ipa_opts, + struct confdb_ctx *cdb, + const char *conf_path, + struct dp_option **_opts); + +int ipa_get_autofs_options(struct ipa_options *ipa_opts, + struct ldb_context *ldb, + struct confdb_ctx *cdb, + const char *conf_path, + struct sdap_options **_opts); + +errno_t ipa_get_dyndns_options(struct be_ctx *be_ctx, + struct ipa_options *ctx); + +errno_t ipa_hostid_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_method *dp_methods); + +errno_t ipa_autofs_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_method *dp_methods); + +int ipa_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *primary_servers, + const char *backup_servers, + struct ipa_options *options, + struct ipa_service **_service); + +int ipa_sudo_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_method *dp_methods); + +errno_t get_idmap_data_from_range(struct range_info *r, char *domain_name, + char **_name, char **_sid, uint32_t *_rid, + struct sss_idmap_range *_range, + bool *_external_mapping); + +errno_t ipa_ranges_parse_results(TALLOC_CTX *mem_ctx, + char *domain_name, + size_t count, + struct sysdb_attrs **reply, + struct range_info ***_range_list); + +errno_t ipa_idmap_get_ranges_from_sysdb(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid_str, + bool allow_collisions); + +errno_t ipa_idmap_init(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_idmap_ctx **_idmap_ctx); + + +struct krb5_ctx *ipa_init_get_krb5_auth_ctx(void *data); + +errno_t ipa_get_host_attrs(struct dp_option *ipa_options, + size_t host_count, + struct sysdb_attrs **hosts, + struct sysdb_attrs **_ipa_host); + +errno_t ipa_refresh_init(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx); + +#endif /* _IPA_COMMON_H_ */ diff --git a/src/providers/ipa/ipa_config.c b/src/providers/ipa/ipa_config.c new file mode 100644 index 0000000..23f0c58 --- /dev/null +++ b/src/providers/ipa/ipa_config.c @@ -0,0 +1,165 @@ +/* + SSSD + + IPA Backend Module -- configuration retrieval + + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2012 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "providers/ipa/ipa_config.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ldap/sdap_async.h" + +struct ipa_get_config_state { + char *base; + const char **attrs; + + struct sysdb_attrs *config; +}; + +static void ipa_get_config_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_get_config_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *domain, + const char **attrs, + const char *filter, + const char *base) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct ipa_get_config_state *state; + errno_t ret; + char *ldap_basedn; + + req = tevent_req_create(mem_ctx, &state, struct ipa_get_config_state); + if (req == NULL) { + return NULL; + } + + if (attrs == NULL) { + state->attrs = talloc_zero_array(state, const char *, 4); + if (state->attrs == NULL) { + ret = ENOMEM; + goto done; + } + state->attrs[0] = IPA_CONFIG_MIGRATION_ENABLED; + state->attrs[1] = IPA_CONFIG_SELINUX_DEFAULT_USER_CTX; + state->attrs[2] = IPA_CONFIG_SELINUX_MAP_ORDER; + state->attrs[3] = NULL; + } else { + state->attrs = attrs; + } + + if (filter == NULL) { + filter = IPA_CONFIG_FILTER; + } + + ret = domain_to_basedn(state, domain, &ldap_basedn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "domain_to_basedn failed.\n"); + goto done; + } + + if (base == NULL) { + base = IPA_CONFIG_SEARCH_BASE_TEMPLATE; + } + state->base = talloc_asprintf(state, base, ldap_basedn); + if (state->base == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + subreq = sdap_get_generic_send(state, ev, opts, + sh, state->base, + LDAP_SCOPE_SUBTREE, filter, + state->attrs, NULL, 0, + dp_opt_get_int(opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ipa_get_config_done, req); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ipa_get_config_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_config_state *state = tevent_req_data(req, + struct ipa_get_config_state); + size_t reply_count; + struct sysdb_attrs **reply = NULL; + errno_t ret; + + ret = sdap_get_generic_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret) { + goto done; + } + + if (reply_count != 1) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unexpected number of results, expected 1, " + "got %zu.\n", reply_count); + ret = EINVAL; + goto done; + } + + state->config = reply[0]; + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } +} + +errno_t ipa_get_config_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **config) +{ + struct ipa_get_config_state *state = tevent_req_data(req, + struct ipa_get_config_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *config = talloc_steal(mem_ctx, state->config); + + return EOK; +} diff --git a/src/providers/ipa/ipa_config.h b/src/providers/ipa/ipa_config.h new file mode 100644 index 0000000..a3a1523 --- /dev/null +++ b/src/providers/ipa/ipa_config.h @@ -0,0 +1,55 @@ +/* + SSSD + + IPA Backend Module -- configuration retrieval header + + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2012 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef IPA_CONFIG_H_ +#define IPA_CONFIG_H_ + +#include <talloc.h> +#include <tevent.h> + +#include "providers/ldap/ldap_common.h" +#include "db/sysdb.h" + +#define IPA_CONFIG_SELINUX_DEFAULT_USER_CTX "ipaSELinuxUserMapDefault" +#define IPA_CONFIG_SELINUX_MAP_ORDER "ipaSELinuxUserMapOrder" +#define IPA_CONFIG_MIGRATION_ENABLED "ipaMigrationEnabled" +#define IPA_CONFIG_SEARCH_BASE_TEMPLATE "cn=etc,%s" +#define IPA_CONFIG_FILTER "(&(cn=ipaConfig)(objectClass=ipaGuiConfig))" + +#define IPA_OC_CONFIG "ipaConfig" + +struct tevent_req * ipa_get_config_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *domain, + const char **attrs, + const char *filter, + const char *base); + +errno_t ipa_get_config_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **config); + +#endif /* IPA_CONFIG_H_ */ diff --git a/src/providers/ipa/ipa_deskprofile_config.c b/src/providers/ipa/ipa_deskprofile_config.c new file mode 100644 index 0000000..8c66dda --- /dev/null +++ b/src/providers/ipa/ipa_deskprofile_config.c @@ -0,0 +1,156 @@ +/* + SSSD + + Authors: + Fabiano Fidêncio <fidencio@redhat.com> + + Copyright (C) 2017 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_deskprofile_private.h" +#include "providers/ipa/ipa_deskprofile_config.h" +#include "providers/ldap/sdap_async.h" + +struct ipa_deskprofile_config_state { + struct sysdb_attrs *config; +}; + +static void +ipa_deskprofile_get_config_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_deskprofile_get_config_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct dp_option *ipa_opts) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq; + struct ipa_deskprofile_rule_state *state; + char *rule_filter; + const char *attrs[] = { IPA_DESKPROFILE_PRIORITY, NULL }; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_deskprofile_config_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed.\n"); + return NULL; + } + + rule_filter = talloc_asprintf(state, "(objectclass=%s)", + IPA_DESKPROFILE_CONFIG); + if (rule_filter == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = sdap_get_generic_send(state, ev, opts, sh, + dp_opt_get_string(ipa_opts, + IPA_DESKPROFILE_SEARCH_BASE), + LDAP_SCOPE_BASE, rule_filter, + attrs, NULL, 0, + dp_opt_get_int(opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic_send failed.\n"); + goto done; + } + + tevent_req_set_callback(subreq, ipa_deskprofile_get_config_done, req); + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void +ipa_deskprofile_get_config_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ipa_deskprofile_config_state *state; + size_t reply_count; + struct sysdb_attrs **reply = NULL; + errno_t ret; + + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_deskprofile_config_state); + + ret = sdap_get_generic_recv(subreq, state, &reply_count, &reply); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not retrieve Desktop Profile config\n"); + goto done; + } + + if (reply_count == 0) { + /* + * When connecting to an old server that doesn't support Desktop + * Profile, the reply_count will be zero. + * In order to not throw a unnecessary error and fail let's just + * return ENOENT and print a debug message about it. + */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Server doesn't support Desktop Profile.\n"); + ret = ENOENT; + goto done; + } else if (reply_count != 1) { + DEBUG(SSSDBG_OP_FAILURE, + "Unexpected number of results, expected 1, got %zu.\n", + reply_count); + ret = EINVAL; + goto done; + } + + state->config = reply[0]; + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +ipa_deskprofile_get_config_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **config) +{ + struct ipa_deskprofile_config_state *state; + + state = tevent_req_data(req, struct ipa_deskprofile_config_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + + *config = talloc_steal(mem_ctx, state->config); + + return EOK; +} diff --git a/src/providers/ipa/ipa_deskprofile_config.h b/src/providers/ipa/ipa_deskprofile_config.h new file mode 100644 index 0000000..c4a05b2 --- /dev/null +++ b/src/providers/ipa/ipa_deskprofile_config.h @@ -0,0 +1,45 @@ +/* + SSSD + + Authors: + Fabiano Fidêncio <fidencio@redhat.com> + + Copyright (C) 2017 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef IPA_DESKPROFILE_CONFIG_H_ +#define IPA_DESKPROFILE_CONFIG_H_ + +#include <talloc.h> +#include <tevent.h> + +#include "providers/ldap/ldap_common.h" +#include "db/sysdb.h" + +/* From ipa_deskprofile_config.c */ +struct tevent_req * +ipa_deskprofile_get_config_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct dp_option *ipa_opts); + +errno_t +ipa_deskprofile_get_config_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **config); + +#endif /* IPA_DESKPROFILE_CONFIG_H_ */ diff --git a/src/providers/ipa/ipa_deskprofile_private.h b/src/providers/ipa/ipa_deskprofile_private.h new file mode 100644 index 0000000..1db154b --- /dev/null +++ b/src/providers/ipa/ipa_deskprofile_private.h @@ -0,0 +1,50 @@ +/* + SSSD + + Authors: + Fabiano Fidêncio <fidencio@redhat.com> + + Copyright (C) 2017 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef IPA_DESKPROFILE_PRIVATE_H_ +#define IPA_DESKPROFILE_PRIVATE_H_ + +#define IPA_DESKPROFILE_CONFIG "ipaDeskProfileConfig" +#define IPA_DESKPROFILE_RULE "ipaDeskProfileRule" +#define IPA_DESKPROFILE_PRIORITY "ipaDeskProfilePriority" +#define IPA_DESKPROFILE_DATA "ipaDeskData" + +#define DESKPROFILE_HOSTS_SUBDIR "deskprofile_hosts" +#define DESKPROFILE_HOSTGROUPS_SUBDIR "deskprofile_hostgroups" + +#define IPA_SESSION_RULE_TYPE "sessionRuleType" + +#define IPA_DESKPROFILE_BASE_TMPL "cn=desktop-profile,%s" + +#define SYSDB_DESKPROFILE_BASE_TMPL "cn=desktop-profile,"SYSDB_TMPL_CUSTOM_BASE + +#define DESKPROFILE_RULES_SUBDIR "deskprofile_rules" + +#define DESKPROFILE_CONFIG_SUBDIR "deskprofile_config" + +struct deskprofile_rule { + const char *name; + int priority; + const char *data; +}; + +#endif /* IPA_DESKPROFILE_PRIVATE_H_ */ diff --git a/src/providers/ipa/ipa_deskprofile_rules.c b/src/providers/ipa/ipa_deskprofile_rules.c new file mode 100644 index 0000000..cce6184 --- /dev/null +++ b/src/providers/ipa/ipa_deskprofile_rules.c @@ -0,0 +1,367 @@ +/* + SSSD + + Authors: + Fabiano Fidêncio <fidencio@redhat.com> + + Copyright (C) 2017 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ipa/ipa_rules_common.h" +#include "providers/ipa/ipa_deskprofile_private.h" +#include "providers/ipa/ipa_deskprofile_rules.h" +#include "providers/ipa/ipa_deskprofile_rules_util.h" + +struct ipa_deskprofile_rule_state { + struct tevent_context *ev; + struct sdap_handle *sh; + struct sdap_options *opts; + + int search_base_iter; + struct sdap_search_base **search_bases; + + const char **attrs; + char *rules_filter; + char *cur_filter; + + size_t rule_count; + struct sysdb_attrs **rules; +}; + +static errno_t +ipa_deskprofile_rule_info_next(struct tevent_req *req, + struct ipa_deskprofile_rule_state *state); +static void +ipa_deskprofile_rule_info_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_deskprofile_rule_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sysdb_attrs *ipa_host, + struct sss_domain_info *domain, + const char *username) +{ + struct tevent_req *req = NULL; + struct ipa_deskprofile_rule_state *state; + char *user; + char *group; + char *host_dn_clean; + char *group_clean; + char *host_group_clean; + char *rule_filter; + const char *host_dn; + const char **memberof_list; + char **groups_list; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ipa_deskprofile_rule_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + if (ipa_host == NULL) { + ret = EINVAL; + DEBUG(SSSDBG_CRIT_FAILURE, "Missing host\n"); + goto immediate; + } + + ret = sysdb_attrs_get_string(ipa_host, SYSDB_ORIG_DN, &host_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not identify IPA hostname\n"); + goto immediate; + } + + ret = sss_filter_sanitize_dn(state, host_dn, &host_dn_clean); + if (ret != EOK) { + goto immediate; + } + + state->ev = ev; + state->sh = sh; + state->opts = opts; + state->search_bases = search_bases; + state->search_base_iter = 0; + state->attrs = deskprofile_get_attrs_to_get_cached_rules(state); + if (state->attrs == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, + "deskprofile_get_attrs_get_cached_rules() failed\n"); + goto immediate; + } + + rule_filter = talloc_asprintf(state, + "(&(objectclass=%s)" + "(%s=%s)" + "(|(%s=%s)(%s=%s)(%s=%s)", + IPA_DESKPROFILE_RULE, + IPA_ENABLED_FLAG, IPA_TRUE_VALUE, + IPA_HOST_CATEGORY, "all", + IPA_USER_CATEGORY, "all", + IPA_MEMBER_HOST, host_dn_clean); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + + /* Add all parent groups of ipa_hostname to the filter */ + ret = sysdb_attrs_get_string_array(ipa_host, SYSDB_ORIG_MEMBEROF, + state, &memberof_list); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not identify.\n"); + } else if (ret == ENOENT) { + /* This host is not a member of any hostgroups */ + memberof_list = talloc_array(state, const char *, 1); + if (memberof_list == NULL) { + ret = ENOMEM; + goto immediate; + } + memberof_list[0] = NULL; + } + + for (size_t i = 0; memberof_list[i] != NULL; i++) { + ret = sss_filter_sanitize(state, + memberof_list[i], + &host_group_clean); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_filter_sanitize() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + rule_filter = talloc_asprintf_append(rule_filter, "(%s=%s)", + IPA_MEMBER_HOST, + host_group_clean); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + } + + /* Add the username to the filter */ + ret = sss_parse_internal_fqname(state, username, &user, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_parse_internal_fqname() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + rule_filter = talloc_asprintf_append(rule_filter, "(%s=%s)", + IPA_MEMBER_USER, user); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + + /* Add all parent groups of `username` to the filter */ + ret = get_sysdb_grouplist(state, domain->sysdb, domain, username, + &groups_list); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "get_sysdb_grouplist() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + for (size_t i = 0; groups_list[i] != NULL; i++) { + ret = sss_filter_sanitize(state, groups_list[i], &group_clean); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_filter_sanitize() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + ret = sss_parse_internal_fqname(state, group_clean, &group, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_parse_internal_fqname() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + rule_filter = talloc_asprintf_append(rule_filter, "(%s=%s)", + IPA_MEMBER_USER, group); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + } + + rule_filter = talloc_asprintf_append(rule_filter, "))"); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + state->rules_filter = talloc_steal(state, rule_filter); + + ret = ipa_deskprofile_rule_info_next(req, state); + if (ret != EAGAIN) { + if (ret == EOK) { + /* ipa_deskprofile_rule_info_next should always have a search base + * when called for the first time. + * + * For the subsequent iterations, not finding any more search bases + * is fine though (thus the function returns EOK). + * + * As, here, it's the first case happening, let's return EINVAL. + */ + DEBUG(SSSDBG_CRIT_FAILURE, "No search base found\n"); + ret = EINVAL; + } + goto immediate; + } + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t +ipa_deskprofile_rule_info_next(struct tevent_req *req, + struct ipa_deskprofile_rule_state *state) +{ + struct tevent_req *subreq; + struct sdap_search_base *base; + + base = state->search_bases[state->search_base_iter]; + if (base == NULL) { + return EOK; + } + + talloc_zfree(state->cur_filter); + state->cur_filter = sdap_combine_filters(state, state->rules_filter, + base->filter); + if (state->cur_filter == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Sending request for next search base: [%s][%d][%s]\n", + base->basedn, base->scope, state->cur_filter); + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + base->basedn, base->scope, + state->cur_filter, state->attrs, + NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_deskprofile_rule_info_done, req); + + return EAGAIN; +} + +static void +ipa_deskprofile_rule_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req; + struct ipa_deskprofile_rule_state *state; + size_t rule_count; + size_t total_count; + struct sysdb_attrs **rules; + struct sysdb_attrs **target; + int i; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_deskprofile_rule_state); + + ret = sdap_get_generic_recv(subreq, state, + &rule_count, + &rules); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not retrieve Desktop Profile rules\n"); + goto fail; + } + + if (rule_count > 0) { + total_count = rule_count + state->rule_count; + state->rules = talloc_realloc(state, state->rules, + struct sysdb_attrs *, + total_count); + if (state->rules == NULL) { + ret = ENOMEM; + goto fail; + } + + i = 0; + while (state->rule_count < total_count) { + target = &state->rules[state->rule_count]; + *target = talloc_steal(state->rules, rules[i]); + + state->rule_count++; + i++; + } + } + + state->search_base_iter++; + ret = ipa_deskprofile_rule_info_next(req, state); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + goto fail; + } else if (ret == EOK && state->rule_count == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "No rules apply to this host\n"); + tevent_req_error(req, ENOENT); + return; + } + + /* We went through all search bases and we have some results */ + tevent_req_done(req); + + return; + +fail: + tevent_req_error(req, ret); +} + +errno_t +ipa_deskprofile_rule_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_rule_count, + struct sysdb_attrs ***_rules) +{ + struct ipa_deskprofile_rule_state *state; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + state = tevent_req_data(req, struct ipa_deskprofile_rule_state); + + *_rule_count = state->rule_count; + *_rules = talloc_steal(mem_ctx, state->rules); + + return EOK; +} diff --git a/src/providers/ipa/ipa_deskprofile_rules.h b/src/providers/ipa/ipa_deskprofile_rules.h new file mode 100644 index 0000000..313e526 --- /dev/null +++ b/src/providers/ipa/ipa_deskprofile_rules.h @@ -0,0 +1,43 @@ +/* + SSSD + + Authors: + Fabiano Fidêncio <fidencio@redhat.com> + + Copyright (C) 2017 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef IPA_DESKPROFILE_RULES_H_ +#define IPA_DESKPROFILE_RULES_H_ + +/* From ipa_deskprofile_rules.c */ +struct tevent_req * +ipa_deskprofile_rule_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sysdb_attrs *ipa_host, + struct sss_domain_info *domain, + const char *username); + +errno_t +ipa_deskprofile_rule_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *rule_count, + struct sysdb_attrs ***rules); + +#endif /* IPA_DESKPROFILE_RULES_H_ */ diff --git a/src/providers/ipa/ipa_deskprofile_rules_util.c b/src/providers/ipa/ipa_deskprofile_rules_util.c new file mode 100644 index 0000000..d6fa3cc --- /dev/null +++ b/src/providers/ipa/ipa_deskprofile_rules_util.c @@ -0,0 +1,1147 @@ +/* + SSSD + + Authors: + Fabiano Fidêncio <fidencio@redhat.com> + + Copyright (C) 2017 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "providers/ipa/ipa_deskprofile_rules_util.h" +#include "providers/ipa/ipa_deskprofile_private.h" +#include "providers/ipa/ipa_rules_common.h" +#include <ctype.h> +#include <fcntl.h> + +#define DESKPROFILE_GLOBAL_POLICY_MIN_VALUE 1 +#define DESKPROFILE_GLOBAL_POLICY_MAX_VALUE 24 + +enum deskprofile_name { + RULES_DIR = 0, + DOMAIN, + USERNAME, + PRIORITY, + USER, + GROUP, + HOST, + HOSTGROUP, + RULE_NAME, + EXTENSION, + DESKPROFILE_NAME_SENTINEL +}; + +/* + * The rule's filename has to follow a global policy, used by FleetCommander + * client that shows how the profile should be applied. + * + * This global policy is represented by an integer from 1 to 24 (inclusive) and + * has the following meaning: + * 1 = user, group, host, hostgroup + * 2 = user, group, hostgroup, host + * 3 = user, host, group, hostgroup + * 4 = user, host, hostgroup, group + * 5 = user, hostgroup, group, host + * 6 = user, hostgroup, host, group + * 7 = group, user, host, hostgroup + * 8 = group, user, hostgroup, host + * 9 = group, host, user, hostgroup + * 10 = group, host, hostgroup, user + * 11 = group, hostgroup, user, host + * 12 = group, hostgroup, host, user + * 13 = host, user, group, hostgroup + * 14 = host, user, hostgroup, group + * 15 = host, group, user, hostgroup + * 16 = host, group, hostgroup, user + * 17 = host, hostgroup, user, group + * 18 = host, hostgroup, group, user + * 19 = hostgroup, user, group, host + * 20 = hostgroup, user, host, group + * 21 = hostgroup, group, user, host + * 22 = hostgroup, group, host, user + * 23 = hostgroup, host, user, group + * 24 = hostgroup, host, group, user + * + * Having the table above in mind and considering the following example: + * - rule name: testrule + * - policy: 22 + * - priority: 420 + * - client's machine matches: host and group + * + * So, the filename will be: "000420_000000_000420_000420_000000_testrule.json" + * + * The function below not only helps us to create this filename in the correct + * format, but also create the whole path for this rule's file. + * + * An example of the full path would be: + * "/var/lib/sss/deskprofile/ipa.example/user_foobar/000420_000000_000420_000420_000000_testrule.json" + * | RULES DIR | DOMAIN | USERNAME | | |GROUP | HOST | USER | | + * PRIORITY RULE NAME + * HOSTGROUP EXTENSION + * + * In case a element has to be added/remove, please, remember to update: + * - deskprofile_name enum; + * - permuts's matrix; + * - vals array; + */ +errno_t +ipa_deskprofile_get_filename_path(TALLOC_CTX *mem_ctx, + uint16_t config_priority, + const char *rules_dir, + const char *domain, + const char *username, + const char *priority, + const char *user_priority, + const char *group_priority, + const char *host_priority, + const char *hostgroup_priority, + const char *rule_name, + const char *extension, + char **_filename_path) +{ + TALLOC_CTX *tmp_ctx; + static const uint8_t permuts[][DESKPROFILE_NAME_SENTINEL] = { + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, USER, GROUP, HOST, HOSTGROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, USER, GROUP, HOSTGROUP, HOST, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, USER, HOST, GROUP, HOSTGROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, USER, HOST, HOSTGROUP, GROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, USER, HOSTGROUP, GROUP, HOST, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, USER, HOSTGROUP, HOST, GROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, GROUP, USER, HOST, HOSTGROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, GROUP, USER, HOSTGROUP, HOST, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, GROUP, HOST, USER, HOSTGROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, GROUP, HOST, HOSTGROUP, USER, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, GROUP, HOSTGROUP, USER, HOST, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, GROUP, HOSTGROUP, HOST, USER, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOST, USER, GROUP, HOSTGROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOST, USER, HOSTGROUP, GROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOST, GROUP, USER, HOSTGROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOST, GROUP, HOSTGROUP, USER, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOST, HOSTGROUP, USER, GROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOST, HOSTGROUP, GROUP, USER, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOSTGROUP, USER, GROUP, HOST, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOSTGROUP, USER, HOST, GROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOSTGROUP, GROUP, USER, HOST, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOSTGROUP, GROUP, HOST, USER, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOSTGROUP, HOST, USER, GROUP, RULE_NAME, EXTENSION}, + {RULES_DIR, DOMAIN, USERNAME, PRIORITY, HOSTGROUP, HOST, GROUP, USER, RULE_NAME, EXTENSION}, + }; + const char *vals[] = { + rules_dir, + domain, + username, + priority, + user_priority, + group_priority, + host_priority, + hostgroup_priority, + rule_name, + extension, + NULL, + }; + const uint8_t *perms; + char *result; + errno_t ret; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + if (config_priority < DESKPROFILE_GLOBAL_POLICY_MIN_VALUE || + config_priority > DESKPROFILE_GLOBAL_POLICY_MAX_VALUE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "The configuration priority has an invalid value: %d!\n", + config_priority); + ret = EINVAL; + goto done; + } + + perms = permuts[config_priority - 1]; + + result = talloc_strdup(tmp_ctx, ""); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + for (int i = 0; i < DESKPROFILE_NAME_SENTINEL; i++) { + switch(perms[i]) { + case RULES_DIR: + case DOMAIN: + case USERNAME: + result = talloc_asprintf_append(result, "%s/", vals[perms[i]]); + break; + case PRIORITY: + case USER: + case GROUP: + case HOST: + case HOSTGROUP: + result = talloc_asprintf_append(result, "%s_", vals[perms[i]]); + break; + case RULE_NAME: + result = talloc_asprintf_append(result, "%s", vals[perms[i]]); + break; + case EXTENSION: + result = talloc_asprintf_append(result, ".%s", vals[perms[i]]); + break; + default: + DEBUG(SSSDBG_MINOR_FAILURE, + "This situation should never happen\n"); + ret = EINVAL; + goto done; + } + + if (result == NULL) { + ret = ENOMEM; + goto done; + } + } + + *_filename_path = talloc_steal(mem_ctx, result); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ipa_deskprofile_rules_create_user_dir( + const char *username, /* fully-qualified */ + uid_t uid, + gid_t gid) +{ + TALLOC_CTX *tmp_ctx; + char *shortname; + char *domain; + char *domain_dir; + errno_t ret; + mode_t old_umask; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmp_ctx, username, &shortname, &domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_parse_internal_fqname() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + old_umask = umask(0026); + ret = sss_create_dir(IPA_DESKPROFILE_RULES_USER_DIR, domain, 0751, + getuid(), getgid()); + umask(old_umask); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to create the directory \"%s/%s\" that would be used to " + "store the Desktop Profile rules users' directory [%d]: %s\n", + IPA_DESKPROFILE_RULES_USER_DIR, domain, + ret, sss_strerror(ret)); + goto done; + } + + domain_dir = talloc_asprintf(tmp_ctx, IPA_DESKPROFILE_RULES_USER_DIR"/%s", + domain); + if (domain_dir == NULL) { + ret = ENOMEM; + goto done; + } + + /* In order to read, create and traverse the directory, we need to have its + * permissions set as 'rwx------' (700). */ + old_umask = umask(0077); + ret = sss_create_dir(domain_dir, shortname, 0700, uid, gid); + umask(old_umask); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to create the directory \"%s/%s/%s\" that would be used " + "to store the Desktop Profile rules for the user \"%s\" [%d]: " + "%s\n", + IPA_DESKPROFILE_RULES_USER_DIR, domain, shortname, username, + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ipa_deskprofile_get_normalized_rule_name(TALLOC_CTX *mem_ctx, + const char *name, + char **_rule_name) +{ + char buffer[PATH_MAX]; + size_t buffer_len; + size_t name_len; + + name_len = strlen(name); + buffer_len = 0; + for (size_t i = 0; i < name_len; i++) { + char character; + bool replace; + + character = name[i]; + replace = false; + + if (isalnum(character) == 0) { + char next_character; + + next_character = name[i+1]; + if (i + 1 >= name_len || isalnum(next_character) == 0) { + continue; + } + + replace = true; + } + + buffer[buffer_len] = replace ? '_' : character; + buffer_len++; + } + buffer[buffer_len] = '\0'; + + *_rule_name = talloc_strdup(mem_ctx, buffer); + if (*_rule_name == NULL) { + return ENOMEM; + } + + return EOK; +} + +static errno_t +ipa_deskprofile_rule_check_memberuser( + TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct sysdb_attrs *rule, + const char *rule_name, + const char *rule_prio, + const char *base_dn, + const char *username, /* fully-qualified */ + char **_user_prio, + char **_group_prio) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message_element *el; + struct ldb_result *res; + size_t num_groups; + char **groups = NULL; + const char *fqgroupname = NULL; + char *groupname = NULL; + char *shortname; + char *domainname; + char *data; + char *memberuser; + char *membergroup; + char *user_prio; + char *group_prio; + bool user = false; + bool group = false; + errno_t ret; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmp_ctx, username, + &shortname, &domainname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_parse_internal_fqname() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_initgroups(tmp_ctx, domain, username, &res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_initgroups() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (res->count == 0) { + /* This really should NOT happen at this point */ + DEBUG(SSSDBG_MINOR_FAILURE, + "User [%s] not found in cache\n", username); + ret = ENOENT; + goto done; + } + + groups = talloc_array(tmp_ctx, char *, res->count); + if (groups == NULL) { + ret = ENOMEM; + goto done; + } + + num_groups = 0; + /* Start counting from 1 to exclude the user entry */ + for (size_t i = 1; i < res->count; i++) { + fqgroupname = ldb_msg_find_attr_as_string(res->msgs[i], + SYSDB_NAME, + NULL); + if (fqgroupname == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Skipping malformed entry [%s]\n", + ldb_dn_get_linearized(res->msgs[i]->dn)); + continue; + } + + ret = sss_parse_internal_fqname(tmp_ctx, fqgroupname, + &groupname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Malformed name %s, skipping!\n", fqgroupname); + continue; + } + + groups[num_groups] = groupname; + num_groups++; + } + groups[num_groups] = NULL; + + ret = sysdb_attrs_get_el(rule, IPA_MEMBER_USER, &el); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to get the Desktop Profile Rule memberUser for rule " + "\"%s\" [%d]: %s\n", + rule_name, ret, sss_strerror(ret)); + + goto done; + } + + memberuser = talloc_asprintf(tmp_ctx, "uid=%s,cn=users,cn=accounts,%s", + shortname, base_dn); + if (memberuser == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate memberuser\n"); + ret = ENOMEM; + goto done; + } + + for (size_t i = 0; i < el->num_values; i++) { + if (user && group) { + break; + } + + data = (char *)el->values[i].data; + + if (!user && data != NULL && strcmp(memberuser, data) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "Desktop Profile rule \"%s\" matches with the user \"%s\" " + "for the \"%s\" domain!\n", + rule_name, shortname, domainname); + user = true; + continue; + } + + if (!group && data != NULL) { + for (size_t j = 0; !group && groups[j] != NULL; j++) { + membergroup = talloc_asprintf(tmp_ctx, + "cn=%s,cn=groups,cn=accounts,%s", + groups[j], base_dn); + if (membergroup == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate membergroup\n"); + ret = ENOMEM; + goto done; + } + + if (strcmp(membergroup, data) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "Desktop Profile rule \"%s\" matches with (at least) " + "the group \"%s\" for the \"%s\" domain!\n", + rule_name, groups[j], domainname); + group = true; + } + } + } + } + + user_prio = user ? talloc_strdup(tmp_ctx, rule_prio) : + talloc_asprintf(tmp_ctx, "%06d", 0); + if (user_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate the user priority\n"); + ret = ENOMEM; + goto done; + } + + group_prio = group ? talloc_strdup(tmp_ctx, rule_prio) : + talloc_asprintf(tmp_ctx, "%06d", 0); + if (group_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate the group priority\n"); + ret = ENOMEM; + goto done; + } + + *_user_prio = talloc_steal(mem_ctx, user_prio); + *_group_prio = talloc_steal(mem_ctx, group_prio); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ipa_deskprofile_rule_check_memberhost(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct sysdb_attrs *rule, + const char *rule_name, + const char *rule_prio, + const char *base_dn, + const char *hostname, + char **_host_prio, + char **_hostgroup_prio) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *host_dn; + struct ldb_message_element *el_orig_memberof = NULL; + struct ldb_message_element *el = NULL; + struct ldb_message **msgs; + size_t count; + size_t num_memberhostgroup; + char **memberhostgroups = NULL; + char *data; + char *memberhost; + char *memberhostgroup; + char *name; + char *host_prio; + char *hostgroup_prio; + const char *memberof_attrs[] = { SYSDB_ORIG_MEMBEROF, NULL }; + bool host = false; + bool hostgroup = false; + errno_t ret; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + host_dn = sysdb_custom_dn(tmp_ctx, domain, hostname, + DESKPROFILE_HOSTS_SUBDIR); + if (host_dn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_entry(tmp_ctx, domain->sysdb, host_dn, + LDB_SCOPE_BASE, NULL, + memberof_attrs, + &count, &msgs); + if (ret == ENOENT || count == 0) { + memberhostgroups = talloc_array(tmp_ctx, char *, 1); + memberhostgroups[0] = NULL; + } else if (ret != EOK) { + goto done; + } else if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More than one result for a BASE search!\n"); + ret = EIO; + goto done; + } else { /* ret == EOK && count == 1 */ + el_orig_memberof = ldb_msg_find_element(msgs[0], SYSDB_ORIG_MEMBEROF); + memberhostgroups = talloc_array(tmp_ctx, + char *, + el_orig_memberof->num_values); + } + + if (el_orig_memberof != NULL) { + num_memberhostgroup = 0; + for (size_t i = 0; i < el_orig_memberof->num_values; i++) { + data = (char *)el_orig_memberof->values[i].data; + + ret = ipa_common_get_hostgroupname(tmp_ctx, domain->sysdb, data, + &name); + + /* ERR_UNEXPECTED_ENTRY_TYPE means we had a memberOf entry that + * wasn't a host group, thus we'll just ignore those. + */ + if (ret != EOK && ret != ERR_UNEXPECTED_ENTRY_TYPE) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Skipping malformed entry [%s]\n", + data); + continue; + } else if (ret == EOK) { + memberhostgroups[num_memberhostgroup] = name; + num_memberhostgroup++; + } + } + memberhostgroups[num_memberhostgroup] = NULL; + } + + ret = sysdb_attrs_get_el(rule, IPA_MEMBER_HOST, &el); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to get the Desktop Profile Rule memberHost for rule " + "\"%s\" [%d]: %s\n", + rule_name, ret, sss_strerror(ret)); + + goto done; + } + + memberhost = talloc_asprintf(tmp_ctx, "fqdn=%s,cn=computers,cn=accounts,%s", + hostname, base_dn); + if (memberhost == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate memberhost\n"); + goto done; + } + + for (size_t i = 0; i < el->num_values; i++) { + if (host && hostgroup) { + break; + } + + data = (char *)el->values[i].data; + + if (!host && data != NULL && strcmp(memberhost, data) == 0) { + host = true; + DEBUG(SSSDBG_TRACE_FUNC, + "Desktop Profile rule \"%s\" matches with the host \"%s\" " + "for the \"%s\" domain!\n", + rule_name, hostname, domain->name); + continue; + } + + if (!hostgroup && data != NULL) { + for (size_t j = 0; !hostgroup && memberhostgroups[j] != NULL; j++) { + memberhostgroup = talloc_asprintf( + tmp_ctx, + "cn=%s,cn=hostgroups,cn=accounts,%s", + memberhostgroups[j], base_dn); + + if (memberhostgroup == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate memberhostgroup\n"); + ret = ENOMEM; + goto done; + } + + if (strcmp(memberhostgroup, data) == 0) { + hostgroup = true; + DEBUG(SSSDBG_TRACE_FUNC, + "Desktop Profile rule \"%s\" matches with (at least) " + "the hostgroup \"%s\" for the \"%s\" domain!\n", + rule_name, memberhostgroups[j], domain->name); + continue; + } + } + } + } + + host_prio = host ? talloc_strdup(tmp_ctx, rule_prio) : + talloc_asprintf(tmp_ctx, "%06d", 0); + if (host_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate the host priority\n"); + ret = ENOMEM; + goto done; + } + + hostgroup_prio = hostgroup ? talloc_strdup(tmp_ctx, rule_prio) : + talloc_asprintf(tmp_ctx, "%06d", 0); + if (hostgroup_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate the hostgroup priority\n"); + ret = ENOMEM; + goto done; + } + + *_host_prio = talloc_steal(mem_ctx, host_prio); + *_hostgroup_prio = talloc_steal(mem_ctx, hostgroup_prio); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + + +errno_t +ipa_deskprofile_rules_save_rule_to_disk( + TALLOC_CTX *mem_ctx, + uint16_t priority, + struct sysdb_attrs *rule, + struct sss_domain_info *domain, + const char *hostname, + const char *username, /* fully-qualified */ + uid_t uid, + gid_t gid) +{ + TALLOC_CTX *tmp_ctx; + const char *rule_name; + const char *data; + const char *hostcat; + const char *usercat; + char *shortname; + char *domainname; + char *base_dn; + char *rule_prio; + char *user_prio; + char *group_prio; + char *host_prio; + char *hostgroup_prio; + char *normalized_rule_name = NULL; + char *filename_path = NULL; + const char *extension = "json"; + uint32_t prio; + int fd = -1; + gid_t orig_gid; + uid_t orig_uid; + errno_t ret; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + orig_gid = getegid(); + orig_uid = geteuid(); + + ret = sysdb_attrs_get_string(rule, IPA_CN, &rule_name); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to get the Desktop Profile Rule name [%d]: %s\n", + ret, sss_strerror(ret)); + + goto done; + } + + ret = sysdb_attrs_get_uint32_t(rule, IPA_DESKPROFILE_PRIORITY, &prio); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to get the Desktop Profile Rule priority for rule " + "\"%s\" [%d]: %s\n", + rule_name, ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_attrs_get_string(rule, IPA_HOST_CATEGORY, &hostcat); + if (ret == ENOENT) { + hostcat = NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to get the Desktop Profile Rule host category for rule " + "\"%s\" [%d]: %s\n", + rule_name, ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_attrs_get_string(rule, IPA_USER_CATEGORY, &usercat); + if (ret == ENOENT) { + usercat = NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to get the Desktop Profile Rule user category for rule " + "\"%s\" [%d]: %s\n", + rule_name, ret, sss_strerror(ret)); + goto done; + } + + rule_prio = talloc_asprintf(tmp_ctx, "%06d", prio); + if (rule_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate rule priority\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(rule, IPA_DESKPROFILE_DATA, &data); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Failed to get the Desktop Profile Rule data for rule \"%s\" " + "[%d]: %s\n", + rule_name, ret, sss_strerror(ret)); + goto done; + } + + ret = sss_parse_internal_fqname(tmp_ctx, username, &shortname, &domainname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_parse_internal_fqname() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = domain_to_basedn(tmp_ctx, domainname, &base_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "domain_to_basedn() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (usercat != NULL && strcasecmp(usercat, "all") == 0) { + user_prio = talloc_strdup(tmp_ctx, rule_prio); + if (user_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate the user priority " + "when user category is \"all\"\n"); + ret = ENOMEM; + goto done; + } + + group_prio = talloc_strdup(tmp_ctx, rule_prio); + if (group_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate the group priority " + "when user category is \"all\"\n"); + ret = ENOMEM; + goto done; + } + } else { + ret = ipa_deskprofile_rule_check_memberuser(tmp_ctx, domain, rule, + rule_name, rule_prio, + base_dn, username, + &user_prio, &group_prio); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_deskprofile_rule_check_memberuser() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + if (hostcat != NULL && strcasecmp(hostcat, "all") == 0) { + host_prio = talloc_strdup(tmp_ctx, rule_prio); + if (host_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate the host priority " + "when host category is \"all\"\n"); + ret = ENOMEM; + goto done; + } + + hostgroup_prio = talloc_strdup(tmp_ctx, rule_prio); + if (hostgroup_prio == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate the hostgroup priority " + "when host category is \"all\"\n"); + ret = ENOMEM; + goto done; + } + } else { + ret = ipa_deskprofile_rule_check_memberhost(tmp_ctx, domain, rule, + rule_name, rule_prio, + base_dn, hostname, + &host_prio, &hostgroup_prio); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_deskprofile_rule_check_memberhost() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + ret = ipa_deskprofile_get_normalized_rule_name(mem_ctx, rule_name, + &normalized_rule_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_deskprofile_get_normalized_rule_name() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ipa_deskprofile_get_filename_path(tmp_ctx, + priority, + IPA_DESKPROFILE_RULES_USER_DIR, + domainname, + shortname, + rule_prio, + user_prio, + group_prio, + host_prio, + hostgroup_prio, + normalized_rule_name, + extension, + &filename_path); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_deskprofile_get_filename_path() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = setegid(gid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to set effective group id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + gid, ret, sss_strerror(ret)); + goto done; + } + + ret = seteuid(uid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to set effective user id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + uid, ret, sss_strerror(ret)); + goto done; + } + + fd = open(filename_path, O_WRONLY | O_CREAT | O_TRUNC, 0400); + if (fd == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to create the Desktop Profile rule file \"%s\" " + "[%d]: %s\n", + filename_path, ret, sss_strerror(ret)); + goto done; + } + + ret = dprintf(fd, "%s", data); + if (ret < 0) { + ret = EIO; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to write the content of the Desktop Profile rule for " + "the \"%s\" file.\n", + filename_path); + goto done; + } + + ret = seteuid(orig_uid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set the effect user id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + orig_uid, ret, sss_strerror(ret)); + goto done; + } + + ret = setegid(orig_gid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set the effect group id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + orig_gid, ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (fd != -1) { + close(fd); + } + if (geteuid() != orig_uid) { + ret = seteuid(orig_uid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to set effective user id (%"PRIu32") of the " + "domain's process [%d]: %s\n", + orig_uid, ret, sss_strerror(ret)); + DEBUG(SSSDBG_CRIT_FAILURE, + "Sending SIGUSR2 to the process: %d\n", getpid()); + kill(getpid(), SIGUSR2); + } + } + if (getegid() != orig_gid) { + ret = setegid(orig_gid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to set effective group id (%"PRIu32") of the " + "domain's process. Let's have the process restarted!\n", + orig_gid); + DEBUG(SSSDBG_CRIT_FAILURE, + "Sending SIGUSR2 to the process: %d\n", getpid()); + kill(getpid(), SIGUSR2); + } + } + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ipa_deskprofile_rules_remove_user_dir(const char *user_dir, + uid_t uid, + gid_t gid) +{ + gid_t orig_gid; + uid_t orig_uid; + errno_t ret; + + orig_gid = getegid(); + orig_uid = geteuid(); + + ret = setegid(gid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to set effective group id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + gid, ret, sss_strerror(ret)); + goto done; + } + + ret = seteuid(uid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to set effective user id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + uid, ret, sss_strerror(ret)); + goto done; + } + + ret = sss_remove_subtree(user_dir); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot remove \"%s\" directory [%d]: %s\n", + user_dir, ret, sss_strerror(ret)); + goto done; + } + + ret = seteuid(orig_uid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set the effect user id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + orig_uid, ret, sss_strerror(ret)); + goto done; + } + + ret = setegid(orig_gid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to set the effect group id (%"PRIu32") of the domain's " + "process [%d]: %s\n", + orig_gid, ret, sss_strerror(ret)); + goto done; + } + + ret = sss_remove_tree(user_dir); + if ((ret != EOK) && (ret != ENOENT)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot remove \"%s\" directory [%d]: %s\n", + user_dir, ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (geteuid() != orig_uid) { + ret = seteuid(orig_uid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "unable to set effective user id (%"PRIu32") of the " + "domain's process [%d]: %s\n", + orig_uid, ret, sss_strerror(ret)); + DEBUG(SSSDBG_CRIT_FAILURE, + "Sending SIGUSR2 to the process: %d\n", getpid()); + kill(getpid(), SIGUSR2); + } + } + if (getegid() != orig_gid) { + ret = setegid(orig_gid); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to set effective user id (%"PRIu32") of the " + "domain's process [%d]: %s\n", + orig_uid, ret, sss_strerror(ret)); + DEBUG(SSSDBG_CRIT_FAILURE, + "Sending SIGUSR2 to the process: %d\n", getpid()); + kill(getpid(), SIGUSR2); + } + } + return ret; +} + +errno_t +deskprofile_get_cached_priority(struct sss_domain_info *domain, + uint16_t *_priority) +{ + TALLOC_CTX *tmp_ctx; + const char *attrs[] = { IPA_DESKPROFILE_PRIORITY, NULL }; + struct ldb_message **resp; + size_t resp_count; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_search_custom_by_name(tmp_ctx, + domain, + IPA_DESKPROFILE_PRIORITY, + DESKPROFILE_CONFIG_SUBDIR, + attrs, &resp_count, &resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_search_custom_by_name() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (resp_count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_search_custom_by_name() got more attributes than " + "expected. Expected (1), got (%zu)\n", resp_count); + ret = EINVAL; + goto done; + } + + *_priority = ldb_msg_find_attr_as_uint(resp[0], + IPA_DESKPROFILE_PRIORITY, + 0); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +const char ** +deskprofile_get_attrs_to_get_cached_rules(TALLOC_CTX *mem_ctx) +{ + const char **attrs = talloc_zero_array(mem_ctx, const char *, 11); + if (attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array() failed\n"); + goto done; + } + + attrs[0] = OBJECTCLASS; + attrs[1] = IPA_CN; + attrs[2] = IPA_UNIQUE_ID; + attrs[3] = IPA_ENABLED_FLAG; + attrs[4] = IPA_MEMBER_USER; + attrs[5] = IPA_USER_CATEGORY; + attrs[6] = IPA_MEMBER_HOST; + attrs[7] = IPA_HOST_CATEGORY; + attrs[8] = IPA_DESKPROFILE_PRIORITY; + attrs[9] = IPA_DESKPROFILE_DATA; + attrs[10] = NULL; + +done: + return attrs; +} diff --git a/src/providers/ipa/ipa_deskprofile_rules_util.h b/src/providers/ipa/ipa_deskprofile_rules_util.h new file mode 100644 index 0000000..063bbd2 --- /dev/null +++ b/src/providers/ipa/ipa_deskprofile_rules_util.h @@ -0,0 +1,74 @@ +/* + SSSD + + Authors: + Fabiano Fidêncio <fidencio@redhat.com> + + Copyright (C) 2017 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef IPA_DESKPROFILE_RULES_UTIL_H_ +#define IPA_DESKPROFILE_RULES_UTIL_H_ + +#include "db/sysdb.h" + +#ifndef IPA_DESKPROFILE_RULES_USER_DIR +#define IPA_DESKPROFILE_RULES_USER_DIR SSS_STATEDIR"/deskprofile" +#endif /* IPA_DESKPROFILE_RULES_USER_DIR */ + +errno_t +ipa_deskprofile_get_filename_path(TALLOC_CTX *mem_ctx, + uint16_t config_priority, + const char *rules_dir, + const char *domain, + const char *username, + const char *priority, + const char *user_priority, + const char *group_priority, + const char *host_priority, + const char *hostgroup_priority, + const char *rule_name, + const char *extension, + char **_filename_path); + +errno_t +ipa_deskprofile_rules_create_user_dir( + const char *username, /* fully-qualified */ + uid_t uid, + gid_t gid); +errno_t +ipa_deskprofile_rules_save_rule_to_disk( + TALLOC_CTX *mem_ctx, + uint16_t priority, + struct sysdb_attrs *rule, + struct sss_domain_info *domain, + const char *hostname, + const char *username, /* fully-qualified */ + uid_t uid, + gid_t gid); +errno_t +ipa_deskprofile_rules_remove_user_dir(const char *user_dir, + uid_t uid, + gid_t gid); + +errno_t +deskprofile_get_cached_priority(struct sss_domain_info *domain, + uint16_t *_priority); + +const char ** +deskprofile_get_attrs_to_get_cached_rules(TALLOC_CTX *mem_ctx); + +#endif /* IPA_DESKPROFILE_RULES_UTIL_H_ */ diff --git a/src/providers/ipa/ipa_dn.c b/src/providers/ipa/ipa_dn.c new file mode 100644 index 0000000..c58e014 --- /dev/null +++ b/src/providers/ipa/ipa_dn.c @@ -0,0 +1,145 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <talloc.h> +#include <ldb.h> +#include "db/sysdb.h" +#include "providers/ipa/ipa_dn.h" + +static bool check_dn(struct ldb_dn *dn, + const char *rdn_attr, + va_list in_ap) +{ + const struct ldb_val *ldbval; + const char *strval; + const char *ldbattr; + const char *attr; + const char *val; + va_list ap; + int num_comp; + int comp; + + /* check RDN attribute */ + ldbattr = ldb_dn_get_rdn_name(dn); + if (ldbattr == NULL || strcasecmp(ldbattr, rdn_attr) != 0) { + return false; + } + + /* Check DN components. First we check if all attr=value pairs match input. + * Then we check that the next attribute is a domain component. + */ + + comp = 1; + num_comp = ldb_dn_get_comp_num(dn); + + va_copy(ap, in_ap); + while ((attr = va_arg(ap, const char *)) != NULL) { + val = va_arg(ap, const char *); + if (val == NULL) { + goto vafail; + } + + if (comp > num_comp) { + goto vafail; + } + + ldbattr = ldb_dn_get_component_name(dn, comp); + if (ldbattr == NULL || strcasecmp(ldbattr, attr) != 0) { + goto vafail; + } + + ldbval = ldb_dn_get_component_val(dn, comp); + if (ldbval == NULL) { + goto vafail; + } + + strval = (const char *)ldbval->data; + if (strval == NULL || strncasecmp(strval, val, ldbval->length) != 0) { + goto vafail; + } + + comp++; + } + va_end(ap); + + ldbattr = ldb_dn_get_component_name(dn, comp); + if (ldbattr == NULL || strcmp(ldbattr, "dc") != 0) { + return false; + } + + return true; + +vafail: + va_end(ap); + return false; +} + +errno_t _ipa_get_rdn(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *obj_dn, + char **_rdn_val, + const char *rdn_attr, + ...) +{ + const struct ldb_val *val; + struct ldb_dn *dn; + errno_t ret; + bool bret; + va_list ap; + char *rdn; + + dn = ldb_dn_new(mem_ctx, sysdb_ctx_get_ldb(sysdb), obj_dn); + if (dn == NULL) { + return ENOMEM; + } + + va_start(ap, rdn_attr); + bret = check_dn(dn, rdn_attr, ap); + va_end(ap); + if (bret == false) { + ret = ENOENT; + goto done; + } + + if (_rdn_val == NULL) { + ret = EOK; + goto done; + } + + val = ldb_dn_get_rdn_val(dn); + if (val == NULL || val->data == NULL) { + ret = EINVAL; + goto done; + } + + rdn = talloc_strndup(mem_ctx, (const char*)val->data, val->length); + if (rdn == NULL) { + ret = ENOMEM; + goto done; + } + + *_rdn_val = rdn; + + ret = EOK; + +done: + talloc_free(dn); + return ret; +} diff --git a/src/providers/ipa/ipa_dn.h b/src/providers/ipa/ipa_dn.h new file mode 100644 index 0000000..f889c3e --- /dev/null +++ b/src/providers/ipa/ipa_dn.h @@ -0,0 +1,43 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef IPA_DN_H_ +#define IPA_DN_H_ + +#include <talloc.h> +#include "db/sysdb.h" + +errno_t _ipa_get_rdn(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *obj_dn, + char **_rdn_val, + const char *rdn_attr, + ...); + +#define ipa_get_rdn(mem_ctx, sysdb, dn, _rdn_val, rdn_attr, ...) \ + _ipa_get_rdn(mem_ctx, sysdb, dn, _rdn_val, rdn_attr, ##__VA_ARGS__, NULL) + +#define ipa_check_rdn(sysdb, dn, rdn_attr, ...) \ + _ipa_get_rdn(NULL, sysdb, dn, NULL, rdn_attr, ##__VA_ARGS__, NULL) + +#define ipa_check_rdn_bool(sysdb, dn, rdn_attr, ...) \ + ((bool)(ipa_check_rdn(sysdb, dn, rdn_attr, ##__VA_ARGS__) == EOK)) + +#endif /* IPA_DN_H_ */ diff --git a/src/providers/ipa/ipa_dyndns.c b/src/providers/ipa/ipa_dyndns.c new file mode 100644 index 0000000..2807c28 --- /dev/null +++ b/src/providers/ipa/ipa_dyndns.c @@ -0,0 +1,269 @@ +/* + SSSD + + ipa_dyndns.c + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2010 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <ctype.h> +#include "util/util.h" +#include "providers/ldap/sdap_dyndns.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_dyndns.h" +#include "providers/data_provider.h" +#include "providers/be_dyndns.h" + +struct ipa_dyndns_update_state { + struct ipa_options *ipa_ctx; + struct sdap_id_op *sdap_op; +}; + +static void +ipa_dyndns_sdap_update_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_dyndns_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt); + +static errno_t +ipa_dyndns_update_recv(struct tevent_req *req); + +static void +ipa_dyndns_update_connect_done(struct tevent_req *subreq); + + +errno_t ipa_dyndns_init(struct be_ctx *be_ctx, + struct ipa_options *ctx) +{ + errno_t ret; + const time_t ptask_first_delay = 10; + int period; + int offset; + uint32_t extraflags = 0; + + ctx->be_res = be_ctx->be_res; + if (ctx->be_res == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Resolver must be initialized in order " + "to use the IPA dynamic DNS updates\n"); + return EINVAL; + } + + period = dp_opt_get_int(ctx->dyndns_ctx->opts, DP_OPT_DYNDNS_REFRESH_INTERVAL); + if (period == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "DNS will not be updated periodically, " + "dyndns_refresh_interval is 0\n"); + extraflags |= BE_PTASK_NO_PERIODIC; + offset = 0; + } else { + offset = dp_opt_get_int(ctx->dyndns_ctx->opts, DP_OPT_DYNDNS_REFRESH_OFFSET); + } + + ret = be_ptask_create(ctx, be_ctx, period, ptask_first_delay, 0, offset, + period, 0, + ipa_dyndns_update_send, ipa_dyndns_update_recv, ctx, + "Dyndns update", + extraflags | + BE_PTASK_OFFLINE_DISABLE | + BE_PTASK_SCHEDULE_FROM_LAST, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup ptask " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + return ret; +} + +static struct tevent_req * +ipa_dyndns_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + int ret; + struct ipa_options *ctx; + struct ipa_dyndns_update_state *state; + struct tevent_req *req, *subreq; + struct sdap_id_ctx *sdap_ctx; + + DEBUG(SSSDBG_TRACE_FUNC, "Performing update\n"); + + ctx = talloc_get_type(pvt, struct ipa_options); + sdap_ctx = ctx->id_ctx->sdap_id_ctx; + + req = tevent_req_create(ctx, &state, struct ipa_dyndns_update_state); + if (req == NULL) { + return NULL; + } + state->ipa_ctx = ctx; + + if (ctx->dyndns_ctx->last_refresh + 60 > time(NULL) || + ctx->dyndns_ctx->timer_in_progress) { + DEBUG(SSSDBG_FUNC_DATA, "Last periodic update ran recently or timer " + "in progress, not scheduling another update\n"); + tevent_req_done(req); + tevent_req_post(req, sdap_ctx->be->ev); + return req; + } + state->ipa_ctx->dyndns_ctx->last_refresh = time(NULL); + + /* Make sure to have a valid LDAP connection */ + state->sdap_op = sdap_id_op_create(state, sdap_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (!subreq) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: [%d](%s)\n", + ret, sss_strerror(ret)); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_dyndns_update_connect_done, req); + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, sdap_ctx->be->ev); + } + return req; +} + +static void +ipa_dyndns_update_connect_done(struct tevent_req *subreq) +{ + int dp_error; + int ret; + struct ipa_options *ctx; + struct tevent_req *req; + struct ipa_dyndns_update_state *state; + struct sdap_id_ctx *sdap_ctx; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_dyndns_update_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, "No server is available, " + "dynamic DNS update is skipped in offline mode.\n"); + tevent_req_error(req, ERR_DYNDNS_OFFLINE); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect to LDAP server: [%d](%s)\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ERR_NETWORK_IO); + } + return; + } + + ctx = state->ipa_ctx; + sdap_ctx = ctx->id_ctx->sdap_id_ctx; + + /* The following three checks are here to prevent SEGFAULT + * from ticket #3076. */ + if (ctx->service == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "service structure not initialized\n"); + ret = EINVAL; + goto done; + } + + if (ctx->service->sdap == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap structure not initialized\n"); + ret = EINVAL; + goto done; + } + + if (ctx->service->sdap->uri == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "LDAP uri not set\n"); + ret = EINVAL; + goto done; + } + + if (strncmp(ctx->service->sdap->uri, + "ldap://", 7) != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected format of LDAP URI.\n"); + ret = EIO; + goto done; + } + + subreq = sdap_dyndns_update_send(state, sdap_ctx->be->ev, + sdap_ctx->be, + ctx->dyndns_ctx->opts, + sdap_ctx, + ctx->dyndns_ctx->auth_type, + ctx->dyndns_ctx->auth_ptr_type, + dp_opt_get_string(ctx->dyndns_ctx->opts, + DP_OPT_DYNDNS_IFACE), + dp_opt_get_string(ctx->basic, + IPA_HOSTNAME), + dp_opt_get_string(ctx->basic, + IPA_KRB5_REALM), + dp_opt_get_int(ctx->dyndns_ctx->opts, + DP_OPT_DYNDNS_TTL), + true); + if (!subreq) { + ret = EIO; + DEBUG(SSSDBG_OP_FAILURE, + "sdap_id_op_connect_send failed: [%d](%s)\n", + ret, sss_strerror(ret)); + goto done; + } + tevent_req_set_callback(subreq, ipa_dyndns_sdap_update_done, req); + + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, sdap_ctx->be->ev); + } +} + +static void ipa_dyndns_sdap_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + errno_t ret; + + ret = sdap_dyndns_update_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Dynamic DNS update failed [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_dyndns_update_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ipa/ipa_dyndns.h b/src/providers/ipa/ipa_dyndns.h new file mode 100644 index 0000000..d6873b7 --- /dev/null +++ b/src/providers/ipa/ipa_dyndns.h @@ -0,0 +1,35 @@ +/* + SSSD + + ipa_dyndns.h + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2010 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef IPA_DYNDNS_H_ +#define IPA_DYNDNS_H_ + +#include "util/util_errors.h" +#include "providers/ipa/ipa_common.h" +#include "providers/backend.h" + +errno_t ipa_dyndns_init(struct be_ctx *be_ctx, + struct ipa_options *ctx); + +#endif /* IPA_DYNDNS_H_ */ diff --git a/src/providers/ipa/ipa_hbac_common.c b/src/providers/ipa/ipa_hbac_common.c new file mode 100644 index 0000000..1fee41a --- /dev/null +++ b/src/providers/ipa/ipa_hbac_common.c @@ -0,0 +1,748 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2011 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_rules_common.h" + +errno_t +replace_attribute_name(const char *old_name, + const char *new_name, const size_t count, + struct sysdb_attrs **list) +{ + int ret; + int i; + + for (i = 0; i < count; i++) { + ret = sysdb_attrs_replace_name(list[i], old_name, new_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_replace_name failed.\n"); + return ret; + } + } + + return EOK; +} + +static errno_t +create_empty_grouplist(struct hbac_request_element *el) +{ + el->groups = talloc_array(el, const char *, 1); + if (!el->groups) return ENOMEM; + + el->groups[0] = NULL; + return EOK; +} + +/******************************************** + * Functions for handling conversion to the * + * HBAC evaluator format * + ********************************************/ + +static errno_t +hbac_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct hbac_ctx *hbac_ctx, + size_t index, + struct hbac_rule **rule); + +static errno_t +hbac_ctx_to_eval_request(TALLOC_CTX *mem_ctx, + struct hbac_ctx *hbac_ctx, + struct hbac_eval_req **request); + +errno_t +hbac_ctx_to_rules(TALLOC_CTX *mem_ctx, + struct hbac_ctx *hbac_ctx, + struct hbac_rule ***rules, + struct hbac_eval_req **request) +{ + errno_t ret; + struct hbac_rule **new_rules; + struct hbac_eval_req *new_request = NULL; + size_t i; + TALLOC_CTX *tmp_ctx = NULL; + + if (!rules || !request) return EINVAL; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + /* First create an array of rules */ + new_rules = talloc_array(tmp_ctx, struct hbac_rule *, + hbac_ctx->rule_count + 1); + if (new_rules == NULL) { + ret = ENOMEM; + goto done; + } + + /* Create each rule one at a time */ + for (i = 0; i < hbac_ctx->rule_count ; i++) { + ret = hbac_attrs_to_rule(new_rules, hbac_ctx, i, &(new_rules[i])); + if (ret == EPERM) { + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not construct rules\n"); + goto done; + } + } + new_rules[i] = NULL; + + /* Create the eval request */ + ret = hbac_ctx_to_eval_request(tmp_ctx, hbac_ctx, &new_request); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not construct eval request\n"); + goto done; + } + + *rules = talloc_steal(mem_ctx, new_rules); + *request = talloc_steal(mem_ctx, new_request); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +hbac_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct hbac_ctx *hbac_ctx, + size_t idx, + struct hbac_rule **rule) +{ + errno_t ret; + struct hbac_rule *new_rule; + struct ldb_message_element *el; + const char *rule_type; + + new_rule = talloc_zero(mem_ctx, struct hbac_rule); + if (new_rule == NULL) return ENOMEM; + + ret = sysdb_attrs_get_el(hbac_ctx->rules[idx], + IPA_CN, &el); + if (ret != EOK || el->num_values == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, "rule has no name, assuming '(none)'.\n"); + new_rule->name = talloc_strdup(new_rule, "(none)"); + } else { + new_rule->name = talloc_strndup(new_rule, + (const char*) el->values[0].data, + el->values[0].length); + } + + DEBUG(SSSDBG_TRACE_LIBS, "Processing rule [%s]\n", new_rule->name); + + ret = sysdb_attrs_get_bool(hbac_ctx->rules[idx], IPA_ENABLED_FLAG, + &new_rule->enabled); + if (ret != EOK) goto done; + + if (!new_rule->enabled) { + ret = EOK; + goto done; + } + + ret = sysdb_attrs_get_string(hbac_ctx->rules[idx], + IPA_ACCESS_RULE_TYPE, + &rule_type); + if (ret != EOK) goto done; + + if (strcasecmp(rule_type, IPA_HBAC_ALLOW) != 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Rule [%s] is not an ALLOW rule\n", new_rule->name); + ret = EPERM; + goto done; + } + + /* Get the users */ + ret = hbac_user_attrs_to_rule(new_rule, hbac_ctx->be_ctx->domain, + new_rule->name, + hbac_ctx->rules[idx], + &new_rule->users); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not parse users for rule [%s]\n", + new_rule->name); + goto done; + } + + /* Get the services */ + ret = hbac_service_attrs_to_rule(new_rule, hbac_ctx->be_ctx->domain, + new_rule->name, + hbac_ctx->rules[idx], + &new_rule->services); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not parse services for rule [%s]\n", + new_rule->name); + goto done; + } + + /* Get the target hosts */ + ret = hbac_thost_attrs_to_rule(new_rule, hbac_ctx->be_ctx->domain, + new_rule->name, + hbac_ctx->rules[idx], + &new_rule->targethosts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not parse target hosts for rule [%s]\n", + new_rule->name); + goto done; + } + + /* Get the source hosts */ + + ret = hbac_shost_attrs_to_rule(new_rule, hbac_ctx->be_ctx->domain, + new_rule->name, + hbac_ctx->rules[idx], + dp_opt_get_bool(hbac_ctx->ipa_options, + IPA_HBAC_SUPPORT_SRCHOST), + &new_rule->srchosts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not parse source hosts for rule [%s]\n", + new_rule->name); + goto done; + } + + *rule = new_rule; + ret = EOK; + +done: + if (ret != EOK) talloc_free(new_rule); + return ret; +} + +errno_t +hbac_get_category(struct sysdb_attrs *attrs, + const char *category_attr, + uint32_t *_categories) +{ + errno_t ret; + size_t i; + uint32_t cats = HBAC_CATEGORY_NULL; + const char **categories; + + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) return ENOMEM; + + ret = sysdb_attrs_get_string_array(attrs, category_attr, + tmp_ctx, &categories); + if (ret != EOK && ret != ENOENT) goto done; + + if (ret != ENOENT) { + for (i = 0; categories[i]; i++) { + if (strcasecmp("all", categories[i]) == 0) { + DEBUG(SSSDBG_FUNC_DATA, "Category is set to 'all'.\n"); + cats |= HBAC_CATEGORY_ALL; + continue; + } + DEBUG(SSSDBG_TRACE_ALL, "Unsupported user category [%s].\n", + categories[i]); + } + } + + *_categories = cats; + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +hbac_eval_user_element(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *username, + struct hbac_request_element **user_element); + +static errno_t +hbac_eval_service_element(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *servicename, + struct hbac_request_element **svc_element); + +static errno_t +hbac_eval_host_element(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *hostname, + struct hbac_request_element **host_element); + +static errno_t +hbac_ctx_to_eval_request(TALLOC_CTX *mem_ctx, + struct hbac_ctx *hbac_ctx, + struct hbac_eval_req **request) +{ + errno_t ret; + struct pam_data *pd = hbac_ctx->pd; + TALLOC_CTX *tmp_ctx; + struct hbac_eval_req *eval_req; + struct sss_domain_info *domain = hbac_ctx->be_ctx->domain; + const char *rhost; + const char *thost; + struct sss_domain_info *user_dom; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + eval_req = talloc_zero(tmp_ctx, struct hbac_eval_req); + if (eval_req == NULL) { + ret = ENOMEM; + goto done; + } + + eval_req->request_time = time(NULL); + + /* Get user the user name and groups, + * take care of subdomain users as well */ + if (strcasecmp(pd->domain, domain->name) != 0) { + user_dom = find_domain_by_name(domain, pd->domain, true); + if (user_dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n"); + ret = ENOMEM; + goto done; + } + ret = hbac_eval_user_element(eval_req, user_dom, pd->user, + &eval_req->user); + } else { + ret = hbac_eval_user_element(eval_req, domain, pd->user, + &eval_req->user); + } + if (ret != EOK) goto done; + + /* Get the PAM service and service groups */ + ret = hbac_eval_service_element(eval_req, domain, pd->service, + &eval_req->service); + if (ret != EOK) goto done; + + /* Get the source host */ + if (pd->rhost == NULL || pd->rhost[0] == '\0') { + /* If we haven't been passed an rhost, + * the rhost is unknown. This will fail + * to match any rule requiring the + * source host. + */ + rhost = NULL; + } else { + rhost = pd->rhost; + } + + ret = hbac_eval_host_element(eval_req, domain, rhost, + &eval_req->srchost); + if (ret != EOK) goto done; + + /* The target host is always the current machine */ + thost = dp_opt_get_cstring(hbac_ctx->ipa_options, IPA_HOSTNAME); + if (thost == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing ipa_hostname, this should never happen.\n"); + ret = EINVAL; + goto done; + } + + ret = hbac_eval_host_element(eval_req, domain, thost, + &eval_req->targethost); + if (ret != EOK) goto done; + + *request = talloc_steal(mem_ctx, eval_req); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +hbac_eval_user_element(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *username, + struct hbac_request_element **user_element) +{ + errno_t ret; + unsigned int num_groups = 0; + TALLOC_CTX *tmp_ctx; + struct hbac_request_element *users; + char *shortname; + const char *fqgroupname = NULL; + struct sss_domain_info *ipa_domain; + struct ldb_dn *ipa_groups_basedn; + struct ldb_result *res; + int exp_comp; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + users = talloc_zero(tmp_ctx, struct hbac_request_element); + if (users == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_parse_internal_fqname(tmp_ctx, username, &shortname, NULL); + if (ret != EOK) { + ret = ERR_WRONG_NAME_FORMAT; + goto done; + } + users->name = talloc_steal(users, shortname); + + ipa_domain = get_domains_head(domain); + if (ipa_domain == NULL) { + ret = EINVAL; + goto done; + } + + ipa_groups_basedn = ldb_dn_new_fmt(tmp_ctx, sysdb_ctx_get_ldb(domain->sysdb), + SYSDB_TMPL_GROUP_BASE, ipa_domain->name); + if (ipa_groups_basedn == NULL) { + ret = ENOMEM; + goto done; + } + + /* +1 because there will be a RDN preceding the base DN */ + exp_comp = ldb_dn_get_comp_num(ipa_groups_basedn) + 1; + + /* + * Get all the groups the user is a member of. + * This includes both POSIX and non-POSIX groups. + */ + ret = sysdb_initgroups(tmp_ctx, domain, username, &res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_initgroups() failed [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + if (res->count == 0) { + /* This should not happen at this point */ + DEBUG(SSSDBG_MINOR_FAILURE, + "User [%s] not found in cache.\n", username); + ret = ENOENT; + goto done; + } else if (res->count == 1) { + /* The first item is the user entry */ + DEBUG(SSSDBG_TRACE_LIBS, "No groups for [%s]\n", users->name); + ret = create_empty_grouplist(users); + goto done; + } + DEBUG(SSSDBG_TRACE_LIBS, + "[%u] groups for [%s]\n", res->count - 1, username); + + /* This also includes the sentinel, b/c we'll skip the user entry below */ + users->groups = talloc_array(users, const char *, res->count); + if (users->groups == NULL) { + ret = ENOMEM; + goto done; + } + + /* Start counting from 1 to exclude the user entry */ + for (size_t i = 1; i < res->count; i++) { + /* Only groups from the IPA domain can be referenced from HBAC rules. To + * avoid evaluating groups which might even have the same name, but come + * from a trusted domain, we first copy the DN to a temporary one.. + */ + if (ldb_dn_get_comp_num(res->msgs[i]->dn) != exp_comp + || ldb_dn_compare_base(ipa_groups_basedn, + res->msgs[i]->dn) != 0) { + DEBUG(SSSDBG_FUNC_DATA, + "Skipping non-IPA group %s\n", + ldb_dn_get_linearized(res->msgs[i]->dn)); + continue; + } + + fqgroupname = ldb_msg_find_attr_as_string(res->msgs[i], SYSDB_NAME, NULL); + if (fqgroupname == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Skipping malformed entry [%s]\n", + ldb_dn_get_linearized(res->msgs[i]->dn)); + continue; + } + + ret = sss_parse_internal_fqname(tmp_ctx, fqgroupname, + &shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Malformed name %s, skipping!\n", fqgroupname); + continue; + } + + users->groups[num_groups] = talloc_steal(users->groups, shortname); + DEBUG(SSSDBG_TRACE_LIBS, "Added group [%s] for user [%s]\n", + users->groups[num_groups], users->name); + num_groups++; + } + users->groups[num_groups] = NULL; + + if (num_groups < (res->count - 1)) { + /* Shrink the array memory */ + users->groups = talloc_realloc(users, users->groups, const char *, + num_groups+1); + if (users->groups == NULL) { + ret = ENOMEM; + goto done; + } + } + + ret = EOK; +done: + if (ret == EOK) { + *user_element = talloc_steal(mem_ctx, users); + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +hbac_eval_service_element(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *servicename, + struct hbac_request_element **svc_element) +{ + errno_t ret; + size_t i, j, count; + TALLOC_CTX *tmp_ctx; + struct hbac_request_element *svc; + struct ldb_message **msgs; + struct ldb_message_element *el; + struct ldb_dn *svc_dn; + const char *memberof_attrs[] = { SYSDB_ORIG_MEMBEROF, NULL }; + char *name; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + svc = talloc_zero(tmp_ctx, struct hbac_request_element); + if (svc == NULL) { + ret = ENOMEM; + goto done; + } + + svc->name = servicename; + + svc_dn = sysdb_custom_dn(tmp_ctx, domain, svc->name, HBAC_SERVICES_SUBDIR); + if (svc_dn == NULL) { + ret = ENOMEM; + goto done; + } + + /* Look up the service to get its originalMemberOf entries */ + ret = sysdb_search_entry(tmp_ctx, domain->sysdb, svc_dn, + LDB_SCOPE_BASE, NULL, + memberof_attrs, + &count, &msgs); + if (ret == ENOENT || count == 0) { + /* We won't be able to identify any groups + * This rule will only match the name or + * a service category of ALL + */ + ret = create_empty_grouplist(svc); + goto done; + } else if (ret != EOK) { + goto done; + } else if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "More than one result for a BASE search!\n"); + ret = EIO; + goto done; + } + + el = ldb_msg_find_element(msgs[0], SYSDB_ORIG_MEMBEROF); + if (!el) { + /* Service is not a member of any groups + * This rule will only match the name or + * a service category of ALL + */ + ret = create_empty_grouplist(svc); + goto done; + } + + + svc->groups = talloc_array(svc, const char *, el->num_values + 1); + if (svc->groups == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = j = 0; i < el->num_values; i++) { + ret = get_ipa_servicegroupname(tmp_ctx, domain->sysdb, + (const char *)el->values[i].data, + &name); + if (ret != EOK && ret != ERR_UNEXPECTED_ENTRY_TYPE) { + DEBUG(SSSDBG_MINOR_FAILURE, "Skipping malformed entry [%s]\n", + (const char *)el->values[i].data); + continue; + } + + /* ERR_UNEXPECTED_ENTRY_TYPE means we had a memberOf entry that wasn't a + * service group. We'll just ignore those (could be + * HBAC rules) + */ + + if (ret == EOK) { + svc->groups[j] = talloc_steal(svc->groups, name); + j++; + } + } + svc->groups[j] = NULL; + + ret = EOK; + +done: + if (ret == EOK) { + *svc_element = talloc_steal(mem_ctx, svc); + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +hbac_eval_host_element(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *hostname, + struct hbac_request_element **host_element) +{ + errno_t ret; + size_t i, j, count; + TALLOC_CTX *tmp_ctx; + struct hbac_request_element *host; + struct ldb_message **msgs; + struct ldb_message_element *el; + struct ldb_dn *host_dn; + const char *memberof_attrs[] = { SYSDB_ORIG_MEMBEROF, NULL }; + char *name; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + host = talloc_zero(tmp_ctx, struct hbac_request_element); + if (host == NULL) { + ret = ENOMEM; + goto done; + } + + host->name = hostname; + + if (host->name == NULL) { + /* We don't know the host (probably an rhost) + * So we can't determine it's groups either. + */ + ret = create_empty_grouplist(host); + goto done; + } + + host_dn = sysdb_custom_dn(tmp_ctx, domain, host->name, HBAC_HOSTS_SUBDIR); + if (host_dn == NULL) { + ret = ENOMEM; + goto done; + } + + /* Look up the host to get its originalMemberOf entries */ + ret = sysdb_search_entry(tmp_ctx, domain->sysdb, host_dn, + LDB_SCOPE_BASE, NULL, + memberof_attrs, + &count, &msgs); + if (ret == ENOENT || count == 0) { + /* We won't be able to identify any groups + * This rule will only match the name or + * a host category of ALL + */ + ret = create_empty_grouplist(host); + goto done; + } else if (ret != EOK) { + goto done; + } else if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "More than one result for a BASE search!\n"); + ret = EIO; + goto done; + } + + el = ldb_msg_find_element(msgs[0], SYSDB_ORIG_MEMBEROF); + if (!el) { + /* Host is not a member of any groups + * This rule will only match the name or + * a host category of ALL + */ + ret = create_empty_grouplist(host); + goto done; + } + + + host->groups = talloc_array(host, const char *, el->num_values + 1); + if (host->groups == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = j = 0; i < el->num_values; i++) { + ret = ipa_common_get_hostgroupname(tmp_ctx, domain->sysdb, + (const char *)el->values[i].data, + &name); + if (ret != EOK && ret != ERR_UNEXPECTED_ENTRY_TYPE) { + DEBUG(SSSDBG_MINOR_FAILURE, "Skipping malformed entry [%s]\n", + (const char *)el->values[i].data); + continue; + } + + /* ERR_UNEXPECTED_ENTRY_TYPE means we had a memberOf entry that wasn't a + * host group. We'll just ignore those (could be + * HBAC rules) + */ + + if (ret == EOK) { + host->groups[j] = talloc_steal(host->groups, name); + j++; + } + } + host->groups[j] = NULL; + + ret = EOK; + +done: + if (ret == EOK) { + *host_element = talloc_steal(mem_ctx, host); + } + talloc_free(tmp_ctx); + return ret; +} + +const char ** +hbac_get_attrs_to_get_cached_rules(TALLOC_CTX *mem_ctx) +{ + const char **attrs = talloc_zero_array(mem_ctx, const char *, 16); + if (attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array() failed\n"); + goto done; + } + + attrs[0] = OBJECTCLASS; + attrs[1] = IPA_CN; + attrs[2] = SYSDB_ORIG_DN; + attrs[3] = IPA_UNIQUE_ID; + attrs[4] = IPA_ENABLED_FLAG; + attrs[5] = IPA_ACCESS_RULE_TYPE; + attrs[6] = IPA_MEMBER_USER; + attrs[7] = IPA_USER_CATEGORY; + attrs[8] = IPA_MEMBER_SERVICE; + attrs[9] = IPA_SERVICE_CATEGORY; + attrs[10] = IPA_SOURCE_HOST; + attrs[11] = IPA_SOURCE_HOST_CATEGORY; + attrs[12] = IPA_EXTERNAL_HOST; + attrs[13] = IPA_MEMBER_HOST; + attrs[14] = IPA_HOST_CATEGORY; + attrs[15] = NULL; + +done: + return attrs; +} diff --git a/src/providers/ipa/ipa_hbac_hosts.c b/src/providers/ipa/ipa_hbac_hosts.c new file mode 100644 index 0000000..f85ce53 --- /dev/null +++ b/src/providers/ipa/ipa_hbac_hosts.c @@ -0,0 +1,335 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2011 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ipa/ipa_rules_common.h" +#include "providers/ldap/sdap_async.h" + +/* + * Functions to convert sysdb_attrs to the hbac_rule format + */ +static errno_t hbac_host_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + const char *category_attr, + const char *member_attr, + size_t *host_count, + struct hbac_rule_element **hosts) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + struct hbac_rule_element *new_hosts; + const char *attrs[] = { SYSDB_FQDN, SYSDB_NAME, NULL }; + struct ldb_message_element *el; + size_t num_hosts = 0; + size_t num_hostgroups = 0; + size_t i; + char *member_dn; + char *filter; + size_t count; + struct ldb_message **msgs; + const char *name; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + new_hosts = talloc_zero(tmp_ctx, struct hbac_rule_element); + if (new_hosts == NULL) { + ret = ENOMEM; + goto done; + } + + /* First check for host category */ + ret = hbac_get_category(rule_attrs, category_attr, &new_hosts->category); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not identify host categories\n"); + goto done; + } + if (new_hosts->category & HBAC_CATEGORY_ALL) { + /* Short-cut to the exit */ + ret = EOK; + goto done; + } + + /* Get the list of DNs from the member_attr */ + ret = sysdb_attrs_get_el(rule_attrs, member_attr, &el); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_get_el failed.\n"); + goto done; + } + if (ret == ENOENT || el->num_values == 0) { + el->num_values = 0; + DEBUG(SSSDBG_CONF_SETTINGS, + "No host specified, rule will never apply.\n"); + } + + /* Assume maximum size; We'll trim it later */ + new_hosts->names = talloc_array(new_hosts, + const char *, + el->num_values +1); + if (new_hosts->names == NULL) { + ret = ENOMEM; + goto done; + } + + new_hosts->groups = talloc_array(new_hosts, + const char *, + el->num_values + 1); + if (new_hosts->groups == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < el->num_values; i++) { + ret = sss_filter_sanitize(tmp_ctx, + (const char *)el->values[i].data, + &member_dn); + if (ret != EOK) goto done; + + filter = talloc_asprintf(member_dn, "(%s=%s)", + SYSDB_ORIG_DN, member_dn); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + /* First check if this is a specific host */ + ret = sysdb_search_custom(tmp_ctx, domain, filter, + HBAC_HOSTS_SUBDIR, attrs, + &count, &msgs); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && count == 0) { + ret = ENOENT; + } + + if (ret == EOK) { + if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Original DN matched multiple hosts. Skipping \n"); + talloc_zfree(member_dn); + continue; + } + + /* Original DN matched a single host. Get the hostname */ + name = ldb_msg_find_attr_as_string(msgs[0], + SYSDB_FQDN, + NULL); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "FQDN is missing!\n"); + ret = EFAULT; + goto done; + } + + new_hosts->names[num_hosts] = talloc_strdup(new_hosts->names, + name); + if (new_hosts->names[num_hosts] == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Added host [%s] to rule [%s]\n", + name, rule_name); + num_hosts++; + } else { /* ret == ENOENT */ + /* Check if this is a hostgroup */ + ret = sysdb_search_custom(tmp_ctx, domain, filter, + HBAC_HOSTGROUPS_SUBDIR, attrs, + &count, &msgs); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && count == 0) { + ret = ENOENT; + } + + if (ret == EOK) { + if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Original DN matched multiple hostgroups. " + "Skipping\n"); + talloc_zfree(member_dn); + continue; + } + + /* Original DN matched a single group. Get the groupname */ + name = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Hostgroup name is missing!\n"); + ret = EFAULT; + goto done; + } + + new_hosts->groups[num_hostgroups] = + talloc_strdup(new_hosts->groups, name); + if (new_hosts->groups[num_hostgroups] == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Added hostgroup [%s] to rule [%s]\n", + name, rule_name); + num_hostgroups++; + } else { /* ret == ENOENT */ + /* Neither a host nor a hostgroup? Skip it */ + DEBUG(SSSDBG_TRACE_LIBS, + "[%s] does not map to either a host or hostgroup. " + "Skipping\n", member_dn); + } + } + talloc_zfree(member_dn); + } + new_hosts->names[num_hosts] = NULL; + new_hosts->groups[num_hostgroups] = NULL; + + /* Shrink the arrays down to their real sizes */ + new_hosts->names = talloc_realloc(new_hosts, new_hosts->names, + const char *, num_hosts + 1); + if (new_hosts->names == NULL) { + ret = ENOMEM; + goto done; + } + + new_hosts->groups = talloc_realloc(new_hosts, new_hosts->groups, + const char *, num_hostgroups + 1); + if (new_hosts->groups == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + *hosts = talloc_steal(mem_ctx, new_hosts); + if (host_count) *host_count = num_hosts; + } + talloc_free(tmp_ctx); + return ret; +} + +errno_t +hbac_thost_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **thosts) +{ + DEBUG(SSSDBG_TRACE_LIBS, + "Processing target hosts for rule [%s]\n", rule_name); + + return hbac_host_attrs_to_rule(mem_ctx, domain, + rule_name, rule_attrs, + IPA_HOST_CATEGORY, IPA_MEMBER_HOST, + NULL, thosts); +} + +errno_t +hbac_shost_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + bool support_srchost, + struct hbac_rule_element **source_hosts) +{ + errno_t ret; + size_t host_count; + TALLOC_CTX *tmp_ctx; + size_t idx; + struct ldb_message_element *el; + struct hbac_rule_element *shosts; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + DEBUG(SSSDBG_TRACE_FUNC, "Processing source hosts for rule [%s]\n", rule_name); + + if (!support_srchost) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Source hosts disabled, setting ALL\n"); + shosts = talloc_zero(tmp_ctx, struct hbac_rule_element); + if (shosts == NULL) { + ret = ENOMEM; + goto done; + } + + shosts->category = HBAC_CATEGORY_ALL; + ret = EOK; + goto done; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "WARNING: Using deprecated option " + "ipa_hbac_support_srchost.\n"); + sss_log(SSS_LOG_NOTICE, "WARNING: Using deprecated option " + "ipa_hbac_support_srchost.\n"); + } + + ret = hbac_host_attrs_to_rule(tmp_ctx, domain, + rule_name, rule_attrs, + IPA_SOURCE_HOST_CATEGORY, IPA_SOURCE_HOST, + &host_count, &shosts); + if (ret != EOK) { + goto done; + } + + if (shosts->category & HBAC_CATEGORY_ALL) { + /* All hosts (including external) are + * allowed. + */ + goto done; + } + + /* Include external (non-IPA-managed) source hosts */ + ret = sysdb_attrs_get_el(rule_attrs, IPA_EXTERNAL_HOST, &el); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && el->num_values == 0) ret = ENOENT; + + if (ret != ENOENT) { + shosts->names = talloc_realloc(shosts, shosts->names, const char *, + host_count + el->num_values + 1); + if (shosts->names == NULL) { + ret = ENOMEM; + goto done; + } + + for (idx = host_count; idx < host_count + el->num_values; idx++) { + shosts->names[idx] = + talloc_strdup(shosts->names, + (const char *)el->values[idx - host_count].data); + if (shosts->names[idx] == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, + "Added external source host [%s] to rule [%s]\n", + shosts->names[idx], rule_name); + } + shosts->names[idx] = NULL; + } + + ret = EOK; + +done: + if (ret == EOK) { + *source_hosts = talloc_steal(mem_ctx, shosts); + } + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/ipa/ipa_hbac_private.h b/src/providers/ipa/ipa_hbac_private.h new file mode 100644 index 0000000..8ca7d09 --- /dev/null +++ b/src/providers/ipa/ipa_hbac_private.h @@ -0,0 +1,132 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2011 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef IPA_HBAC_PRIVATE_H_ +#define IPA_HBAC_PRIVATE_H_ + +#include "providers/ipa/ipa_access.h" +#include "lib/ipa_hbac/ipa_hbac.h" + +#define IPA_HBAC_RULE "ipaHBACRule" + +#define IPA_HBAC_SERVICE "ipaHBACService" +#define IPA_HBAC_SERVICE_GROUP "ipaHBACServiceGroup" + +#define IPA_MEMBER "member" +#define HBAC_HOSTS_SUBDIR "hbac_hosts" +#define HBAC_HOSTGROUPS_SUBDIR "hbac_hostgroups" + +#define IPA_MEMBEROF "memberOf" +#define IPA_ACCESS_RULE_TYPE "accessRuleType" +#define IPA_HBAC_ALLOW "allow" +#define IPA_SERVICE_NAME "serviceName" +#define IPA_SOURCE_HOST "sourceHost" +#define IPA_SOURCE_HOST_CATEGORY "sourceHostCategory" +#define IPA_MEMBER_SERVICE "memberService" +#define IPA_SERVICE_CATEGORY "serviceCategory" + +#define IPA_HBAC_BASE_TMPL "cn=hbac,%s" +#define IPA_SERVICES_BASE_TMPL "cn=hbacservices,cn=accounts,%s" + +#define SYSDB_HBAC_BASE_TMPL "cn=hbac,"SYSDB_TMPL_CUSTOM_BASE + +#define HBAC_RULES_SUBDIR "hbac_rules" +#define HBAC_SERVICES_SUBDIR "hbac_services" +#define HBAC_SERVICEGROUPS_SUBDIR "hbac_servicegroups" + +/* From ipa_hbac_common.c */ +errno_t +replace_attribute_name(const char *old_name, + const char *new_name, const size_t count, + struct sysdb_attrs **list); + +errno_t hbac_ctx_to_rules(TALLOC_CTX *mem_ctx, + struct hbac_ctx *hbac_ctx, + struct hbac_rule ***rules, + struct hbac_eval_req **request); + +errno_t +hbac_get_category(struct sysdb_attrs *attrs, + const char *category_attr, + uint32_t *_categories); + +errno_t +hbac_thost_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **thosts); + +errno_t +hbac_shost_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + bool support_srchost, + struct hbac_rule_element **source_hosts); + +const char ** +hbac_get_attrs_to_get_cached_rules(TALLOC_CTX *mem_ctx); + +/* From ipa_hbac_services.c */ +struct tevent_req * +ipa_hbac_service_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct sdap_search_base **search_bases); + +errno_t +ipa_hbac_service_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *service_count, + struct sysdb_attrs ***services, + size_t *servicegroup_count, + struct sysdb_attrs ***servicegroups); + +errno_t +hbac_service_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **services); +errno_t +get_ipa_servicegroupname(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *service_dn, + char **servicename); + +/* From ipa_hbac_users.c */ +errno_t +hbac_user_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **users); + +errno_t +get_ipa_groupname(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *group_dn, + const char **groupname); + +#endif /* IPA_HBAC_PRIVATE_H_ */ diff --git a/src/providers/ipa/ipa_hbac_rules.c b/src/providers/ipa/ipa_hbac_rules.c new file mode 100644 index 0000000..e2c97ae --- /dev/null +++ b/src/providers/ipa/ipa_hbac_rules.c @@ -0,0 +1,313 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2011 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "providers/ipa/ipa_rules_common.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ipa/ipa_hbac_rules.h" +#include "providers/ldap/sdap_async.h" + +struct ipa_hbac_rule_state { + struct tevent_context *ev; + struct sdap_handle *sh; + struct sdap_options *opts; + + int search_base_iter; + struct sdap_search_base **search_bases; + + const char **attrs; + char *rules_filter; + char *cur_filter; + + size_t rule_count; + struct sysdb_attrs **rules; +}; + +static errno_t +ipa_hbac_rule_info_next(struct tevent_req *req, + struct ipa_hbac_rule_state *state); +static void +ipa_hbac_rule_info_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_hbac_rule_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sysdb_attrs *ipa_host) +{ + errno_t ret; + size_t i; + struct tevent_req *req = NULL; + struct ipa_hbac_rule_state *state; + const char *host_dn; + char *host_dn_clean; + char *host_group_clean; + char *rule_filter; + const char **memberof_list; + + req = tevent_req_create(mem_ctx, &state, struct ipa_hbac_rule_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + if (ipa_host == NULL) { + ret = EINVAL; + DEBUG(SSSDBG_CRIT_FAILURE, "Missing host\n"); + goto immediate; + } + + ret = sysdb_attrs_get_string(ipa_host, SYSDB_ORIG_DN, &host_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not identify IPA hostname\n"); + goto immediate; + } + + ret = sss_filter_sanitize_dn(state, host_dn, &host_dn_clean); + if (ret != EOK) goto immediate; + + state->ev = ev; + state->sh = sh; + state->opts = opts; + state->search_bases = search_bases; + state->search_base_iter = 0; + state->attrs = talloc_zero_array(state, const char *, 15); + if (state->attrs == NULL) { + ret = ENOMEM; + goto immediate; + } + state->attrs[0] = OBJECTCLASS; + state->attrs[1] = IPA_CN; + state->attrs[2] = IPA_UNIQUE_ID; + state->attrs[3] = IPA_ENABLED_FLAG; + state->attrs[4] = IPA_ACCESS_RULE_TYPE; + state->attrs[5] = IPA_MEMBER_USER; + state->attrs[6] = IPA_USER_CATEGORY; + state->attrs[7] = IPA_MEMBER_SERVICE; + state->attrs[8] = IPA_SERVICE_CATEGORY; + state->attrs[9] = IPA_SOURCE_HOST; + state->attrs[10] = IPA_SOURCE_HOST_CATEGORY; + state->attrs[11] = IPA_EXTERNAL_HOST; + state->attrs[12] = IPA_MEMBER_HOST; + state->attrs[13] = IPA_HOST_CATEGORY; + state->attrs[14] = NULL; + + rule_filter = talloc_asprintf(state, + "(&(objectclass=%s)" + "(%s=%s)(%s=%s)" + "(|(%s=%s)(%s=%s)", + IPA_HBAC_RULE, + IPA_ENABLED_FLAG, IPA_TRUE_VALUE, + IPA_ACCESS_RULE_TYPE, IPA_HBAC_ALLOW, + IPA_HOST_CATEGORY, "all", + IPA_MEMBER_HOST, host_dn_clean); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + + /* Add all parent groups of ipa_hostname to the filter */ + ret = sysdb_attrs_get_string_array(ipa_host, SYSDB_ORIG_MEMBEROF, + state, &memberof_list); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not identify.\n"); + } else if (ret == ENOENT) { + /* This host is not a member of any hostgroups */ + memberof_list = talloc_array(state, const char *, 1); + if (memberof_list == NULL) { + ret = ENOMEM; + goto immediate; + } + memberof_list[0] = NULL; + } + + for (i = 0; memberof_list[i]; i++) { + ret = sss_filter_sanitize(state, + memberof_list[i], + &host_group_clean); + if (ret != EOK) goto immediate; + + rule_filter = talloc_asprintf_append(rule_filter, "(%s=%s)", + IPA_MEMBER_HOST, + host_group_clean); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + } + + rule_filter = talloc_asprintf_append(rule_filter, "))"); + if (rule_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + state->rules_filter = talloc_steal(state, rule_filter); + + ret = ipa_hbac_rule_info_next(req, state); + if (ret != EAGAIN) { + if (ret == EOK) { + /* ipa_hbac_rule_info_next should always have a search base when + * called for the first time. + * + * For the subsequent iterations, not finding any more search bases + * is fine though (thus the function returns EOK). + * + * As, here, it's the first case happening, let's return EINVAL. + */ + DEBUG(SSSDBG_CRIT_FAILURE, "No search base found\n"); + ret = EINVAL; + } + goto immediate; + } + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t +ipa_hbac_rule_info_next(struct tevent_req *req, + struct ipa_hbac_rule_state *state) +{ + struct tevent_req *subreq; + struct sdap_search_base *base; + + base = state->search_bases[state->search_base_iter]; + if (base == NULL) { + return EOK; + } + + talloc_zfree(state->cur_filter); + state->cur_filter = sdap_combine_filters(state, state->rules_filter, + base->filter); + if (state->cur_filter == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Sending request for next search base: " + "[%s][%d][%s]\n", base->basedn, base->scope, + state->cur_filter); + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + base->basedn, base->scope, + state->cur_filter, state->attrs, + NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_hbac_rule_info_done, req); + + return EAGAIN; +} + +static void +ipa_hbac_rule_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_hbac_rule_state *state = + tevent_req_data(req, struct ipa_hbac_rule_state); + int i; + size_t rule_count; + size_t total_count; + struct sysdb_attrs **rules; + struct sysdb_attrs **target; + + ret = sdap_get_generic_recv(subreq, state, + &rule_count, + &rules); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not retrieve HBAC rules\n"); + goto fail; + } + + if (rule_count > 0) { + total_count = rule_count + state->rule_count; + state->rules = talloc_realloc(state, state->rules, + struct sysdb_attrs *, + total_count); + if (state->rules == NULL) { + ret = ENOMEM; + goto fail; + } + + i = 0; + while (state->rule_count < total_count) { + target = &state->rules[state->rule_count]; + *target = talloc_steal(state->rules, rules[i]); + + state->rule_count++; + i++; + } + } + + state->search_base_iter++; + ret = ipa_hbac_rule_info_next(req, state); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + goto fail; + } else if (ret == EOK && state->rule_count == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "No rules apply to this host\n"); + tevent_req_error(req, ENOENT); + return; + } + + /* We went through all search bases and we have some results */ + tevent_req_done(req); + + return; + +fail: + tevent_req_error(req, ret); +} + +errno_t +ipa_hbac_rule_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_rule_count, + struct sysdb_attrs ***_rules) +{ + struct ipa_hbac_rule_state *state = + tevent_req_data(req, struct ipa_hbac_rule_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_rule_count = state->rule_count; + *_rules = talloc_steal(mem_ctx, state->rules); + + return EOK; +} diff --git a/src/providers/ipa/ipa_hbac_rules.h b/src/providers/ipa/ipa_hbac_rules.h new file mode 100644 index 0000000..d8e5a14 --- /dev/null +++ b/src/providers/ipa/ipa_hbac_rules.h @@ -0,0 +1,41 @@ +/* + SSSD + + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2012 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef IPA_HBAC_RULES_H_ +#define IPA_HBAC_RULES_H_ + +/* From ipa_hbac_rules.c */ +struct tevent_req * +ipa_hbac_rule_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct sdap_search_base **search_bases, + struct sysdb_attrs *ipa_host); + +errno_t +ipa_hbac_rule_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *_rule_count, + struct sysdb_attrs ***_rules); + +#endif /* IPA_HBAC_RULES_H_ */ diff --git a/src/providers/ipa/ipa_hbac_services.c b/src/providers/ipa/ipa_hbac_services.c new file mode 100644 index 0000000..387e915 --- /dev/null +++ b/src/providers/ipa/ipa_hbac_services.c @@ -0,0 +1,686 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2011 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "providers/ipa/ipa_rules_common.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ldap/sdap_async.h" + +struct ipa_hbac_service_state { + struct tevent_context *ev; + struct sdap_handle *sh; + struct sdap_options *opts; + const char **attrs; + + char *service_filter; + char *cur_filter; + + struct sdap_search_base **search_bases; + int search_base_iter; + + /* Return values */ + size_t service_count; + struct sysdb_attrs **services; + + size_t servicegroup_count; + struct sysdb_attrs **servicegroups; +}; + +static errno_t +ipa_hbac_service_info_next(struct tevent_req *req, + struct ipa_hbac_service_state *state); +static void +ipa_hbac_service_info_done(struct tevent_req *subreq); +static errno_t +ipa_hbac_servicegroup_info_next(struct tevent_req *req, + struct ipa_hbac_service_state *state); +static void +ipa_hbac_servicegroup_info_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_hbac_service_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + struct sdap_search_base **search_bases) +{ + errno_t ret; + struct ipa_hbac_service_state *state; + struct tevent_req *req; + char *service_filter; + + req = tevent_req_create(mem_ctx, &state, struct ipa_hbac_service_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->sh = sh; + state->opts = opts; + + state->search_bases = search_bases; + state->search_base_iter = 0; + + service_filter = talloc_asprintf(state, "(objectClass=%s)", + IPA_HBAC_SERVICE); + if (service_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + + state->service_filter = service_filter; + state->cur_filter = NULL; + + state->attrs = talloc_array(state, const char *, 6); + if (state->attrs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate service attribute list.\n"); + ret = ENOMEM; + goto immediate; + } + state->attrs[0] = OBJECTCLASS; + state->attrs[1] = IPA_CN; + state->attrs[2] = IPA_UNIQUE_ID; + state->attrs[3] = IPA_MEMBER; + state->attrs[4] = IPA_MEMBEROF; + state->attrs[5] = NULL; + + ret = ipa_hbac_service_info_next(req, state); + if (ret == EOK) { + ret = EINVAL; + } + + if (ret != EAGAIN) { + goto immediate; + } + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ipa_hbac_service_info_next(struct tevent_req *req, + struct ipa_hbac_service_state *state) +{ + struct tevent_req *subreq; + struct sdap_search_base *base; + + base = state->search_bases[state->search_base_iter]; + if (base == NULL) { + return EOK; + } + + talloc_zfree(state->cur_filter); + state->cur_filter = sdap_combine_filters(state, state->service_filter, + base->filter); + if (state->cur_filter == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Sending request for next search base: " + "[%s][%d][%s]\n", base->basedn, base->scope, + state->cur_filter); + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + base->basedn, base->scope, + state->cur_filter, + state->attrs, NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error requesting service info\n"); + return EIO; + } + tevent_req_set_callback(subreq, ipa_hbac_service_info_done, req); + + return EAGAIN; +} + +static void +ipa_hbac_service_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_hbac_service_state *state = + tevent_req_data(req, struct ipa_hbac_service_state); + char *servicegroup_filter; + + ret = sdap_get_generic_recv(subreq, state, + &state->service_count, + &state->services); + talloc_zfree(subreq); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + if (ret == ENOENT || state->service_count == 0) { + /* If there are no services, we'll shortcut out + * This is still valid, as rules can apply to + * all services + * + * There's no reason to try to process groups + */ + + state->search_base_iter++; + ret = ipa_hbac_service_info_next(req, state); + if (ret == EAGAIN) { + return; + } + + state->service_count = 0; + state->services = NULL; + goto done; + } + + ret = replace_attribute_name(IPA_MEMBEROF, SYSDB_ORIG_MEMBEROF, + state->service_count, + state->services); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not replace attribute names\n"); + goto done; + } + + servicegroup_filter = talloc_asprintf(state, "(objectClass=%s)", + IPA_HBAC_SERVICE_GROUP); + if (servicegroup_filter == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_zfree(state->service_filter); + state->service_filter = servicegroup_filter; + + state->search_base_iter = 0; + ret = ipa_hbac_servicegroup_info_next(req, state); + if (ret == EOK) { + ret = EINVAL; + } + + if (ret != EAGAIN) { + goto done; + } + + return; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static errno_t +ipa_hbac_servicegroup_info_next(struct tevent_req *req, + struct ipa_hbac_service_state *state) +{ + struct tevent_req *subreq; + struct sdap_search_base *base; + + base = state->search_bases[state->search_base_iter]; + if (base == NULL) { + return EOK; + } + + talloc_zfree(state->cur_filter); + state->cur_filter = sdap_combine_filters(state, state->service_filter, + base->filter); + if (state->cur_filter == NULL) { + return ENOMEM; + } + + /* Look up service groups */ + DEBUG(SSSDBG_TRACE_FUNC, "Sending request for next search base: " + "[%s][%d][%s]\n", base->basedn, base->scope, + state->cur_filter); + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + base->basedn, base->scope, + state->cur_filter, state->attrs, NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error requesting servicegroup info\n"); + return EIO; + } + tevent_req_set_callback(subreq, ipa_hbac_servicegroup_info_done, req); + + return EAGAIN; +} + +static void +ipa_hbac_servicegroup_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_hbac_service_state *state = + tevent_req_data(req, struct ipa_hbac_service_state); + size_t total_count; + size_t group_count; + struct sysdb_attrs **groups; + struct sysdb_attrs **target; + int i; + + ret = sdap_get_generic_recv(subreq, state, + &group_count, + &groups); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + if (group_count > 0) { + ret = replace_attribute_name(IPA_MEMBER, SYSDB_ORIG_MEMBER, + group_count, + groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not replace attribute names\n"); + goto done; + } + + ret = replace_attribute_name(IPA_MEMBEROF, SYSDB_ORIG_MEMBEROF, + state->servicegroup_count, + state->servicegroups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not replace attribute names\n"); + goto done; + } + + total_count = state->servicegroup_count + group_count; + state->servicegroups = talloc_realloc(state, state->servicegroups, + struct sysdb_attrs *, + total_count); + if (state->servicegroups == NULL) { + ret = ENOMEM; + goto done; + } + + i = 0; + while (state->servicegroup_count < total_count) { + target = &state->servicegroups[state->servicegroup_count]; + *target = talloc_steal(state->servicegroups, groups[i]); + + state->servicegroup_count++; + i++; + } + } + + state->search_base_iter++; + ret = ipa_hbac_servicegroup_info_next(req, state); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + goto done; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Error [%d][%s]\n", ret, strerror(ret)); + tevent_req_error(req, ret); + } +} + +errno_t +ipa_hbac_service_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *service_count, + struct sysdb_attrs ***services, + size_t *servicegroup_count, + struct sysdb_attrs ***servicegroups) +{ + size_t c; + struct ipa_hbac_service_state *state = + tevent_req_data(req, struct ipa_hbac_service_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *service_count = state->service_count; + *services = talloc_steal(mem_ctx, state->services); + for (c = 0; c < state->service_count; c++) { + /* Guarantee the memory heirarchy of the list */ + talloc_steal(state->services, state->services[c]); + } + + *servicegroup_count = state->servicegroup_count; + *servicegroups = talloc_steal(mem_ctx, state->servicegroups); + + return EOK; +} + +errno_t +hbac_service_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **services) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + struct hbac_rule_element *new_services; + const char *attrs[] = { IPA_CN, NULL }; + struct ldb_message_element *el; + size_t num_services = 0; + size_t num_servicegroups = 0; + size_t i; + char *member_dn; + char *filter; + size_t count; + struct ldb_message **msgs; + const char *name; + + DEBUG(SSSDBG_TRACE_LIBS, + "Processing PAM services for rule [%s]\n", rule_name); + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + new_services = talloc_zero(tmp_ctx, struct hbac_rule_element); + if (new_services == NULL) { + ret = ENOMEM; + goto done; + } + + /* First check for service category */ + ret = hbac_get_category(rule_attrs, IPA_SERVICE_CATEGORY, + &new_services->category); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not identify service categories\n"); + goto done; + } + if (new_services->category & HBAC_CATEGORY_ALL) { + /* Short-cut to the exit */ + ret = EOK; + goto done; + } + + /* Get the list of DNs from the member attr */ + ret = sysdb_attrs_get_el(rule_attrs, IPA_MEMBER_SERVICE, &el); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_get_el failed.\n"); + goto done; + } + if (ret == ENOENT || el->num_values == 0) { + el->num_values = 0; + DEBUG(SSSDBG_CONF_SETTINGS, + "No services specified, rule will never apply.\n"); + } + + /* Assume maximum size; We'll trim it later */ + new_services->names = talloc_array(new_services, + const char *, + el->num_values +1); + if (new_services->names == NULL) { + ret = ENOMEM; + goto done; + } + + new_services->groups = talloc_array(new_services, + const char *, + el->num_values + 1); + if (new_services->groups == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < el->num_values; i++) { + ret = sss_filter_sanitize(tmp_ctx, + (const char *)el->values[i].data, + &member_dn); + if (ret != EOK) goto done; + + filter = talloc_asprintf(member_dn, "(%s=%s)", + SYSDB_ORIG_DN, member_dn); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + /* First check if this is a specific service */ + ret = sysdb_search_custom(tmp_ctx, domain, filter, + HBAC_SERVICES_SUBDIR, attrs, + &count, &msgs); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && count == 0) { + ret = ENOENT; + } + + if (ret == EOK) { + if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Original DN matched multiple services. " + "Skipping \n"); + talloc_zfree(member_dn); + continue; + } + + /* Original DN matched a single service. Get the service name */ + name = ldb_msg_find_attr_as_string(msgs[0], IPA_CN, NULL); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Attribute IPA_CN is missing!\n"); + ret = EFAULT; + goto done; + } + + new_services->names[num_services] = + talloc_strdup(new_services->names, name); + if (new_services->names[num_services] == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Added service [%s] to rule [%s]\n", + name, rule_name); + num_services++; + } else { /* ret == ENOENT */ + /* Check if this is a service group */ + ret = sysdb_search_custom(tmp_ctx, domain, filter, + HBAC_SERVICEGROUPS_SUBDIR, attrs, + &count, &msgs); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && count == 0) { + ret = ENOENT; + } + + if (ret == EOK) { + if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Original DN matched multiple service groups. " + "Skipping\n"); + talloc_zfree(member_dn); + continue; + } + + /* Original DN matched a single group. Get the groupname */ + name = ldb_msg_find_attr_as_string(msgs[0], IPA_CN, NULL); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Attribute IPA_CN is missing!\n"); + ret = EFAULT; + goto done; + } + + new_services->groups[num_servicegroups] = + talloc_strdup(new_services->groups, name); + if (new_services->groups[num_servicegroups] == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Added service group [%s] to rule [%s]\n", + name, rule_name); + num_servicegroups++; + } else { /* ret == ENOENT */ + /* Neither a service nor a service group? Skip it */ + DEBUG(SSSDBG_CRIT_FAILURE, + "[%s] does not map to either a service or " + "service group. Skipping\n", member_dn); + } + } + talloc_zfree(member_dn); + } + new_services->names[num_services] = NULL; + new_services->groups[num_servicegroups] = NULL; + + /* Shrink the arrays down to their real sizes */ + new_services->names = talloc_realloc(new_services, new_services->names, + const char *, num_services + 1); + if (new_services->names == NULL) { + ret = ENOMEM; + goto done; + } + + new_services->groups = talloc_realloc(new_services, new_services->groups, + const char *, num_servicegroups + 1); + if (new_services->groups == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + *services = talloc_steal(mem_ctx, new_services); + } + talloc_free(tmp_ctx); + return ret; +} + +errno_t +get_ipa_servicegroupname(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *service_dn, + char **servicegroupname) +{ + errno_t ret; + struct ldb_dn *dn; + const char *rdn_name; + const char *svc_comp_name; + const char *hbac_comp_name; + const struct ldb_val *rdn_val; + const struct ldb_val *svc_comp_val; + const struct ldb_val *hbac_comp_val; + + /* This is an IPA-specific hack. It may not + * work for non-IPA servers and will need to + * be changed if SSSD ever supports HBAC on + * a non-IPA server. + */ + *servicegroupname = NULL; + + dn = ldb_dn_new(mem_ctx, sysdb_ctx_get_ldb(sysdb), service_dn); + if (dn == NULL) { + ret = ENOMEM; + goto done; + } + + if (!ldb_dn_validate(dn)) { + ret = ERR_MALFORMED_ENTRY; + goto done; + } + + if (ldb_dn_get_comp_num(dn) < 4) { + /* RDN, services, hbac, and at least one DC= */ + /* If it's fewer, it's not a group DN */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* If the RDN name is 'cn' */ + rdn_name = ldb_dn_get_rdn_name(dn); + if (rdn_name == NULL) { + /* Shouldn't happen if ldb_dn_validate() + * passed, but we'll be careful. + */ + ret = ERR_MALFORMED_ENTRY; + goto done; + } + + if (strcasecmp("cn", rdn_name) != 0) { + /* RDN has the wrong attribute name. + * It's not a service. + */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* and the second component is "cn=hbacservicegroups" */ + svc_comp_name = ldb_dn_get_component_name(dn, 1); + if (strcasecmp("cn", svc_comp_name) != 0) { + /* The second component name is not "cn" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + svc_comp_val = ldb_dn_get_component_val(dn, 1); + if (strncasecmp("hbacservicegroups", + (const char *) svc_comp_val->data, + svc_comp_val->length) != 0) { + /* The second component value is not "hbacservicegroups" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* and the third component is "hbac" */ + hbac_comp_name = ldb_dn_get_component_name(dn, 2); + if (strcasecmp("cn", hbac_comp_name) != 0) { + /* The third component name is not "cn" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + hbac_comp_val = ldb_dn_get_component_val(dn, 2); + if (strncasecmp("hbac", + (const char *) hbac_comp_val->data, + hbac_comp_val->length) != 0) { + /* The third component value is not "hbac" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* Then the value of the RDN is the group name */ + rdn_val = ldb_dn_get_rdn_val(dn); + *servicegroupname = talloc_strndup(mem_ctx, + (const char *)rdn_val->data, + rdn_val->length); + if (*servicegroupname == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + talloc_free(dn); + return ret; +} diff --git a/src/providers/ipa/ipa_hbac_users.c b/src/providers/ipa/ipa_hbac_users.c new file mode 100644 index 0000000..2f9e986 --- /dev/null +++ b/src/providers/ipa/ipa_hbac_users.c @@ -0,0 +1,369 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2011 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "providers/ipa/ipa_rules_common.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ldap/sdap_async.h" + +/* Returns EOK and populates groupname if + * the group_dn is actually a group. + * Returns ENOENT if group_dn does not point + * at a group. + * Returns EINVAL if there is a parsing error. + * Returns ENOMEM as appropriate + */ +errno_t +get_ipa_groupname(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *group_dn, + const char **groupname) +{ + errno_t ret; + struct ldb_dn *dn; + const char *rdn_name; + const char *group_comp_name; + const char *account_comp_name; + const struct ldb_val *rdn_val; + const struct ldb_val *group_comp_val; + const struct ldb_val *account_comp_val; + + /* This is an IPA-specific hack. It may not + * work for non-IPA servers and will need to + * be changed if SSSD ever supports HBAC on + * a non-IPA server. + */ + *groupname = NULL; + + DEBUG(SSSDBG_TRACE_LIBS, "Parsing %s\n", group_dn); + + dn = ldb_dn_new(mem_ctx, sysdb_ctx_get_ldb(sysdb), group_dn); + if (dn == NULL) { + ret = ENOMEM; + goto done; + } + + if (!ldb_dn_validate(dn)) { + DEBUG(SSSDBG_CRIT_FAILURE, "DN %s does not validate\n", group_dn); + ret = ERR_MALFORMED_ENTRY; + goto done; + } + + if (ldb_dn_get_comp_num(dn) < 4) { + /* RDN, groups, accounts, and at least one DC= */ + /* If it's fewer, it's not a group DN */ + DEBUG(SSSDBG_CRIT_FAILURE, "DN %s has too few components\n", group_dn); + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* If the RDN name is 'cn' */ + rdn_name = ldb_dn_get_rdn_name(dn); + if (rdn_name == NULL) { + /* Shouldn't happen if ldb_dn_validate() + * passed, but we'll be careful. + */ + DEBUG(SSSDBG_CRIT_FAILURE, "No RDN name in %s\n", group_dn); + ret = ERR_MALFORMED_ENTRY; + goto done; + } + + if (strcasecmp("cn", rdn_name) != 0) { + /* RDN has the wrong attribute name. + * It's not a group. + */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Expected cn in RDN, got %s\n", rdn_name); + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* and the second component is "cn=groups" */ + group_comp_name = ldb_dn_get_component_name(dn, 1); + if (strcasecmp("cn", group_comp_name) != 0) { + /* The second component name is not "cn" */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected cn in second component, got %s\n", group_comp_name); + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + group_comp_val = ldb_dn_get_component_val(dn, 1); + if (strncasecmp("groups", + (const char *) group_comp_val->data, + group_comp_val->length) != 0) { + /* The second component value is not "groups" */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected groups second component, got %s\n", + (const char *) group_comp_val->data); + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* and the third component is "accounts" */ + account_comp_name = ldb_dn_get_component_name(dn, 2); + if (strcasecmp("cn", account_comp_name) != 0) { + /* The third component name is not "cn" */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected cn in third component, got %s\n", account_comp_name); + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + account_comp_val = ldb_dn_get_component_val(dn, 2); + if (strncasecmp("accounts", + (const char *) account_comp_val->data, + account_comp_val->length) != 0) { + /* The third component value is not "accounts" */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected accounts third component, got %s\n", + (const char *) account_comp_val->data); + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* Then the value of the RDN is the group name */ + rdn_val = ldb_dn_get_rdn_val(dn); + *groupname = talloc_strndup(mem_ctx, + (const char *)rdn_val->data, + rdn_val->length); + if (*groupname == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_LIBS, "Parsed %s out of the DN\n", *groupname); + + ret = EOK; + +done: + talloc_free(dn); + return ret; +} + +errno_t +hbac_user_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **users) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + struct hbac_rule_element *new_users = NULL; + struct ldb_message_element *el = NULL; + struct ldb_message **msgs = NULL; + const char *member_dn; + const char *attrs[] = { SYSDB_NAME, NULL }; + size_t num_users = 0; + size_t num_groups = 0; + const char *sysdb_name; + char *shortname; + + size_t count; + size_t i; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return ENOMEM; + + new_users = talloc_zero(tmp_ctx, struct hbac_rule_element); + if (new_users == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Processing users for rule [%s]\n", rule_name); + + ret = hbac_get_category(rule_attrs, IPA_USER_CATEGORY, + &new_users->category); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not identify user categories\n"); + goto done; + } + if (new_users->category & HBAC_CATEGORY_ALL) { + /* Short-cut to the exit */ + ret = EOK; + goto done; + } + + ret = sysdb_attrs_get_el(rule_attrs, IPA_MEMBER_USER, &el); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_get_el failed.\n"); + goto done; + } + if (ret == ENOENT || el->num_values == 0) { + el->num_values = 0; + DEBUG(SSSDBG_CONF_SETTINGS, + "No user specified, rule will never apply.\n"); + } + + new_users->names = talloc_array(new_users, + const char *, + el->num_values + 1); + if (new_users->names == NULL) { + ret = ENOMEM; + goto done; + } + + new_users->groups = talloc_array(new_users, + const char *, + el->num_values + 1); + if (new_users->groups == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < el->num_values; i++) { + member_dn = (const char *)el->values[i].data; + + /* First check if this is a user */ + ret = sysdb_search_users_by_orig_dn(tmp_ctx, domain, member_dn, attrs, + &count, &msgs); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && count == 0) { + ret = ENOENT; + } + + if (ret == EOK) { + if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Original DN matched multiple users. Skipping \n"); + continue; + } + + /* Original DN matched a single user. Get the username */ + sysdb_name = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL); + if (sysdb_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Attribute is missing!\n"); + ret = EFAULT; + goto done; + } + + ret = sss_parse_internal_fqname(tmp_ctx, sysdb_name, + &shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot parse %s, skipping\n", sysdb_name); + continue; + } + + new_users->names[num_users] = talloc_strdup(new_users->names, + shortname); + if (new_users->names[num_users] == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, + "Added user [%s] to rule [%s]\n", sysdb_name, rule_name); + num_users++; + } else { + /* Check if it is a group instead */ + ret = sysdb_search_groups_by_orig_dn(tmp_ctx, domain, member_dn, + attrs, &count, &msgs); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && count == 0) { + ret = ENOENT; + } + + if (ret == EOK) { + if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Original DN matched multiple groups. " + "Skipping\n"); + continue; + } + + /* Original DN matched a single group. Get the groupname */ + sysdb_name = ldb_msg_find_attr_as_string(msgs[0], + SYSDB_NAME, NULL); + if (sysdb_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Attribute is missing!\n"); + ret = EFAULT; + goto done; + } + + ret = sss_parse_internal_fqname(tmp_ctx, sysdb_name, + &shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot parse %s, skipping\n", sysdb_name); + continue; + } + + new_users->groups[num_groups] = + talloc_strdup(new_users->groups, shortname); + if (new_users->groups[num_groups] == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, + "Added POSIX group [%s] to rule [%s]\n", + sysdb_name, rule_name); + num_groups++; + } else { + /* If the group still matches the group pattern, + * we can assume it is a non-POSIX group. + */ + ret = get_ipa_groupname(new_users->groups, domain->sysdb, + member_dn, + &new_users->groups[num_groups]); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Added non-POSIX group [%s] to rule [%s]\n", + new_users->groups[num_groups], rule_name); + num_groups++; + } else { + /* Not a group, so we don't care about it */ + DEBUG(SSSDBG_TRACE_FUNC, + "[%s] does not map to either a user or group. " + "Maybe it is an object which is currently not in the " + "cache. Skipping\n", member_dn); + } + } + } + } + new_users->names[num_users] = NULL; + new_users->groups[num_groups] = NULL; + + /* Shrink the arrays down to their real sizes */ + new_users->names = talloc_realloc(new_users, new_users->names, + const char *, num_users + 1); + if (new_users->names == NULL) { + ret = ENOMEM; + goto done; + } + + new_users->groups = talloc_realloc(new_users, new_users->groups, + const char *, num_groups + 1); + if (new_users->groups == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; +done: + if (ret == EOK) { + *users = talloc_steal(mem_ctx, new_users); + } + talloc_free(tmp_ctx); + + return ret; +} diff --git a/src/providers/ipa/ipa_hostid.c b/src/providers/ipa/ipa_hostid.c new file mode 100644 index 0000000..891536f --- /dev/null +++ b/src/providers/ipa/ipa_hostid.c @@ -0,0 +1,30 @@ +/* + Authors: + Hristo Venev <hristo@venev.name> + + Copyright (C) 2017 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "providers/ipa/ipa_common.h" +#include "providers/ldap/sdap_hostid.h" + +errno_t ipa_hostid_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_method *dp_methods) +{ + return sdap_hostid_init(mem_ctx, be_ctx, id_ctx->sdap_id_ctx, dp_methods); +} diff --git a/src/providers/ipa/ipa_hosts.c b/src/providers/ipa/ipa_hosts.c new file mode 100644 index 0000000..e209bca --- /dev/null +++ b/src/providers/ipa/ipa_hosts.c @@ -0,0 +1,365 @@ +/* + SSSD + + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2012 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ipa/ipa_hosts.h" +#include "providers/ipa/ipa_common.h" + +struct ipa_host_state { + struct tevent_context *ev; + struct sdap_handle *sh; + struct sdap_options *opts; + const char **attrs; + struct sdap_attr_map *hostgroup_map; + + struct sdap_search_base **search_bases; + int search_base_iter; + + char *cur_filter; + char *host_filter; + + const char *hostname; + + /* Return values */ + size_t host_count; + struct sysdb_attrs **hosts; + + size_t hostgroup_count; + struct sysdb_attrs **hostgroups; +}; + +static void +ipa_host_info_done(struct tevent_req *subreq); +static void +ipa_hostgroup_info_done(struct tevent_req *subreq); +static errno_t +ipa_hostgroup_info_next(struct tevent_req *req, + struct ipa_host_state *state); + +/** + * hostname == NULL -> look up all hosts / host groups + * hostname != NULL -> look up only given host and groups + * it's member of + * hostgroup_map == NULL -> skip looking up hostgroups + */ +struct tevent_req * +ipa_host_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *hostname, + struct sdap_attr_map *host_map, + struct sdap_attr_map *hostgroup_map, + struct sdap_search_base **search_bases) +{ + struct ipa_host_state *state; + struct tevent_req *req, *subreq; + + req = tevent_req_create(mem_ctx, &state, struct ipa_host_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->sh = sh; + state->opts = opts; + state->hostname = hostname; + state->search_bases = search_bases; + state->search_base_iter = 0; + state->cur_filter = NULL; + state->hostgroup_map = hostgroup_map; + + subreq = sdap_host_info_send(mem_ctx, ev, sh, opts, hostname, host_map, + search_bases); + if (subreq == NULL) { + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, ipa_host_info_done, req); + + return req; +} + +static void +ipa_host_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_host_state *state = + tevent_req_data(req, struct ipa_host_state); + const char *host_dn; + struct sdap_attr_map_info *maps; + const int num_maps = 1; + + ret = sdap_host_info_recv(subreq, state, + &state->host_count, + &state->hosts); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (state->hostgroup_map) { + ret = build_attrs_from_map(state, state->hostgroup_map, + IPA_OPTS_HOSTGROUP, NULL, + &state->attrs, NULL); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* Look up host groups */ + if (state->hostname == NULL) { + talloc_zfree(state->host_filter); + state->host_filter = talloc_asprintf(state, "(objectClass=%s)", + state->hostgroup_map[IPA_OC_HOSTGROUP].name); + if (state->host_filter == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + state->search_base_iter = 0; + + ret = ipa_hostgroup_info_next(req, state); + if (ret == EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "No host search base configured?\n"); + tevent_req_error(req, EINVAL); + return; + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + } else { + ret = sysdb_attrs_get_string(state->hosts[0], SYSDB_ORIG_DN, &host_dn); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (!sdap_has_deref_support_ex(state->sh, state->opts, true)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Server does not support deref\n"); + tevent_req_error(req, EIO); + return; + } + + maps = talloc_array(state, struct sdap_attr_map_info, num_maps + 1); + if (maps == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + maps[0].map = state->hostgroup_map; + maps[0].num_attrs = IPA_OPTS_HOSTGROUP; + maps[1].map = NULL; + + subreq = sdap_deref_search_send(state, state->ev, state->opts, state->sh, + host_dn, + state->hostgroup_map[IPA_AT_HOSTGROUP_MEMBER_OF].name, + state->attrs, + num_maps, maps, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT)); + if (subreq == NULL) { + talloc_free(maps); + DEBUG(SSSDBG_CRIT_FAILURE, "Error requesting host info\n"); + tevent_req_error(req, EIO); + return; + } + tevent_req_set_callback(subreq, ipa_hostgroup_info_done, req); + } + } else { + /* Nothing else to do, just complete the req */ + tevent_req_done(req); + } +} + +static errno_t ipa_hostgroup_info_next(struct tevent_req *req, + struct ipa_host_state *state) +{ + struct sdap_search_base *base; + struct tevent_req *subreq; + + base = state->search_bases[state->search_base_iter]; + if (base == NULL) { + return EOK; + } + + talloc_zfree(state->cur_filter); + state->cur_filter = sdap_combine_filters(state, state->host_filter, + base->filter); + if (state->cur_filter == NULL) { + return ENOMEM; + } + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + base->basedn, base->scope, + state->cur_filter, state->attrs, + state->hostgroup_map, + IPA_OPTS_HOSTGROUP, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error requesting hostgroup info\n"); + talloc_zfree(state->cur_filter); + return EIO; + } + tevent_req_set_callback(subreq, ipa_hostgroup_info_done, req); + + return EAGAIN; +} + +static void +ipa_hostgroup_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_host_state *state = + tevent_req_data(req, struct ipa_host_state); + + size_t hostgroups_total; + size_t hostgroup_count; + struct sysdb_attrs **hostgroups; + struct sdap_deref_attrs **deref_result; + const char *hostgroup_name; + const char *hostgroup_dn; + int i, j; + + if (state->hostname == NULL) { + ret = sdap_get_generic_recv(subreq, state, + &hostgroup_count, + &hostgroups); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_get_generic_recv failed: [%d]\n", ret); + tevent_req_error(req, ret); + return; + } + + /* Merge the two arrays */ + if (hostgroup_count > 0) { + hostgroups_total = hostgroup_count + state->hostgroup_count; + state->hostgroups = talloc_realloc(state, state->hostgroups, + struct sysdb_attrs *, + hostgroups_total); + if (state->hostgroups == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + i = 0; + while(state->hostgroup_count < hostgroups_total) { + state->hostgroups[state->hostgroup_count] = + talloc_steal(state->hostgroups, hostgroups[i]); + state->hostgroup_count++; + i++; + } + } + + /* Now look in the next base */ + state->search_base_iter++; + ret = ipa_hostgroup_info_next(req, state); + if (ret != EOK && ret != EAGAIN) { + tevent_req_error(req, ret); + } + + if (ret != EOK) { + /* Only continue if no error occurred + * and no req was created */ + return; + } + } else { + ret = sdap_deref_search_recv(subreq, state, + &state->hostgroup_count, + &deref_result); + talloc_zfree(subreq); + if (ret != EOK) goto done; + + if (state->hostgroup_count == 0) { + DEBUG(SSSDBG_FUNC_DATA, "No host groups were dereferenced\n"); + } else { + state->hostgroups = talloc_zero_array(state, struct sysdb_attrs *, + state->hostgroup_count); + if (state->hostgroups == NULL) { + ret = ENOMEM; + goto done; + } + + j = 0; + for (i = 0; i < state->hostgroup_count; i++) { + ret = sysdb_attrs_get_string(deref_result[i]->attrs, + SYSDB_ORIG_DN, &hostgroup_dn); + if (ret != EOK) goto done; + + if (!sss_ldap_dn_in_search_bases(state, hostgroup_dn, + state->search_bases, + NULL)) { + continue; + } + + ret = sysdb_attrs_get_string(deref_result[i]->attrs, + state->hostgroup_map[IPA_AT_HOSTGROUP_NAME].sys_name, + &hostgroup_name); + if (ret != EOK) goto done; + + DEBUG(SSSDBG_FUNC_DATA, "Dereferenced host group: %s\n", + hostgroup_name); + state->hostgroups[j] = talloc_steal(state->hostgroups, + deref_result[i]->attrs); + j++; + } + state->hostgroup_count = j; + } + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Error [%d][%s]\n", ret, strerror(ret)); + tevent_req_error(req, ret); + } +} + +errno_t ipa_host_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *host_count, + struct sysdb_attrs ***hosts, + size_t *hostgroup_count, + struct sysdb_attrs ***hostgroups) +{ + struct ipa_host_state *state = + tevent_req_data(req, struct ipa_host_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *host_count = state->host_count; + *hosts = talloc_steal(mem_ctx, state->hosts); + + if (hostgroup_count) *hostgroup_count = state->hostgroup_count; + if (hostgroups) *hostgroups = talloc_steal(mem_ctx, state->hostgroups); + + return EOK; +} diff --git a/src/providers/ipa/ipa_hosts.h b/src/providers/ipa/ipa_hosts.h new file mode 100644 index 0000000..a1ea7a2 --- /dev/null +++ b/src/providers/ipa/ipa_hosts.h @@ -0,0 +1,44 @@ +/* + SSSD + + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2012 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef IPA_HOSTS_H_ +#define IPA_HOSTS_H_ + +struct tevent_req * +ipa_host_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *hostname, + struct sdap_attr_map *host_map, + struct sdap_attr_map *hostgroup_map, + struct sdap_search_base **search_bases); + +errno_t +ipa_host_info_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *host_count, + struct sysdb_attrs ***hosts, + size_t *hostgroup_count, + struct sysdb_attrs ***hostgroups); + +#endif /* IPA_HOSTS_H_ */ diff --git a/src/providers/ipa/ipa_id.c b/src/providers/ipa/ipa_id.c new file mode 100644 index 0000000..fcac56c --- /dev/null +++ b/src/providers/ipa/ipa_id.c @@ -0,0 +1,1562 @@ +/* + SSSD + + IPA Identity Backend Module + + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2011 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <errno.h> + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ipa/ipa_id.h" + +static bool is_object_overridable(struct dp_id_data *ar) +{ + bool ret = false; + + switch (ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: + case BE_REQ_GROUP: + case BE_REQ_INITGROUPS: + case BE_REQ_BY_SECID: + case BE_REQ_USER_AND_GROUP: + case BE_REQ_BY_UUID: + case BE_REQ_BY_CERT: + ret = true; + break; + default: + break; + } + + return ret; +} + +struct ipa_resolve_user_list_state { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct ldb_message_element *users; + const char *domain_name; + struct sss_domain_info *domain; + struct sss_domain_info *user_domain; + size_t user_idx; + + int dp_error; +}; + +static errno_t ipa_resolve_user_list_get_user_step(struct tevent_req *req); +static void ipa_resolve_user_list_get_user_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_resolve_user_list_send(TALLOC_CTX *memctx, struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + const char *domain_name, + struct ldb_message_element *users) +{ + int ret; + struct tevent_req *req; + struct ipa_resolve_user_list_state *state; + + req = tevent_req_create(memctx, &state, + struct ipa_resolve_user_list_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->domain_name = domain_name; + state->domain = find_domain_by_name(state->ipa_ctx->sdap_id_ctx->be->domain, + state->domain_name, true); + state->users = users; + state->user_idx = 0; + state->dp_error = DP_ERR_FATAL; + + ret = ipa_resolve_user_list_get_user_step(req); + if (ret == EAGAIN) { + return req; + } else if (ret == EOK) { + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_resolve_user_list_get_user_step failed.\n"); + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ipa_resolve_user_list_get_user_step(struct tevent_req *req) +{ + int ret; + struct tevent_req *subreq; + struct dp_id_data *ar; + struct ipa_resolve_user_list_state *state = tevent_req_data(req, + struct ipa_resolve_user_list_state); + + if (state->user_idx >= state->users->num_values) { + return EOK; + } + + ret = get_dp_id_data_for_user_name(state, + (char *) state->users->values[state->user_idx].data, + state->domain_name, &ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_user_name failed.\n"); + return ret; + } + + DEBUG(SSSDBG_TRACE_ALL, "Trying to resolve user [%s].\n", ar->filter_value); + + state->user_domain = find_domain_by_object_name_ex( + state->ipa_ctx->sdap_id_ctx->be->domain, + ar->filter_value, true, + SSS_GND_DESCEND); + /* Use provided domain as fallback because no known domain was found in the + * user name. */ + if (state->user_domain == NULL) { + state->user_domain = state->domain; + } + ar->domain = state->user_domain->name; + + if (state->user_domain != state->ipa_ctx->sdap_id_ctx->be->domain) { + subreq = ipa_subdomain_account_send(state, state->ev, state->ipa_ctx, + ar); + } else { + subreq = ipa_id_get_account_info_send(state, state->ev, state->ipa_ctx, + ar); + } + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_handle_acct_req_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_resolve_user_list_get_user_done, req); + + return EAGAIN; +} + +static void ipa_resolve_user_list_get_user_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_resolve_user_list_state *state = tevent_req_data(req, + struct ipa_resolve_user_list_state); + int ret; + + if (state->user_domain != state->ipa_ctx->sdap_id_ctx->be->domain) { + ret = ipa_subdomain_account_recv(subreq, &state->dp_error); + } else { + ret = ipa_id_get_account_info_recv(subreq, &state->dp_error); + } + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_handle_acct request failed: %d\n", ret); + goto done; + } + + state->user_idx++; + + ret = ipa_resolve_user_list_get_user_step(req); + if (ret == EAGAIN) { + return; + } + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_resolve_user_list_get_user_step failed.\n"); + } + +done: + if (ret == EOK) { + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + } else { + if (state->dp_error == DP_ERR_OK) { + state->dp_error = DP_ERR_FATAL; + } + tevent_req_error(req, ret); + } + return; +} + +int ipa_resolve_user_list_recv(struct tevent_req *req, int *dp_error) +{ + struct ipa_resolve_user_list_state *state = tevent_req_data(req, + struct ipa_resolve_user_list_state); + + if (dp_error) { + *dp_error = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_initgr_get_overrides_state { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct sss_domain_info *user_dom; + const char *realm; + + struct ldb_message **groups; + size_t group_count; + const char *groups_id_attr; + size_t group_idx; + struct dp_id_data *ar; + + int dp_error; +}; + +static int ipa_initgr_get_overrides_step(struct tevent_req *req); + +struct tevent_req * +ipa_initgr_get_overrides_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sss_domain_info *user_dom, + size_t groups_count, + struct ldb_message **groups, + const char *groups_id_attr) +{ + int ret; + struct tevent_req *req; + struct ipa_initgr_get_overrides_state *state; + + req = tevent_req_create(memctx, &state, + struct ipa_initgr_get_overrides_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->user_dom = user_dom; + state->groups = groups; + state->group_count = groups_count; + state->group_idx = 0; + state->ar = NULL; + state->realm = dp_opt_get_string(state->ipa_ctx->ipa_options->basic, + IPA_KRB5_REALM); + if (state->realm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No Kerberos realm for IPA?\n"); + ret = EINVAL; + goto done; + } + state->groups_id_attr = talloc_strdup(state, groups_id_attr); + if (state->groups_id_attr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = ipa_initgr_get_overrides_step(req); +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ipa_initgr_get_overrides_override_done(struct tevent_req *subreq); + +static int ipa_initgr_get_overrides_step(struct tevent_req *req) +{ + int ret; + struct tevent_req *subreq; + const char *ipa_uuid; + const char *dn; + struct ipa_initgr_get_overrides_state *state = tevent_req_data(req, + struct ipa_initgr_get_overrides_state); + + for (; state->group_idx < state->group_count; state->group_idx++) { + dn = ldb_dn_get_linearized(state->groups[state->group_idx]->dn); + + DEBUG(SSSDBG_TRACE_LIBS, "Processing group %s (%zu/%zu)\n", + dn, state->group_idx, state->group_count); + + ipa_uuid = ldb_msg_find_attr_as_string(state->groups[state->group_idx], + state->groups_id_attr, NULL); + if (ipa_uuid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "The group %s has no UUID attribute %s, error!\n", + dn, state->groups_id_attr); + continue; + } + + talloc_free(state->ar); /* Avoid spiking memory with many groups */ + + if (strcmp(state->groups_id_attr, SYSDB_UUID) == 0) { + ret = get_dp_id_data_for_uuid(state, ipa_uuid, + state->user_dom->name, &state->ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + return ret; + } + } else if (strcmp(state->groups_id_attr, SYSDB_SID_STR) == 0) { + ret = get_dp_id_data_for_sid(state, ipa_uuid, + state->user_dom->name, &state->ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + return ret; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported groups ID type [%s].\n", + state->groups_id_attr); + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Fetching group %s: %s\n", dn, ipa_uuid); + + subreq = ipa_get_ad_override_send(state, state->ev, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->ipa_options, + state->realm, + state->ipa_ctx->view_name, + state->ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, + ipa_initgr_get_overrides_override_done, req); + return EAGAIN; + } + + return EOK; +} + +static void ipa_initgr_get_overrides_override_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_initgr_get_overrides_state *state = tevent_req_data(req, + struct ipa_initgr_get_overrides_state); + int ret; + struct sysdb_attrs *override_attrs = NULL; + + ret = ipa_get_ad_override_recv(subreq, &state->dp_error, state, + &override_attrs); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); + tevent_req_error(req, ret); + return; + } + + if (is_default_view(state->ipa_ctx->view_name)) { + ret = sysdb_apply_default_override(state->user_dom, override_attrs, + state->groups[state->group_idx]->dn); + } else { + ret = sysdb_store_override(state->user_dom, + state->ipa_ctx->view_name, + SYSDB_MEMBER_GROUP, + override_attrs, + state->groups[state->group_idx]->dn); + } + talloc_free(override_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_store_override failed.\n"); + tevent_req_error(req, ret); + return; + } + + state->group_idx++; + + ret = ipa_initgr_get_overrides_step(req); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int ipa_initgr_get_overrides_recv(struct tevent_req *req, int *dp_error) +{ + struct ipa_initgr_get_overrides_state *state = tevent_req_data(req, + struct ipa_initgr_get_overrides_state); + + if (dp_error) { + *dp_error = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +/* Given a user name, retrieve an array of group UUIDs of groups that have + * no overrideDN attribute but do have an UUID attribute. + */ +static errno_t ipa_id_get_group_uuids(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + size_t *_msgs_count, + struct ldb_message ***_msgs) +{ + const char *filter; + TALLOC_CTX *tmp_ctx; + char **uuid_list = NULL; + errno_t ret; + struct ldb_dn *base_dn; + const char *attrs[] = { SYSDB_UUID, NULL }; + size_t msgs_count; + struct ldb_message **msgs; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + filter = talloc_asprintf(tmp_ctx, + "(&(%s=%s)(!(%s=*))(%s=*))", + SYSDB_OBJECTCATEGORY, + SYSDB_GROUP_CLASS, SYSDB_OVERRIDE_DN, + SYSDB_UUID); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + base_dn = sysdb_base_dn(sysdb, tmp_ctx); + if (base_dn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_entry(tmp_ctx, sysdb, base_dn, + LDB_SCOPE_SUBTREE, filter, attrs, + &msgs_count, &msgs); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, + "No groups without %s in sysdb\n", SYSDB_OVERRIDE_DN); + ret = EOK; + goto done; + } else if (ret != EOK) { + goto done; + } + + uuid_list = talloc_zero_array(tmp_ctx, char *, msgs_count); + if (uuid_list == NULL) { + goto done; + } + + *_msgs_count = msgs_count; + *_msgs = talloc_steal(mem_ctx, msgs); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +struct ipa_id_get_account_info_state { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct sdap_id_ctx *ctx; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct dp_id_data *ar; + struct dp_id_data *orig_ar; + const char *realm; + + struct sysdb_attrs *override_attrs; + struct ldb_message *obj_msg; + struct ldb_message_element *ghosts; + + struct ldb_message **user_groups; + size_t group_cnt; + size_t group_idx; + + struct ldb_result *res; + size_t res_index; + int dp_error; +}; + +static void ipa_id_get_account_info_connected(struct tevent_req *subreq); +static void ipa_id_get_account_info_got_override(struct tevent_req *subreq); +static errno_t ipa_id_get_account_info_get_original_step(struct tevent_req *req, + struct dp_id_data *ar); +static void ipa_id_get_account_info_orig_done(struct tevent_req *subreq); +static void ipa_id_get_account_info_done(struct tevent_req *subreq); +static void ipa_id_get_user_list_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_id_get_account_info_send(TALLOC_CTX *memctx, struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct dp_id_data *ar) +{ + int ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct ipa_id_get_account_info_state *state; + + req = tevent_req_create(memctx, &state, + struct ipa_id_get_account_info_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->ctx = ipa_ctx->sdap_id_ctx; + state->dp_error = DP_ERR_FATAL; + + state->op = sdap_id_op_create(state, state->ctx->conn->conn_cache); + if (state->op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed.\n"); + ret = ENOMEM; + goto fail; + } + + state->domain = find_domain_by_name(state->ctx->be->domain, + ar->domain, true); + if (state->domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n"); + ret = ENOMEM; + goto fail; + } + state->sysdb = state->domain->sysdb; + state->ar = ar; + state->realm = dp_opt_get_string(state->ipa_ctx->ipa_options->basic, + IPA_KRB5_REALM); + if (state->realm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No Kerberos realm for IPA?\n"); + ret = EINVAL; + goto fail; + } + + /* We can skip the override lookup and go directly to the original object + * if + * - the lookup is by SID + * - there is no view set of it is the default view + * - if the EXTRA_INPUT_MAYBE_WITH_VIEW flag is not set + */ + if (is_default_view(state->ipa_ctx->view_name) + || state->ar->filter_type == BE_FILTER_SECID + || state->ar->extra_value == NULL + || strcmp(state->ar->extra_value, + EXTRA_INPUT_MAYBE_WITH_VIEW) != 0 + || ! is_object_overridable(state->ar)) { + ret = ipa_id_get_account_info_get_original_step(req, ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_subdomain_account_get_original_step failed.\n"); + goto fail; + } + } else { + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed.\n"); + goto fail; + } + tevent_req_set_callback(subreq, ipa_id_get_account_info_connected, req); + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ipa_id_get_account_info_connected(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect request failed.\n"); + goto fail; + } + + subreq = ipa_get_ad_override_send(state, state->ev, state->ctx, + state->ipa_ctx->ipa_options, state->realm, + state->ipa_ctx->view_name, state->ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, ipa_id_get_account_info_got_override, req); + + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +static void ipa_id_get_account_info_got_override(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + int dp_error = DP_ERR_FATAL; + int ret; + const char *anchor = NULL; + char *anchor_domain; + char *ipa_uuid; + + ret = ipa_get_ad_override_recv(subreq, &dp_error, state, + &state->override_attrs); + talloc_zfree(subreq); + + if (ret != EOK) { + ret = sdap_id_op_done(state->op, ret, &dp_error); + + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed.\n"); + goto fail; + } + tevent_req_set_callback(subreq, ipa_id_get_account_info_connected, + req); + return; + } + + DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); + goto fail; + } + + if (state->override_attrs != NULL) { + ret = sysdb_attrs_get_string(state->override_attrs, + SYSDB_OVERRIDE_ANCHOR_UUID, + &anchor); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto fail; + } + + ret = split_ipa_anchor(state, anchor, &anchor_domain, &ipa_uuid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupported override anchor [%s].\n", anchor); + ret = EINVAL; + goto fail; + } + + if (strcmp(state->ar->domain, anchor_domain) == 0) { + + state->orig_ar = state->ar; + + ret = get_dp_id_data_for_uuid(state, ipa_uuid, + state->ar->domain, + &state->ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_uuid failed.\n"); + goto fail; + } + + if ((state->orig_ar->entry_type & BE_REQ_TYPE_MASK) + == BE_REQ_INITGROUPS) { + DEBUG(SSSDBG_TRACE_ALL, + "Switching back to BE_REQ_INITGROUPS.\n"); + state->ar->entry_type = BE_REQ_INITGROUPS; + state->ar->filter_type = BE_FILTER_UUID; + } + + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Anchor from a different domain [%s], expected [%s]. " \ + "This is currently not supported, continue lookup in " \ + "local IPA domain.\n", + anchor_domain, state->ar->domain); + } + } + + ret = ipa_id_get_account_info_get_original_step(req, state->ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_subdomain_account_get_original_step failed.\n"); + goto fail; + } + + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +static errno_t ipa_id_get_account_info_get_original_step(struct tevent_req *req, + struct dp_id_data *ar) +{ + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + struct tevent_req *subreq; + +#ifdef BUILD_SUBID + if ((ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_SUBID_RANGES) { + if (!state->ctx->opts->sdom->subid_ranges_search_bases || + !state->ctx->opts->sdom->subid_ranges_search_bases[0] || + !state->ctx->opts->sdom->subid_ranges_search_bases[0]->basedn) { + DEBUG(SSSDBG_OP_FAILURE, "subid_ranges_search_bases isn't set\n"); + return EINVAL; + } + ar->extra_value = talloc_asprintf(ar, + "%s=%s,"SYSDB_USERS_CONTAINER",%s", + state->ctx->opts->user_map[SDAP_AT_USER_NAME].name, + ar->filter_value, + state->ctx->opts->sdom->user_search_bases[0]->basedn); + } +#endif + + subreq = sdap_handle_acct_req_send(state, state->ctx->be, ar, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->sdap_id_ctx->opts->sdom, + state->ipa_ctx->sdap_id_ctx->conn, true); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_handle_acct_req_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_id_get_account_info_orig_done, req); + + return EOK; +} + +static int ipa_id_get_account_info_post_proc_step(struct tevent_req *req); +static void ipa_id_get_user_groups_done(struct tevent_req *subreq); + +static void ipa_id_get_account_info_orig_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + int dp_error = DP_ERR_FATAL; + int ret; + const char *attrs[] = { SYSDB_NAME, + SYSDB_UIDNUM, + SYSDB_SID_STR, + SYSDB_OBJECTCATEGORY, + SYSDB_UUID, + SYSDB_GHOST, + SYSDB_HOMEDIR, + NULL }; + + ret = sdap_handle_acct_req_recv(subreq, &dp_error, NULL, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_handle_acct request failed: %d\n", ret); + goto fail; + } + + if (! is_object_overridable(state->ar)) { + DEBUG(SSSDBG_FUNC_DATA, "Object not overridable, ending request\n"); + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + } + + /* Lookups by certificate can return muliple results and need special + * handling because get_object_from_cache() expects a unique match */ + state->res = NULL; + state->res_index = 0; + if (state->ar->filter_type == BE_FILTER_CERT) { + ret = sysdb_search_object_by_cert(state, state->domain, + state->ar->filter_value, attrs, + &(state->res)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to make request to our cache: [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto fail; + } + if (state->res->count == 0) { + DEBUG(SSSDBG_OP_FAILURE, "Object not found in our cache.\n"); + ret = ENOENT; + goto fail; + } + + state->obj_msg = state->res->msgs[0]; + if (state->res->count == 1) { + /* Just process the unique result, no need to iterate */ + state->res = NULL; + } + } else { + ret = get_object_from_cache(state, state->domain, state->ar, + &state->obj_msg); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, "Object not found, ending request\n"); + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_object_from_cache failed.\n"); + goto fail; + } + } + + ret = ipa_id_get_account_info_post_proc_step(req); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_id_get_account_info_post_proc_step failed.\n"); + goto fail; + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +static int ipa_id_get_account_info_post_proc_step(struct tevent_req *req) +{ + int ret; + const char *uuid; + const char *class; + enum sysdb_member_type type; + struct tevent_req *subreq; + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + + class = ldb_msg_find_attr_as_string(state->obj_msg, SYSDB_OBJECTCATEGORY, + NULL); + if (class == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot find an objectclass.\n"); + ret = EINVAL; + goto done; + } + + + if (!is_default_view(state->ipa_ctx->view_name)) { + + if ((state->ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_GROUP + || ((state->ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_BY_UUID + && strcmp(class, SYSDB_GROUP_CLASS) == 0)) { + /* check for ghost members because ghost members are not allowed + * if a view other than the default view is applied.*/ + state->ghosts = ldb_msg_find_element(state->obj_msg, SYSDB_GHOST); + } else if ((state->ar->entry_type & BE_REQ_TYPE_MASK) == \ + BE_REQ_INITGROUPS) { + /* Get UUID list of groups that have no overrideDN set. */ + ret = ipa_id_get_group_uuids(state, state->sysdb, + &state->group_cnt, + &state->user_groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get UUID list: %d\n", ret); + goto done; + } + } + } + + + if (state->override_attrs == NULL) { + uuid = ldb_msg_find_attr_as_string(state->obj_msg, SYSDB_UUID, NULL); + if (uuid == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot find a UUID.\n"); + ret = EINVAL; + goto done; + } + + ret = get_dp_id_data_for_uuid(state, uuid, state->domain->name, + &state->ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + goto done; + } + + subreq = ipa_get_ad_override_send(state, state->ev, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->ipa_options, + state->realm, + state->ipa_ctx->view_name, + state->ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_id_get_account_info_done, req); + ret = EAGAIN; + goto done; + } else { + if (strcmp(class, SYSDB_USER_CLASS) == 0) { + type = SYSDB_MEMBER_USER; + } else { + type = SYSDB_MEMBER_GROUP; + } + + ret = sysdb_store_override(state->domain, state->ipa_ctx->view_name, + type, + state->override_attrs, state->obj_msg->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_store_override failed.\n"); + goto done; + } + } + + if (state->ghosts != NULL) { + /* Resolve ghost members */ + subreq = ipa_resolve_user_list_send(state, state->ev, + state->ipa_ctx, + state->domain->name, + state->ghosts); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_resolve_user_list_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_id_get_user_list_done, req); + ret = EAGAIN; + goto done; + } + + if (state->user_groups != NULL) { + subreq = ipa_initgr_get_overrides_send(state, state->ev, state->ipa_ctx, + state->domain, state->group_cnt, + state->user_groups, + SYSDB_UUID); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_resolve_user_list_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_id_get_user_groups_done, req); + ret = EAGAIN; + goto done; + } + + ret = EOK; + +done: + if (ret == EOK && state->res != NULL + && ++state->res_index < state->res->count) { + state->obj_msg = state->res->msgs[state->res_index]; + ret = ipa_id_get_account_info_post_proc_step(req); + } + + return ret; +} + +static void ipa_id_get_account_info_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + int dp_error = DP_ERR_FATAL; + int ret; + const char *class; + enum sysdb_member_type type; + + ret = ipa_get_ad_override_recv(subreq, &dp_error, state, + &state->override_attrs); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); + goto fail; + } + + class = ldb_msg_find_attr_as_string(state->obj_msg, SYSDB_OBJECTCATEGORY, + NULL); + if (class == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot find an objectclass.\n"); + ret = EINVAL; + goto fail; + } + + if (strcmp(class, SYSDB_USER_CLASS) == 0) { + type = SYSDB_MEMBER_USER; + } else { + type = SYSDB_MEMBER_GROUP; + } + + ret = sysdb_store_override(state->domain, state->ipa_ctx->view_name, + type, + state->override_attrs, state->obj_msg->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_store_override failed.\n"); + goto fail; + } + + if (state->ghosts != NULL) { + /* Resolve ghost members */ + subreq = ipa_resolve_user_list_send(state, state->ev, + state->ipa_ctx, + state->domain->name, + state->ghosts); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_resolve_user_list_send failed.\n"); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, ipa_id_get_user_list_done, req); + return; + } + + if (state->user_groups != NULL) { + subreq = ipa_initgr_get_overrides_send(state, state->ev, state->ipa_ctx, + state->domain, state->group_cnt, + state->user_groups, + SYSDB_UUID); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_resolve_user_list_send failed.\n"); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, ipa_id_get_user_groups_done, req); + return; + } + + if (state->res != NULL && ++state->res_index < state->res->count) { + state->obj_msg = state->res->msgs[state->res_index]; + ret = ipa_id_get_account_info_post_proc_step(req); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_id_get_account_info_post_proc_step failed.\n"); + goto fail; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +static void ipa_id_get_user_list_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = ipa_resolve_user_list_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA resolve user list %d\n", ret); + goto fail; + } + + if (state->res != NULL && ++state->res_index < state->res->count) { + state->obj_msg = state->res->msgs[state->res_index]; + ret = ipa_id_get_account_info_post_proc_step(req); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_id_get_account_info_post_proc_step failed.\n"); + goto fail; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +static void ipa_id_get_user_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = ipa_initgr_get_overrides_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA resolve user groups %d\n", ret); + goto fail; + } + + if (state->res != NULL && ++state->res_index < state->res->count) { + state->obj_msg = state->res->msgs[state->res_index]; + ret = ipa_id_get_account_info_post_proc_step(req); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_id_get_account_info_post_proc_step failed.\n"); + goto fail; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +int ipa_id_get_account_info_recv(struct tevent_req *req, int *dp_error) +{ + struct ipa_id_get_account_info_state *state = tevent_req_data(req, + struct ipa_id_get_account_info_state); + + if (dp_error) { + *dp_error = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/* Request for netgroups + * - first start here and then go to ipa_netgroups.c + */ +struct ipa_id_get_netgroup_state { + struct tevent_context *ev; + struct ipa_id_ctx *ctx; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + + const char *name; + int timeout; + + char *filter; + const char **attrs; + + size_t count; + struct sysdb_attrs **netgroups; + + int dp_error; +}; + +static void ipa_id_get_netgroup_connected(struct tevent_req *subreq); +static void ipa_id_get_netgroup_done(struct tevent_req *subreq); + +static struct tevent_req *ipa_id_get_netgroup_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + const char *name) +{ + struct tevent_req *req; + struct ipa_id_get_netgroup_state *state; + struct tevent_req *subreq; + struct sdap_id_ctx *ctx; + char *clean_name; + int ret; + + ctx = ipa_ctx->sdap_id_ctx; + + req = tevent_req_create(memctx, &state, struct ipa_id_get_netgroup_state); + if (!req) return NULL; + + state->ev = ev; + state->ctx = ipa_ctx; + state->dp_error = DP_ERR_FATAL; + + state->op = sdap_id_op_create(state, ctx->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + state->sysdb = ctx->be->domain->sysdb; + state->domain = ctx->be->domain; + state->name = name; + state->timeout = dp_opt_get_int(ctx->opts->basic, SDAP_SEARCH_TIMEOUT); + + ret = sss_filter_sanitize(state, name, &clean_name); + if (ret != EOK) { + goto fail; + } + + state->filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))", + ctx->opts->netgroup_map[IPA_AT_NETGROUP_NAME].name, + clean_name, + ctx->opts->netgroup_map[IPA_OC_NETGROUP].name); + if (!state->filter) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n"); + ret = ENOMEM; + goto fail; + } + talloc_zfree(clean_name); + + ret = build_attrs_from_map(state, ctx->opts->netgroup_map, + IPA_OPTS_NETGROUP, NULL, + &state->attrs, NULL); + if (ret != EOK) goto fail; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + goto fail; + } + tevent_req_set_callback(subreq, ipa_id_get_netgroup_connected, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ipa_id_get_netgroup_connected(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_id_get_netgroup_state *state = + tevent_req_data(req, struct ipa_id_get_netgroup_state); + int dp_error = DP_ERR_FATAL; + int ret; + struct sdap_id_ctx *sdap_ctx = state->ctx->sdap_id_ctx; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + subreq = ipa_get_netgroups_send(state, state->ev, state->sysdb, + state->domain, sdap_ctx->opts, + state->ctx->ipa_options, + sdap_id_op_handle(state->op), + state->attrs, state->filter, + state->timeout); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ipa_id_get_netgroup_done, req); + + return; +} + +static void ipa_id_get_netgroup_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_id_get_netgroup_state *state = + tevent_req_data(req, struct ipa_id_get_netgroup_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = ipa_get_netgroups_recv(subreq, state, + &state->count, &state->netgroups); + talloc_zfree(subreq); + ret = sdap_id_op_done(state->op, ret, &dp_error); + + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + tevent_req_error(req, ret); + return; + } + tevent_req_set_callback(subreq, ipa_id_get_netgroup_connected, req); + return; + } + + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (ret == EOK && state->count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Found more than one netgroup with the name [%s].\n", + state->name); + tevent_req_error(req, EINVAL); + return; + } + + if (ret == ENOENT) { + ret = sysdb_delete_netgroup(state->domain, state->name); + if (ret != EOK && ret != ENOENT) { + tevent_req_error(req, ret); + return; + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; +} + +static int ipa_id_get_netgroup_recv(struct tevent_req *req, int *dp_error) +{ + struct ipa_id_get_netgroup_state *state = + tevent_req_data(req, struct ipa_id_get_netgroup_state); + + if (dp_error) { + *dp_error = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +enum ipa_account_info_type { + IPA_ACCOUNT_INFO_SUBDOMAIN, + IPA_ACCOUNT_INFO_NETGROUP, + IPA_ACCOUNT_INFO_OTHER +}; + +static enum ipa_account_info_type +ipa_decide_account_info_type(struct dp_id_data *data, struct be_ctx *be_ctx) +{ + if (strcasecmp(data->domain, be_ctx->domain->name) != 0) { + return IPA_ACCOUNT_INFO_SUBDOMAIN; + } else if ((data->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_NETGROUP) { + return IPA_ACCOUNT_INFO_NETGROUP; + } + + return IPA_ACCOUNT_INFO_OTHER; +} + +struct ipa_account_info_state { + enum ipa_account_info_type type; + + const char *err_msg; + int dp_error; +}; + +static void ipa_account_info_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_account_info_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_id_data *data) +{ + struct ipa_account_info_state *state = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_account_info_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->type = ipa_decide_account_info_type(data, be_ctx); + + switch (state->type) { + case IPA_ACCOUNT_INFO_SUBDOMAIN: + /* Subdomain lookups are handled differently on server and client. */ + subreq = ipa_subdomain_account_send(state, be_ctx->ev, id_ctx, data); + break; + case IPA_ACCOUNT_INFO_NETGROUP: + if (data->filter_type != BE_FILTER_NAME) { + ret = EINVAL; + goto immediately; + } + + subreq = ipa_id_get_netgroup_send(state, be_ctx->ev, id_ctx, + data->filter_value); + break; + case IPA_ACCOUNT_INFO_OTHER: + subreq = ipa_id_get_account_info_send(state, be_ctx->ev, id_ctx, data); + break; + } + + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, ipa_account_info_done, req); + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, be_ctx->ev); + return req; +} + +static void ipa_account_info_done(struct tevent_req *subreq) +{ + struct ipa_account_info_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_account_info_state); + + switch (state->type) { + case IPA_ACCOUNT_INFO_SUBDOMAIN: + ret = ipa_subdomain_account_recv(subreq, &state->dp_error); + break; + case IPA_ACCOUNT_INFO_NETGROUP: + ret = ipa_id_get_netgroup_recv(subreq, &state->dp_error); + break; + case IPA_ACCOUNT_INFO_OTHER: + ret = ipa_id_get_account_info_recv(subreq, &state->dp_error); + break; + default: + ret = EINVAL; + break; + } + talloc_zfree(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t ipa_account_info_recv(struct tevent_req *req, + int *_dp_error) +{ + struct ipa_account_info_state *state = NULL; + + state = tevent_req_data(req, struct ipa_account_info_state); + + /* Fail the request after collecting the dp_error */ + if (_dp_error) { + *_dp_error = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct ipa_account_info_handler_state { + struct dp_reply_std reply; +}; + +static void ipa_account_info_handler_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params) +{ + struct ipa_account_info_handler_state *state; + struct tevent_req *subreq = NULL; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_account_info_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (sdap_is_enum_request(data)) { + DEBUG(SSSDBG_TRACE_LIBS, "Skipping enumeration on demand\n"); + ret = EOK; + goto immediately; + } + + subreq = ipa_account_info_send(state, params->be_ctx, id_ctx, data); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, ipa_account_info_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ipa_account_info_handler_done(struct tevent_req *subreq) +{ + struct ipa_account_info_handler_state *state; + struct tevent_req *req; + int dp_error; + errno_t ret = ERR_INTERNAL; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_account_info_handler_state); + + ret = ipa_account_info_recv(subreq, &dp_error); + talloc_zfree(subreq); + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, NULL); + tevent_req_done(req); +} + +errno_t ipa_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct ipa_account_info_handler_state *state = NULL; + + state = tevent_req_data(req, struct ipa_account_info_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/ipa/ipa_id.h b/src/providers/ipa/ipa_id.h new file mode 100644 index 0000000..c18e709 --- /dev/null +++ b/src/providers/ipa/ipa_id.h @@ -0,0 +1,159 @@ +/* + SSSD + + IPA Identity Backend Module + + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2011 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + + +#ifndef _IPA_ID_H_ +#define _IPA_ID_H_ + +#include "providers/ldap/ldap_common.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ldap/sdap.h" +#include "providers/ipa/ipa_subdomains.h" + +#define IPA_DEFAULT_VIEW_NAME "Default Trust View" + +struct tevent_req * +ipa_account_info_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_id_data *data); +errno_t ipa_account_info_recv(struct tevent_req *req, + int *_dp_error); + +struct tevent_req * +ipa_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params); + +errno_t ipa_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +struct tevent_req *ipa_get_netgroups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct ipa_options *ipa_options, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout); + +int ipa_get_netgroups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sysdb_attrs ***reply); + +struct tevent_req *ipa_s2n_get_acct_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *override_attrs, + struct sdap_handle *sh, + int entry_type, + struct req_input *req_input); +int ipa_s2n_get_acct_info_recv(struct tevent_req *req); + +struct tevent_req *ipa_get_subdom_acct_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sysdb_attrs *override_attrs, + struct dp_id_data *ar); +int ipa_get_subdom_acct_recv(struct tevent_req *req, int *dp_error_out); + +errno_t get_dp_id_data_for_sid(TALLOC_CTX *mem_ctx, const char *sid, + const char *domain_name, + struct dp_id_data **_ar); + +errno_t get_dp_id_data_for_uuid(TALLOC_CTX *mem_ctx, const char *uuid, + const char *domain_name, + struct dp_id_data **_ar); + +errno_t get_dp_id_data_for_user_name(TALLOC_CTX *mem_ctx, + const char *user_name, + const char *domain_name, + struct dp_id_data **_ar); + +struct tevent_req *ipa_get_ad_override_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *sdap_id_ctx, + struct ipa_options *ipa_options, + const char *ipa_realm, + const char *view_name, + struct dp_id_data *ar); + +errno_t ipa_get_ad_override_recv(struct tevent_req *req, int *dp_error_out, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **override_attrs); + +struct tevent_req *ipa_subdomain_account_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct dp_id_data *ar); + +errno_t ipa_subdomain_account_recv(struct tevent_req *req, int *dp_error_out); + +errno_t split_ipa_anchor(TALLOC_CTX *mem_ctx, const char *anchor, + char **_anchor_domain, char **_ipa_uuid); + +errno_t get_object_from_cache(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + struct dp_id_data *ar, + struct ldb_message **_msg); + +struct tevent_req * +ipa_initgr_get_overrides_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sss_domain_info *user_dom, + size_t groups_count, + struct ldb_message **groups, + const char *groups_id_attr); +int ipa_initgr_get_overrides_recv(struct tevent_req *req, int *dp_error); + +struct tevent_req *ipa_get_subdom_acct_process_pac_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct ipa_id_ctx *ipa_ctx, + struct sss_domain_info *dom, + struct ldb_message *user_msg); + +errno_t ipa_get_subdom_acct_process_pac_recv(struct tevent_req *req); + +struct tevent_req * +ipa_resolve_user_list_send(TALLOC_CTX *memctx, struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + const char *domain_name, + struct ldb_message_element *users); +int ipa_resolve_user_list_recv(struct tevent_req *req, int *dp_error); + +struct tevent_req * +ipa_id_get_account_info_send(TALLOC_CTX *memctx, struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct dp_id_data *ar); +int ipa_id_get_account_info_recv(struct tevent_req *req, int *dp_error); +#endif diff --git a/src/providers/ipa/ipa_idmap.c b/src/providers/ipa/ipa_idmap.c new file mode 100644 index 0000000..5d8d56b --- /dev/null +++ b/src/providers/ipa/ipa_idmap.c @@ -0,0 +1,521 @@ +/* + SSSD + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2013 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + + +#include "util/util.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ipa/ipa_common.h" +#include "util/util_sss_idmap.h" + +static errno_t ipa_idmap_check_posix_child(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid_str, + size_t range_count, + struct range_info **range_list) +{ + bool has_algorithmic_mapping; + enum idmap_error_code err; + struct sss_domain_info *dom; + struct sss_domain_info *forest_root; + size_t c; + struct sss_idmap_range range; + struct range_info *r; + char *range_id; + TALLOC_CTX *tmp_ctx; + bool found = false; + int ret; + + err = sss_idmap_domain_has_algorithmic_mapping(idmap_ctx->map, dom_sid_str, + &has_algorithmic_mapping); + if (err == IDMAP_SUCCESS) { + DEBUG(SSSDBG_TRACE_ALL, + "Idmap of domain [%s] already known, nothing to do.\n", + dom_sid_str); + return EOK; + } else { + err = sss_idmap_domain_by_name_has_algorithmic_mapping(idmap_ctx->map, + dom_name, + &has_algorithmic_mapping); + if (err == IDMAP_SUCCESS) { + DEBUG(SSSDBG_TRACE_ALL, + "Idmap of domain [%s] already known, nothing to do.\n", + dom_sid_str); + return EOK; + } + } + DEBUG(SSSDBG_TRACE_ALL, "Trying to add idmap for domain [%s].\n", + dom_sid_str); + + if (err != IDMAP_SID_UNKNOWN && err != IDMAP_NAME_UNKNOWN) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_idmap_domain_has_algorithmic_mapping failed.\n"); + return EINVAL; + } + + dom = find_domain_by_sid(idmap_ctx->id_ctx->be->domain, dom_sid_str); + if (dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "find_domain_by_sid failed with SID [%s].\n", dom_sid_str); + return EINVAL; + } + + if (dom->forest == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "No forest available for domain [%s].\n", + dom_sid_str); + return EINVAL; + } + + forest_root = find_domain_by_name(idmap_ctx->id_ctx->be->domain, + dom->forest, true); + if (forest_root == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "find_domain_by_name failed to find forest root [%s].\n", + dom->forest); + return ENOENT; + } + + if (forest_root->domain_id == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Forest root [%s] does not have a SID.\n", + dom->forest); + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + for (c = 0; c < range_count; c++) { + r = range_list[c]; + if (r->trusted_dom_sid != NULL + && strcmp(r->trusted_dom_sid, forest_root->domain_id) == 0) { + + if (r->range_type == NULL + || strcmp(r->range_type, IPA_RANGE_AD_TRUST_POSIX) != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Forest root does not have range type [%s].\n", + IPA_RANGE_AD_TRUST_POSIX); + ret = EINVAL; + goto done; + } + + range.min = r->base_id; + range.max = r->base_id + r->id_range_size -1; + range_id = talloc_asprintf(tmp_ctx, "%s-%s", dom_sid_str, r->name); + if (range_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + err = sss_idmap_add_domain_ex(idmap_ctx->map, dom_name, dom_sid_str, + &range, range_id, 0, true); + if (err != IDMAP_SUCCESS && err != IDMAP_COLLISION) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not add range [%s] to ID map\n", range_id); + ret = EIO; + goto done; + } + + found = true; + } + } + + if (!found) { + DEBUG(SSSDBG_MINOR_FAILURE, "No idrange found for forest root [%s].\n", + forest_root->domain_id); + ret = ENOENT; + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t get_idmap_data_from_range(struct range_info *r, char *domain_name, + char **_name, char **_sid, uint32_t *_rid, + struct sss_idmap_range *_range, + bool *_external_mapping) +{ + if (r->range_type == NULL) { + /* Older IPA servers might not have the range_type attribute, but + * only support local ranges and trusts with algorithmic mapping. */ + + if (r->trusted_dom_sid == NULL && r->secondary_base_rid != 0) { + /* local IPA domain */ + *_rid = 0; + *_external_mapping = true; + *_name = domain_name; + *_sid = NULL; + } else if (r->trusted_dom_sid != NULL + && r->secondary_base_rid == 0) { + /* trusted domain */ + *_rid = r->base_rid; + *_external_mapping = false; + *_name = r->trusted_dom_sid; + *_sid = r->trusted_dom_sid; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot determine range type, " \ + "for id range [%s].\n", + r->name); + return EINVAL; + } + } else { + if (strcmp(r->range_type, IPA_RANGE_LOCAL) == 0) { + *_rid = 0; + *_external_mapping = true; + *_name = domain_name; + *_sid = NULL; + } else if (strcmp(r->range_type, IPA_RANGE_AD_TRUST_POSIX) == 0) { + *_rid = 0; + *_external_mapping = true; + *_name = r->trusted_dom_sid; + *_sid = r->trusted_dom_sid; + } else if (strcmp(r->range_type, IPA_RANGE_AD_TRUST) == 0) { + *_rid = r->base_rid; + *_external_mapping = false; + *_name = r->trusted_dom_sid; + *_sid = r->trusted_dom_sid; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Range type [%s] of id range " \ + "[%s] not supported.\n", \ + r->range_type, r->name); + return ERR_UNSUPPORTED_RANGE_TYPE; + } + } + + _range->min = r->base_id; + _range->max = r->base_id + r->id_range_size -1; + + return EOK; +} + +errno_t ipa_ranges_parse_results(TALLOC_CTX *mem_ctx, + char *domain_name, + size_t count, + struct sysdb_attrs **reply, + struct range_info ***_range_list) +{ + struct range_info **range_list = NULL; + struct range_info *r; + const char *value; + size_t c; + size_t rc = 0; + size_t d; + int ret; + enum idmap_error_code err; + char *name1; + char *name2; + char *sid1; + char *sid2; + uint32_t rid1; + uint32_t rid2; + struct sss_idmap_range range1; + struct sss_idmap_range range2; + bool mapping1; + bool mapping2; + + range_list = talloc_array(mem_ctx, struct range_info *, count + 1); + if (range_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + return ENOMEM; + } + + for (c = 0; c < count; c++) { + r = talloc_zero(range_list, struct range_info); + if (r == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(reply[c], IPA_CN, &value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + r->name = talloc_strdup(r, value); + if (r->name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(reply[c], IPA_TRUSTED_DOMAIN_SID, &value); + if (ret == EOK) { + r->trusted_dom_sid = talloc_strdup(r, value); + if (r->trusted_dom_sid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } else if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_uint32_t(reply[c], IPA_BASE_ID, + &r->base_id); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_uint32_t(reply[c], IPA_ID_RANGE_SIZE, + &r->id_range_size); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_uint32_t(reply[c], IPA_BASE_RID, + &r->base_rid); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_uint32_t(reply[c], IPA_SECONDARY_BASE_RID, + &r->secondary_base_rid); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_string(reply[c], IPA_RANGE_TYPE, &value); + if (ret == EOK) { + r->range_type = talloc_strdup(r, value); + if (r->range_type == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } else if (ret == ENOENT) { + /* Older IPA servers might not have the range_type attribute, but + * only support local ranges and trusts with algorithmic mapping. */ + if (r->trusted_dom_sid == NULL) { + r->range_type = talloc_strdup(r, IPA_RANGE_LOCAL); + } else { + r->range_type = talloc_strdup(r, IPA_RANGE_AD_TRUST); + } + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + if (r->range_type == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(reply[c], IPA_ID_RANGE_MPG, &value); + if (ret == EOK) { + r->mpg_mode = str_to_domain_mpg_mode(value); + } else if (ret == ENOENT) { + r->mpg_mode = MPG_DEFAULT; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = get_idmap_data_from_range(r, domain_name, &name1, &sid1, &rid1, + &range1, &mapping1); + if (ret == ERR_UNSUPPORTED_RANGE_TYPE) { + talloc_free(r); + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_idmap_data_from_range failed.\n"); + goto done; + } + for (d = 0; d < rc; d++) { + ret = get_idmap_data_from_range(range_list[d], domain_name, &name2, + &sid2, &rid2, &range2, &mapping2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "get_idmap_data_from_range failed.\n"); + goto done; + } + + err = sss_idmap_check_collision_ex(name1, sid1, &range1, rid1, + r->name, mapping1, + name2, sid2, &range2, rid2, + range_list[d]->name, mapping2); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Collision of ranges [%s] and [%s] detected.\n", + r->name, range_list[d]->name); + ret = EINVAL; + goto done; + } + } + + range_list[rc++] = r; + } + + range_list[rc] = NULL; + + *_range_list = range_list; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(range_list); + } + + return ret; +} + +errno_t ipa_idmap_get_ranges_from_sysdb(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid_str, + bool allow_collisions) +{ + int ret; + size_t range_count; + struct range_info **range_list; + TALLOC_CTX *tmp_ctx; + size_t c; + enum idmap_error_code err; + struct sss_idmap_range range; + uint32_t rid; + bool external_mapping; + char *name; + char *sid; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + ret = sysdb_get_ranges(tmp_ctx, idmap_ctx->id_ctx->be->domain->sysdb, + &range_count, &range_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_ranges failed.\n"); + goto done; + } + + for (c = 0; c < range_count; c++) { + ret = get_idmap_data_from_range(range_list[c], + idmap_ctx->id_ctx->be->domain->name, + &name, &sid, &rid, &range, + &external_mapping); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_idmap_data_from_range failed for " \ + "id range [%s], skipping.\n", + range_list[c]->name); + continue; + } + + err = sss_idmap_add_domain_ex(idmap_ctx->map, name, sid, &range, + range_list[c]->name, rid, + external_mapping); + if (err != IDMAP_SUCCESS) { + if (!allow_collisions || err != IDMAP_COLLISION) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not add range [%s] to ID map\n", + range_list[c]->name); + ret = EIO; + goto done; + } + } + } + + if (dom_name != NULL || dom_sid_str != NULL) { + ret = ipa_idmap_check_posix_child(idmap_ctx, dom_name, dom_sid_str, + range_count, range_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_idmap_check_posix_child failed.\n"); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t ipa_idmap_find_new_domain(struct sdap_idmap_ctx *idmap_ctx, + const char *dom_name, + const char *dom_sid_str) +{ + return ipa_idmap_get_ranges_from_sysdb(idmap_ctx, dom_name, dom_sid_str, + true); +} + +errno_t ipa_idmap_init(TALLOC_CTX *mem_ctx, + struct sdap_id_ctx *id_ctx, + struct sdap_idmap_ctx **_idmap_ctx) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + enum idmap_error_code err; + struct sdap_idmap_ctx *idmap_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + idmap_ctx = talloc_zero(tmp_ctx, struct sdap_idmap_ctx); + if (!idmap_ctx) { + ret = ENOMEM; + goto done; + } + idmap_ctx->id_ctx = id_ctx; + idmap_ctx->find_new_domain = ipa_idmap_find_new_domain; + + /* Initialize the map */ + err = sss_idmap_init(sss_idmap_talloc, idmap_ctx, + sss_idmap_talloc_free, + &idmap_ctx->map); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not initialize the ID map: [%s]\n", + idmap_error_string(err)); + if (err == IDMAP_OUT_OF_MEMORY) { + ret = ENOMEM; + } else { + ret = EINVAL; + } + goto done; + } + + ret = ipa_idmap_get_ranges_from_sysdb(idmap_ctx, NULL, NULL, false); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_idmap_get_ranges_from_sysdb failed.\n"); + goto done; + } + + *_idmap_ctx = talloc_steal(mem_ctx, idmap_ctx); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/ipa/ipa_init.c b/src/providers/ipa/ipa_init.c new file mode 100644 index 0000000..5ea92ec --- /dev/null +++ b/src/providers/ipa/ipa_init.c @@ -0,0 +1,960 @@ +/* + SSSD + + IPA Provider Initialization functions + + Authors: + Simo Sorce <ssorce@redhat.com> + + Copyright (C) 2009 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <sys/types.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "util/child_common.h" +#include "providers/ipa/ipa_common.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_init_shared.h" +#include "providers/ipa/ipa_id.h" +#include "providers/ipa/ipa_auth.h" +#include "providers/ipa/ipa_access.h" +#include "providers/ipa/ipa_dyndns.h" +#include "providers/ipa/ipa_selinux.h" +#include "providers/ldap/sdap_access.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ipa/ipa_srv.h" +#include "providers/be_dyndns.h" +#include "providers/ipa/ipa_session.h" + +#define DNS_SRV_MISCONFIGURATION "SRV discovery is enabled on the IPA " \ + "server while using custom dns_discovery_domain. DNS discovery of " \ + "trusted AD domain will likely fail. It is recommended not to use " \ + "SRV discovery or the dns_discovery_domain option for the IPA " \ + "domain while running on the server itself\n" + +#define PREAUTH_INDICATOR_ERROR "Failed to create preauth indicator file, " \ + "special password prompting might not be available.\n" + +struct ipa_init_ctx { + struct ipa_options *options; + struct ipa_id_ctx *id_ctx; + struct ipa_auth_ctx *auth_ctx; +}; + + +struct krb5_ctx *ipa_init_get_krb5_auth_ctx(void *data) +{ + struct ipa_init_ctx *ipa_init_ctx; + + ipa_init_ctx = talloc_get_type(data, struct ipa_init_ctx); + if (ipa_init_ctx == NULL || ipa_init_ctx->auth_ctx == NULL) { + return NULL; + } + + return ipa_init_ctx->auth_ctx->krb5_auth_ctx; +} + +static bool srv_in_server_list(const char *servers) +{ + TALLOC_CTX *tmp_ctx; + char **list = NULL; + int ret = 0; + bool has_srv = false; + + if (servers == NULL) return true; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return false; + } + + /* split server parm into a list */ + ret = split_on_separator(tmp_ctx, servers, ',', true, true, &list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse server list!\n"); + goto done; + } + + for (int i = 0; list[i]; i++) { + has_srv = be_fo_is_srv_identifier(list[i]); + if (has_srv == true) { + break; + } + } + +done: + talloc_free(tmp_ctx); + return has_srv; +} + +static errno_t ipa_init_options(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_options **_ipa_options) +{ + struct ipa_options *ipa_options; + const char *ipa_servers; + const char *ipa_backup_servers; + errno_t ret; + + ret = ipa_get_options(mem_ctx, be_ctx->cdb, be_ctx->conf_path, + be_ctx->domain, &ipa_options); + if (ret != EOK) { + return ret; + } + + ipa_servers = dp_opt_get_string(ipa_options->basic, IPA_SERVER); + ipa_backup_servers = dp_opt_get_string(ipa_options->basic, IPA_BACKUP_SERVER); + + ret = ipa_service_init(ipa_options, be_ctx, ipa_servers, + ipa_backup_servers, ipa_options, + &ipa_options->service); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to init IPA service [%d]: %s\n", + ret, sss_strerror(ret)); + talloc_free(ipa_options); + return ret; + } + + *_ipa_options = ipa_options; + return EOK; +} + +static errno_t ipa_init_id_ctx(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_options *ipa_options, + struct ipa_id_ctx **_ipa_id_ctx) +{ + struct ipa_id_ctx *ipa_id_ctx = NULL; + struct sdap_id_ctx *sdap_id_ctx = NULL; + errno_t ret; + + ipa_id_ctx = talloc_zero(mem_ctx, struct ipa_id_ctx); + if (ipa_id_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + sdap_id_ctx = sdap_id_ctx_new(mem_ctx, be_ctx, ipa_options->service->sdap); + if (sdap_id_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ipa_id_ctx->ipa_options = ipa_options; + ipa_id_ctx->sdap_id_ctx = sdap_id_ctx; + ipa_options->id_ctx = ipa_id_ctx; + + ret = ipa_get_id_options(ipa_options, + be_ctx->cdb, + be_ctx->conf_path, + be_ctx->provider, + &sdap_id_ctx->opts); + if (ret != EOK) { + goto done; + } + + *_ipa_id_ctx = ipa_id_ctx; + + ret = EOK; + +done: + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init id context [%d]: %s\n", + ret, sss_strerror(ret)); + + talloc_free(ipa_id_ctx); + talloc_free(sdap_id_ctx); + } + + return ret; +} + + +static errno_t ipa_init_dyndns(struct be_ctx *be_ctx, + struct ipa_options *ipa_options) +{ + bool enabled; + errno_t ret; + + ret = ipa_get_dyndns_options(be_ctx, ipa_options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get dyndns options [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + enabled = dp_opt_get_bool(ipa_options->dyndns_ctx->opts, + DP_OPT_DYNDNS_UPDATE); + if (!enabled) { + DEBUG(SSSDBG_CONF_SETTINGS, "Dynamic DNS updates are off.\n"); + return EOK; + } + + /* Perform automatic DNS updates when the IP address changes. + * Register a callback for successful LDAP reconnections. + * This is the easiest way to identify that we have gone online. + */ + + DEBUG(SSSDBG_CONF_SETTINGS, + "Dynamic DNS updates are on. Checking for nsupdate...\n"); + + ret = be_nsupdate_check(); + if (ret != EOK) { + DEBUG(SSSDBG_CONF_SETTINGS, "nsupdate is not availabe, " + "dynamic DNS updates will not work\n"); + return EOK; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "nsupdate is available\n"); + + ret = ipa_dyndns_init(be_ctx, ipa_options); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failure setting up automatic DNS update\n"); + /* We will continue without DNS updating */ + } + + return EOK; +} + +static errno_t ipa_init_server_mode(struct be_ctx *be_ctx, + struct ipa_options *ipa_options, + struct ipa_id_ctx *ipa_id_ctx) +{ + const char *ipa_servers; + const char *dnsdomain; + const char *hostname; + bool sites_enabled; + errno_t ret; + + ipa_id_ctx->view_name = talloc_strdup(ipa_id_ctx, SYSDB_DEFAULT_VIEW_NAME); + if (ipa_id_ctx->view_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup() failed.\n"); + return ENOMEM; + } + + ret = sysdb_update_view_name(be_ctx->domain->sysdb, ipa_id_ctx->view_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot add/update view name to sysdb.\n"); + return ret; + } + + hostname = dp_opt_get_string(ipa_options->basic, IPA_HOSTNAME); + ipa_servers = dp_opt_get_string(ipa_options->basic, IPA_SERVER); + sites_enabled = dp_opt_get_bool(ipa_options->basic, IPA_ENABLE_DNS_SITES); + dnsdomain = dp_opt_get_string(be_ctx->be_res->opts, DP_RES_OPT_DNS_DOMAIN); + + if (srv_in_server_list(ipa_servers) || sites_enabled) { + DEBUG(SSSDBG_IMPORTANT_INFO, "SSSD configuration uses either DNS " + "SRV resolution or IPA site discovery to locate IPA servers. " + "On IPA server itself, it is recommended that SSSD is " + "configured to only connect to the IPA server it's running at. "); + + /* If SRV discovery is enabled on the server and + * dns_discovery_domain is set explicitly, then + * the current failover code would use the dns_discovery + * domain to try to find AD servers and fail. + */ + if (dnsdomain != NULL) { + sss_log(SSS_LOG_ERR, DNS_SRV_MISCONFIGURATION); + DEBUG(SSSDBG_CRIT_FAILURE, DNS_SRV_MISCONFIGURATION); + } + + ret = be_fo_set_dns_srv_lookup_plugin(be_ctx, hostname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set SRV lookup plugin " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + return EOK; + } else { + /* In server mode we need to ignore the dns_discovery_domain if set + * and only discover servers based on AD domains. */ + ret = dp_opt_set_string(be_ctx->be_res->opts, DP_RES_OPT_DNS_DOMAIN, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not reset the " + "dns_discovery_domain, trusted AD domains discovery " + "might fail. Please remove dns_discovery_domain " + "from the config file and restart the SSSD\n"); + } else { + DEBUG(SSSDBG_CONF_SETTINGS, "The value of dns_discovery_domain " + "will be ignored in ipa_server_mode\n"); + } + } + + return EOK; +} + +static errno_t ipa_init_client_mode(struct be_ctx *be_ctx, + struct ipa_options *ipa_options, + struct ipa_id_ctx *ipa_id_ctx) +{ + struct ipa_srv_plugin_ctx *srv_ctx; + const char *ipa_domain; + const char *hostname; + bool sites_enabled; + errno_t ret; + + ret = sysdb_get_view_name(ipa_id_ctx, be_ctx->domain->sysdb, + &ipa_id_ctx->view_name); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot find view name in the cache. " + "Will do online lookup later.\n"); + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_get_view_name() failed [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + hostname = dp_opt_get_string(ipa_options->basic, IPA_HOSTNAME); + sites_enabled = dp_opt_get_bool(ipa_options->basic, IPA_ENABLE_DNS_SITES); + + if (sites_enabled) { + /* use IPA plugin */ + ipa_domain = dp_opt_get_string(ipa_options->basic, IPA_DOMAIN); + srv_ctx = ipa_srv_plugin_ctx_init(be_ctx, be_ctx->be_res->resolv, + hostname, ipa_domain); + if (srv_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory?\n"); + return ENOMEM; + } + + be_fo_set_srv_lookup_plugin(be_ctx, ipa_srv_plugin_send, + ipa_srv_plugin_recv, srv_ctx, "IPA"); + } else { + /* fall back to standard plugin on clients. */ + ret = be_fo_set_dns_srv_lookup_plugin(be_ctx, hostname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set SRV lookup plugin " + "[%d]: %s\n", ret, strerror(ret)); + return ret; + } + } + + return EOK; +} + +static errno_t ipa_init_ipa_auth_ctx(TALLOC_CTX *mem_ctx, + struct ipa_options *ipa_options, + struct ipa_id_ctx *ipa_id_ctx, + struct ipa_auth_ctx **_ipa_auth_ctx) +{ + struct ipa_auth_ctx *ipa_auth_ctx; + errno_t ret; + + ipa_auth_ctx = talloc_zero(mem_ctx, struct ipa_auth_ctx); + if (ipa_auth_ctx == NULL) { + return ENOMEM; + } + + ipa_auth_ctx->sdap_id_ctx = ipa_id_ctx->sdap_id_ctx; + + ret = dp_copy_options(ipa_auth_ctx, ipa_options->basic, + IPA_OPTS_BASIC, &ipa_auth_ctx->ipa_options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_copy_options failed.\n"); + talloc_free(ipa_auth_ctx); + return ret; + } + + *_ipa_auth_ctx = ipa_auth_ctx; + + return EOK; +} + +static errno_t ipa_init_krb5_auth_ctx(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_options *ipa_options, + struct krb5_ctx **_krb5_auth_ctx) +{ + struct krb5_ctx *krb5_auth_ctx; + bool server_mode; + errno_t ret; + + krb5_auth_ctx = talloc_zero(mem_ctx, struct krb5_ctx); + if (krb5_auth_ctx == NULL) { + return ENOMEM; + } + + krb5_auth_ctx->service = ipa_options->service->krb5_service; + + server_mode = dp_opt_get_bool(ipa_options->basic, IPA_SERVER_MODE); + krb5_auth_ctx->config_type = server_mode ? K5C_IPA_SERVER : K5C_IPA_CLIENT; + + ret = ipa_get_auth_options(ipa_options, be_ctx->cdb, be_ctx->conf_path, + &krb5_auth_ctx->opts); + if (ret != EOK) { + talloc_free(krb5_auth_ctx); + return ret; + } + + *_krb5_auth_ctx = krb5_auth_ctx; + return EOK; +} + +static errno_t ipa_init_sdap_auth_ctx(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_options *ipa_options, + struct sdap_auth_ctx **_sdap_auth_ctx) +{ + struct sdap_auth_ctx *sdap_auth_ctx; + + sdap_auth_ctx = talloc_zero(mem_ctx, struct sdap_auth_ctx); + if (sdap_auth_ctx == NULL) { + return ENOMEM; + } + + sdap_auth_ctx->be = be_ctx; + sdap_auth_ctx->service = ipa_options->service->sdap; + + if (ipa_options->id == NULL) { + talloc_free(sdap_auth_ctx); + return EINVAL; + } + + sdap_auth_ctx->opts = ipa_options->id; + + *_sdap_auth_ctx = sdap_auth_ctx; + + return EOK; +} + +static struct sdap_ext_member_ctx * +ipa_create_ext_members_ctx(TALLOC_CTX *mem_ctx, + struct ipa_id_ctx *id_ctx) +{ + struct sdap_ext_member_ctx *ext_ctx = NULL; + + ext_ctx = talloc_zero(mem_ctx, struct sdap_ext_member_ctx); + if (ext_ctx == NULL) { + return NULL; + } + + ext_ctx->pvt = id_ctx; + ext_ctx->ext_member_resolve_send = ipa_ext_group_member_send; + ext_ctx->ext_member_resolve_recv = ipa_ext_group_member_recv; + + return ext_ctx; +} + +static errno_t ipa_init_auth_ctx(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_options *ipa_options, + struct ipa_id_ctx *id_ctx, + struct ipa_auth_ctx **_auth_ctx) +{ + struct sdap_auth_ctx *sdap_auth_ctx; + struct ipa_auth_ctx *ipa_auth_ctx; + struct krb5_ctx *krb5_auth_ctx; + errno_t ret; + + ret = ipa_init_ipa_auth_ctx(mem_ctx, ipa_options, id_ctx, &ipa_auth_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init IPA auth context\n"); + return ret; + } + + ipa_options->auth_ctx = ipa_auth_ctx; + + ret = ipa_init_krb5_auth_ctx(ipa_auth_ctx, be_ctx, ipa_options, + &krb5_auth_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init KRB5 auth context\n"); + goto done; + } + ipa_options->auth_ctx->krb5_auth_ctx = krb5_auth_ctx; + + ret = ipa_init_sdap_auth_ctx(ipa_auth_ctx, be_ctx, ipa_options, + &sdap_auth_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init SDAP auth context\n"); + goto done; + } + ipa_options->auth_ctx->sdap_auth_ctx = sdap_auth_ctx; + + setup_ldap_debug(sdap_auth_ctx->opts->basic); + + ret = setup_tls_config(sdap_auth_ctx->opts->basic); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "setup_tls_config failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Initialize features needed by the krb5_child */ + ret = krb5_child_init(krb5_auth_ctx, be_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not initialize krb5_child " + "settings [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = create_preauth_indicator(); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, PREAUTH_INDICATOR_ERROR); + sss_log(SSSDBG_CRIT_FAILURE, PREAUTH_INDICATOR_ERROR); + } + + *_auth_ctx = ipa_auth_ctx; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(ipa_auth_ctx); + } + + return ret; +} + +static bool ipa_check_fqdn(const char *str) +{ + return strchr(str, '.'); +} + +static errno_t ipa_init_misc(struct be_ctx *be_ctx, + struct ipa_options *ipa_options, + struct ipa_id_ctx *ipa_id_ctx, + struct sdap_id_ctx *sdap_id_ctx) +{ + errno_t ret; + + if (!ipa_check_fqdn(dp_opt_get_string(ipa_options->basic, + IPA_HOSTNAME))) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_hostname is not Fully Qualified Domain Name.\n"); + } + + ret = ipa_init_dyndns(be_ctx, ipa_options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init dyndns [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + setup_ldap_debug(sdap_id_ctx->opts->basic); + + ret = setup_tls_config(sdap_id_ctx->opts->basic); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get TLS options [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = ipa_idmap_init(sdap_id_ctx, sdap_id_ctx, + &sdap_id_ctx->opts->idmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not initialize ID mapping. In case ID mapping properties " + "changed on the server, please remove the SSSD database\n"); + return ret; + } + + ret = ldap_id_setup_tasks(sdap_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup background tasks " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + if (dp_opt_get_bool(ipa_options->basic, IPA_SERVER_MODE)) { + ret = ipa_init_server_mode(be_ctx, ipa_options, ipa_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init server mode " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + } else { + ret = ipa_init_client_mode(be_ctx, ipa_options, ipa_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init client mode " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + } + + ret = ipa_refresh_init(be_ctx, ipa_id_ctx); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_MINOR_FAILURE, "Periodical refresh " + "will not work [%d]: %s\n", ret, sss_strerror(ret)); + } + + ipa_id_ctx->sdap_id_ctx->opts->ext_ctx = ipa_create_ext_members_ctx( + ipa_id_ctx->sdap_id_ctx->opts, ipa_id_ctx); + if (ipa_id_ctx->sdap_id_ctx->opts->ext_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set the extrernal group ctx\n"); + return ENOMEM; + } + + ret = sdap_init_certmap(sdap_id_ctx, sdap_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialized certificate mapping.\n"); + return ret; + } + + /* We must ignore entries in the views search base + * (default: cn=views,cn=accounts,$BASEDN) */ + sdap_id_ctx->opts->sdom->ignore_user_search_bases = \ + ipa_id_ctx->ipa_options->views_search_bases; + + return EOK; +} + +errno_t sssm_ipa_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + const char *module_name, + void **_module_data) +{ + struct ipa_init_ctx *init_ctx; + errno_t ret; + + init_ctx = talloc_zero(mem_ctx, struct ipa_init_ctx); + if (init_ctx == NULL) { + return ENOMEM; + } + + /* Always initialize options since it is needed everywhere. */ + ret = ipa_init_options(init_ctx, be_ctx, &init_ctx->options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init IPA options " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Always initialize id_ctx since it is needed everywhere. */ + ret = ipa_init_id_ctx(init_ctx, be_ctx, init_ctx->options, + &init_ctx->id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init IPA ID context " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Setup miscellaneous things. */ + ret = ipa_init_misc(be_ctx, init_ctx->options, init_ctx->id_ctx, + init_ctx->id_ctx->sdap_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init IPA module " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + /* Initialize auth_ctx only if one of the target is enabled. */ + if (dp_target_enabled(provider, module_name, DPT_AUTH, DPT_CHPASS)) { + ret = ipa_init_auth_ctx(init_ctx, be_ctx, init_ctx->options, + init_ctx->id_ctx, &init_ctx->auth_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to init IPA auth context " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + } + + *_module_data = init_ctx; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(init_ctx); + } + + return ret; +} + +errno_t sssm_ipa_id_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ipa_init_ctx *init_ctx; + struct ipa_id_ctx *id_ctx; + + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + id_ctx = init_ctx->id_ctx; + + dp_set_method(dp_methods, DPM_ACCOUNT_HANDLER, + ipa_account_info_handler_send, ipa_account_info_handler_recv, id_ctx, + struct ipa_id_ctx, struct dp_id_data, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_CHECK_ONLINE, + sdap_online_check_handler_send, sdap_online_check_handler_recv, id_ctx->sdap_id_ctx, + struct sdap_id_ctx, void, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_ACCT_DOMAIN_HANDLER, + default_account_domain_send, default_account_domain_recv, NULL, + void, struct dp_get_acct_domain_data, struct dp_reply_std); + + return EOK; +} + +errno_t sssm_ipa_auth_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ipa_init_ctx *init_ctx; + struct ipa_auth_ctx *auth_ctx; + + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + auth_ctx = init_ctx->auth_ctx; + + dp_set_method(dp_methods, DPM_AUTH_HANDLER, + ipa_pam_auth_handler_send, ipa_pam_auth_handler_recv, auth_ctx, + struct ipa_auth_ctx, struct pam_data, struct pam_data *); + + return EOK; +} + +errno_t sssm_ipa_chpass_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + return sssm_ipa_auth_init(mem_ctx, be_ctx, module_data, dp_methods); +} + +errno_t sssm_ipa_access_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ipa_access_ctx *access_ctx; + struct ipa_init_ctx *init_ctx; + struct ipa_id_ctx *id_ctx; + errno_t ret; + + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + id_ctx = init_ctx->id_ctx; + + access_ctx = talloc_zero(mem_ctx, struct ipa_access_ctx); + if (access_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed.\n"); + return ENOMEM; + } + + access_ctx->sdap_ctx = id_ctx->sdap_id_ctx; + access_ctx->host_map = id_ctx->ipa_options->id->host_map; + access_ctx->hostgroup_map = id_ctx->ipa_options->hostgroup_map; + access_ctx->host_search_bases = id_ctx->ipa_options->id->sdom->host_search_bases; + access_ctx->hbac_search_bases = id_ctx->ipa_options->hbac_search_bases; + + ret = dp_copy_options(access_ctx, id_ctx->ipa_options->basic, + IPA_OPTS_BASIC, &access_ctx->ipa_options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_copy_options() failed.\n"); + goto done; + } + + /* Set up an sdap_access_ctx for checking as configured */ + access_ctx->sdap_access_ctx = talloc_zero(access_ctx, struct sdap_access_ctx); + if (access_ctx->sdap_access_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed\n"); + ret = ENOMEM; + goto done; + } + + access_ctx->sdap_access_ctx->type = SDAP_TYPE_IPA; + access_ctx->sdap_access_ctx->id_ctx = access_ctx->sdap_ctx; + ret = sdap_set_access_rules(access_ctx, access_ctx->sdap_access_ctx, + access_ctx->ipa_options, + id_ctx->ipa_options->id->basic); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_set_access_rules failed: [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + dp_set_method(dp_methods, DPM_ACCESS_HANDLER, + ipa_pam_access_handler_send, ipa_pam_access_handler_recv, access_ctx, + struct ipa_access_ctx, struct pam_data, struct pam_data *); + + dp_set_method(dp_methods, DPM_REFRESH_ACCESS_RULES, + ipa_refresh_access_rules_send, ipa_refresh_access_rules_recv, access_ctx, + struct ipa_access_ctx, void, void *); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(access_ctx); + } + + return ret; +} + +errno_t sssm_ipa_selinux_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#if defined HAVE_SELINUX + struct ipa_selinux_ctx *selinux_ctx; + struct ipa_init_ctx *init_ctx; + struct ipa_options *opts; + + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + opts = init_ctx->options; + + selinux_ctx = talloc_zero(mem_ctx, struct ipa_selinux_ctx); + if (selinux_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed.\n"); + return ENOMEM; + } + + selinux_ctx->id_ctx = init_ctx->id_ctx; + selinux_ctx->hbac_search_bases = opts->hbac_search_bases; + selinux_ctx->host_search_bases = opts->id->sdom->host_search_bases; + selinux_ctx->selinux_search_bases = opts->selinux_search_bases; + + dp_set_method(dp_methods, DPM_SELINUX_HANDLER, + ipa_selinux_handler_send, ipa_selinux_handler_recv, selinux_ctx, + struct ipa_selinux_ctx, struct pam_data, struct pam_data *); + + return EOK; +#else + DEBUG(SSSDBG_MINOR_FAILURE, "SELinux init handler called but SSSD is " + "built without SELinux support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ipa_hostid_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#ifdef BUILD_SSH + struct ipa_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing IPA host handler\n"); + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + + return ipa_hostid_init(mem_ctx, be_ctx, init_ctx->id_ctx, dp_methods); + +#else + DEBUG(SSSDBG_MINOR_FAILURE, "HostID init handler called but SSSD is " + "built without SSH support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ipa_autofs_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#ifdef BUILD_AUTOFS + struct ipa_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing IPA autofs handler\n"); + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + + return ipa_autofs_init(mem_ctx, be_ctx, init_ctx->id_ctx, dp_methods); +#else + DEBUG(SSSDBG_MINOR_FAILURE, "Autofs init handler called but SSSD is " + "built without autofs support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ipa_subdomains_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ipa_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing IPA subdomains handler\n"); + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + + return ipa_subdomains_init(mem_ctx, be_ctx, init_ctx->id_ctx, dp_methods); +} + +errno_t sssm_ipa_sudo_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ +#ifdef BUILD_SUDO + struct ipa_init_ctx *init_ctx; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing IPA sudo handler\n"); + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + + return ipa_sudo_init(mem_ctx, be_ctx, init_ctx->id_ctx, dp_methods); +#else + DEBUG(SSSDBG_MINOR_FAILURE, "Sudo init handler called but SSSD is " + "built without sudo support, ignoring\n"); + return EOK; +#endif +} + +errno_t sssm_ipa_session_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct ipa_session_ctx *session_ctx; + struct ipa_init_ctx *init_ctx; + struct ipa_id_ctx *id_ctx; + errno_t ret; + + init_ctx = talloc_get_type(module_data, struct ipa_init_ctx); + id_ctx = init_ctx->id_ctx; + + session_ctx = talloc_zero(mem_ctx, struct ipa_session_ctx); + if (session_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed.\n"); + + return ENOMEM; + } + + session_ctx->sdap_ctx = id_ctx->sdap_id_ctx; + session_ctx->host_map = id_ctx->ipa_options->id->host_map; + session_ctx->hostgroup_map = id_ctx->ipa_options->hostgroup_map; + session_ctx->host_search_bases = id_ctx->ipa_options->id->sdom->host_search_bases; + session_ctx->deskprofile_search_bases = id_ctx->ipa_options->deskprofile_search_bases; + + ret = dp_copy_options(session_ctx, id_ctx->ipa_options->basic, + IPA_OPTS_BASIC, &session_ctx->ipa_options); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "dp_copy_options() failed.\n"); + + goto done; + } + + dp_set_method(dp_methods, DPM_SESSION_HANDLER, + ipa_pam_session_handler_send, ipa_pam_session_handler_recv, session_ctx, + struct ipa_session_ctx, struct pam_data, struct pam_data *); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(session_ctx); + } + + return ret; +} diff --git a/src/providers/ipa/ipa_netgroups.c b/src/providers/ipa/ipa_netgroups.c new file mode 100644 index 0000000..57f11a5 --- /dev/null +++ b/src/providers/ipa/ipa_netgroups.c @@ -0,0 +1,1056 @@ +/* + SSSD + + Async IPA Helper routines for netgroups + + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2011 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ipa/ipa_id.h" +#include <ctype.h> + +#define ENTITY_NG 1 +#define ENTITY_USER 2 +#define ENTITY_HOST 4 + +struct ipa_get_netgroups_state { + struct tevent_context *ev; + struct sdap_options *opts; + struct ipa_options *ipa_opts; + struct sdap_handle *sh; + struct sysdb_ctx *sysdb; + struct sss_domain_info *dom; + const char **attrs; + int timeout; + + char *filter; + const char *base_filter; + + size_t netgr_base_iter; + size_t host_base_iter; + size_t user_base_iter; + + /* Entities which have been already asked for + * and are scheduled for inspection */ + hash_table_t *new_netgroups; + hash_table_t *new_users; + hash_table_t *new_hosts; + + int current_entity; + int entities_found; + + struct sysdb_attrs **netgroups; + int netgroups_count; +}; + +static errno_t ipa_save_netgroup(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct sysdb_attrs *attrs) +{ + struct ldb_message_element *el; + struct sysdb_attrs *netgroup_attrs; + const char *name = NULL; + char **missing; + int missing_index; + int ret; + int i; + size_t c; + + ret = sysdb_attrs_get_el(attrs, + opts->netgroup_map[IPA_AT_NETGROUP_NAME].sys_name, + &el); + if (ret) goto fail; + if (el->num_values == 0) { + ret = EINVAL; + goto fail; + } + name = (const char *)el->values[0].data; + DEBUG(SSSDBG_TRACE_INTERNAL, "Storing netgroup %s\n", name); + + netgroup_attrs = sysdb_new_attrs(mem_ctx); + if (!netgroup_attrs) { + ret = ENOMEM; + goto fail; + } + + missing = talloc_zero_array(netgroup_attrs, char *, attrs->num + 1); + if (missing == NULL) { + ret = ENOMEM; + goto fail; + } + + for (i = 0, missing_index = 0; i < attrs->num; i++) { + if (attrs->a[i].num_values == 0) { + missing[missing_index] = talloc_strdup(missing, attrs->a[i].name); + if (missing[missing_index] == NULL) { + ret = ENOMEM; + goto fail; + } + missing_index++; + } + } + + ret = sysdb_attrs_get_el(attrs, SYSDB_ORIG_DN, &el); + if (ret) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "Original DN is not available for [%s].\n", name); + } else { + DEBUG(SSSDBG_TRACE_LIBS, + "Adding original DN [%s] to attributes of [%s].\n", + el->values[0].data, name); + ret = sysdb_attrs_add_string(netgroup_attrs, SYSDB_ORIG_DN, + (const char *)el->values[0].data); + if (ret) { + goto fail; + } + } + + ret = sysdb_attrs_get_el(attrs, SYSDB_NETGROUP_TRIPLE, &el); + if (ret) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_INTERNAL, "No netgroup triples for netgroup [%s].\n", name); + ret = sysdb_attrs_get_el(netgroup_attrs, SYSDB_NETGROUP_TRIPLE, &el); + if (ret != EOK) { + goto fail; + } + } else { + for(c = 0; c < el->num_values; c++) { + ret = sysdb_attrs_add_string_safe(netgroup_attrs, + SYSDB_NETGROUP_TRIPLE, + (const char*)el->values[c].data); + if (ret) { + goto fail; + } + } + } + + ret = sysdb_attrs_get_el(attrs, + opts->netgroup_map[IPA_AT_NETGROUP_MEMBER].sys_name, + &el); + if (ret != EOK) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_LIBS, + "No original members for netgroup [%s]\n", name); + } else { + DEBUG(SSSDBG_TRACE_LIBS, + "Adding original members to netgroup [%s]\n", name); + for(c = 0; c < el->num_values; c++) { + ret = sysdb_attrs_add_string(netgroup_attrs, + opts->netgroup_map[IPA_AT_NETGROUP_MEMBER].sys_name, + (const char*)el->values[c].data); + if (ret) { + goto fail; + } + } + } + + + ret = sysdb_attrs_get_el(attrs, SYSDB_NETGROUP_MEMBER, &el); + if (ret != EOK) { + goto fail; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_TRACE_LIBS, "No members for netgroup [%s]\n", name); + + } else { + DEBUG(SSSDBG_TRACE_LIBS, "Adding members to netgroup [%s]\n", name); + for(c = 0; c < el->num_values; c++) { + ret = sysdb_attrs_add_string(netgroup_attrs, SYSDB_NETGROUP_MEMBER, + (const char*)el->values[c].data); + if (ret) { + goto fail; + } + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "Storing info for netgroup %s\n", name); + + ret = sysdb_add_netgroup(dom, name, NULL, netgroup_attrs, missing, + dom->netgroup_timeout, 0); + if (ret) goto fail; + + return EOK; + +fail: + DEBUG(SSSDBG_OP_FAILURE, "Failed to save netgroup %s\n", name); + return ret; +} + +static errno_t ipa_netgr_next_base(struct tevent_req *req); +static void ipa_get_netgroups_process(struct tevent_req *subreq); +static int ipa_netgr_process_all(struct ipa_get_netgroups_state *state); + +struct tevent_req *ipa_get_netgroups_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_options *opts, + struct ipa_options *ipa_options, + struct sdap_handle *sh, + const char **attrs, + const char *filter, + int timeout) +{ + struct tevent_req *req; + struct ipa_get_netgroups_state *state; + int ret; + + req = tevent_req_create(memctx, &state, struct ipa_get_netgroups_state); + if (!req) return NULL; + + state->ev = ev; + state->opts = opts; + state->ipa_opts = ipa_options; + state->sh = sh; + state->sysdb = sysdb; + state->attrs = attrs; + state->timeout = timeout; + state->base_filter = filter; + state->netgr_base_iter = 0; + state->dom = dom; + + if (!ipa_options->id->sdom->netgroup_search_bases) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Netgroup lookup request without a search base\n"); + ret = EINVAL; + goto done; + } + + ret = sss_hash_create(state, 0, &state->new_netgroups); + if (ret != EOK) goto done; + ret = sss_hash_create(state, 0, &state->new_users); + if (ret != EOK) goto done; + ret = sss_hash_create(state, 0, &state->new_hosts); + if (ret != EOK) goto done; + + + ret = ipa_netgr_next_base(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static errno_t ipa_netgr_next_base(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct ipa_get_netgroups_state *state; + struct sdap_search_base **netgr_bases; + + state = tevent_req_data(req, struct ipa_get_netgroups_state); + netgr_bases = state->ipa_opts->id->sdom->netgroup_search_bases; + + talloc_zfree(state->filter); + state->filter = sdap_combine_filters( + state, + state->base_filter, + netgr_bases[state->netgr_base_iter]->filter); + if (!state->filter) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Searching for netgroups with base [%s]\n", + netgr_bases[state->netgr_base_iter]->basedn); + + subreq = sdap_get_generic_send( + state, state->ev, state->opts, state->sh, + netgr_bases[state->netgr_base_iter]->basedn, + netgr_bases[state->netgr_base_iter]->scope, + state->filter, state->attrs, + state->opts->netgroup_map, IPA_OPTS_NETGROUP, + state->timeout, + true); + if (!subreq) { + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_get_netgroups_process, req); + + return EOK; +} + +static int ipa_netgr_fetch_netgroups(struct ipa_get_netgroups_state *state, + struct tevent_req *req); +static int ipa_netgr_fetch_users(struct ipa_get_netgroups_state *state, + struct tevent_req *req); +static int ipa_netgr_fetch_hosts(struct ipa_get_netgroups_state *state, + struct tevent_req *req); +static void ipa_netgr_members_process(struct tevent_req *subreq); + +static void ipa_get_netgroups_process(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_netgroups_state *state = tevent_req_data(req, + struct ipa_get_netgroups_state); + int i, ret; + struct ldb_message_element *el; + struct sdap_search_base **netgr_bases; + struct sysdb_attrs **netgroups; + size_t netgroups_count; + const char *orig_dn; + char *dn; + char *filter; + bool fetch_members = false; + hash_key_t key; + hash_value_t value; + + netgr_bases = state->ipa_opts->id->sdom->netgroup_search_bases; + + ret = sdap_get_generic_recv(subreq, state, &netgroups_count, &netgroups); + talloc_zfree(subreq); + if (ret) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Search for netgroups, returned %zu results.\n", + netgroups_count); + + if (netgroups_count == 0) { + /* No netgroups found in this search */ + state->netgr_base_iter++; + if (netgr_bases[state->netgr_base_iter]) { + /* There are more search bases to try */ + ret = ipa_netgr_next_base(req); + if (ret != EOK) { + tevent_req_error(req, ENOENT); + } + return; + } + + ret = ENOENT; + goto done; + } + + filter = talloc_strdup(state, "(|"); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < netgroups_count; i++) { + ret = sysdb_attrs_get_el(netgroups[i], SYSDB_ORIG_NETGROUP_MEMBER, + &el); + if (ret != EOK) goto done; + if (el->num_values) state->entities_found |= ENTITY_NG; + + ret = sysdb_attrs_get_el(netgroups[i], SYSDB_ORIG_MEMBER_USER, + &el); + if (ret != EOK) goto done; + if (el->num_values) state->entities_found |= ENTITY_USER; + + ret = sysdb_attrs_get_el(netgroups[i], SYSDB_ORIG_MEMBER_HOST, + &el); + if (ret != EOK) goto done; + if (el->num_values) state->entities_found |= ENTITY_HOST; + + ret = sysdb_attrs_get_string(netgroups[i], SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + goto done; + } + + key.type = HASH_KEY_STRING; + value.type = HASH_VALUE_PTR; + key.str = discard_const(orig_dn); + value.ptr = netgroups[i]; + ret = hash_enter(state->new_netgroups, &key, &value); + if (ret != HASH_SUCCESS) { + ret = ENOMEM; + goto done; + } + + if (state->entities_found == 0) { + continue; + } + + ret = sss_filter_sanitize_dn(state, orig_dn, &dn); + if (ret != EOK) { + goto done; + } + /* Add this to the filter */ + filter = talloc_asprintf_append(filter, "(%s=%s)", + state->opts->netgroup_map[IPA_AT_NETGROUP_MEMBER_OF].name, + dn); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + fetch_members = true; + } + + if (!fetch_members) { + ret = ipa_netgr_process_all(state); + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + return; + } + + state->filter = talloc_asprintf_append(filter, ")"); + if (state->filter == NULL) { + ret = ENOMEM; + goto done; + } + + if (state->entities_found & ENTITY_NG) { + state->netgr_base_iter = 0; + ret = ipa_netgr_fetch_netgroups(state, req); + if (ret != EOK) goto done; + } else if (state->entities_found & ENTITY_USER) { + ret = ipa_netgr_fetch_users(state, req); + if (ret != EOK) goto done; + } else if (state->entities_found & ENTITY_HOST) { + ret = ipa_netgr_fetch_hosts(state, req); + if (ret != EOK) goto done; + } + + return; +done: + tevent_req_error(req, ret); + return; +} + +static int ipa_netgr_fetch_netgroups(struct ipa_get_netgroups_state *state, + struct tevent_req *req) +{ + char *filter; + const char *base_filter; + struct tevent_req *subreq; + struct sdap_search_base **bases; + + bases = state->ipa_opts->id->sdom->netgroup_search_bases; + if (bases[state->netgr_base_iter] == NULL) { + /* No more bases to try */ + return ENOENT; + } + base_filter = bases[state->netgr_base_iter]->filter; + + filter = talloc_asprintf(state, "(&%s%s(objectclass=%s))", + state->filter, + base_filter?base_filter:"", + state->opts->netgroup_map[SDAP_OC_NETGROUP].name); + if (filter == NULL) + return ENOMEM; + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + bases[state->netgr_base_iter]->basedn, + bases[state->netgr_base_iter]->scope, + filter, state->attrs, state->opts->netgroup_map, + IPA_OPTS_NETGROUP, state->timeout, true); + + state->current_entity = ENTITY_NG; + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_netgr_members_process, req); + + return EOK; +} + +static int ipa_netgr_fetch_users(struct ipa_get_netgroups_state *state, + struct tevent_req *req) +{ + const char *attrs[] = { state->opts->user_map[SDAP_AT_USER_NAME].name, + state->opts->user_map[SDAP_AT_USER_MEMBEROF].name, + "objectclass", NULL }; + char *filter; + const char *base_filter; + struct tevent_req *subreq; + struct sdap_search_base **bases; + + bases = state->ipa_opts->id->sdom->user_search_bases; + if (bases[state->user_base_iter] == NULL) { + return ENOENT; + } + base_filter = bases[state->user_base_iter]->filter; + + filter = talloc_asprintf(state, "(&%s%s(objectclass=%s))", + state->filter, + base_filter?base_filter:"", + state->opts->user_map[SDAP_OC_USER].name); + if (filter == NULL) + return ENOMEM; + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + dp_opt_get_string(state->opts->basic, + SDAP_USER_SEARCH_BASE), + LDAP_SCOPE_SUBTREE, + filter, attrs, state->opts->user_map, + state->opts->user_map_cnt, + state->timeout, true); + + state->current_entity = ENTITY_USER; + if (subreq == NULL) { + talloc_free(attrs); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_netgr_members_process, req); + + return EOK; +} + +static int ipa_netgr_fetch_hosts(struct ipa_get_netgroups_state *state, + struct tevent_req *req) +{ + const char **attrs; + char *filter; + const char *base_filter; + struct tevent_req *subreq; + int ret; + struct sdap_search_base **bases; + + bases = state->ipa_opts->id->sdom->host_search_bases; + if (bases[state->host_base_iter] == NULL) { + return ENOENT; + } + base_filter = bases[state->host_base_iter]->filter; + + filter = talloc_asprintf(state, "(&%s%s(objectclass=%s))", + state->filter, + base_filter ? base_filter : "", + state->ipa_opts->id->host_map[SDAP_OC_HOST].name); + if (filter == NULL) + return ENOMEM; + + ret = build_attrs_from_map(state, state->ipa_opts->id->host_map, + SDAP_OPTS_HOST, NULL, &attrs, NULL); + if (ret != EOK) { + talloc_free(filter); + return ret; + } + + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + bases[state->host_base_iter]->basedn, + bases[state->host_base_iter]->scope, + filter, attrs, state->ipa_opts->id->host_map, + SDAP_OPTS_HOST, state->timeout, true); + + state->current_entity = ENTITY_HOST; + if (subreq == NULL) { + talloc_free(filter); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_netgr_members_process, req); + + return EOK; +} + +static void ipa_netgr_members_process(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_netgroups_state *state = tevent_req_data(req, + struct ipa_get_netgroups_state); + struct sysdb_attrs **entities; + size_t count; + int ret, i; + const char *orig_dn; + hash_table_t *table; + hash_key_t key; + hash_value_t value; + int (* next_call)(struct ipa_get_netgroups_state *, + struct tevent_req *); + bool next_batch_scheduled = false; + + ret = sdap_get_generic_recv(subreq, state, &count, &entities); + talloc_zfree(subreq); + if (ret) { + goto fail; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Found %zu members in current search base\n", + count); + + next_call = NULL; + /* While processing a batch of entities from one search base, + * schedule query for another search base if there is one + * + * If there is no other search base, another class of entities + * will be scheduled for lookup after processing of current + * batch. The order of lookup is: netgroups -> users -> hosts + */ + if (state->current_entity == ENTITY_NG) { + /* We just received a batch of netgroups */ + state->netgr_base_iter++; + ret = ipa_netgr_fetch_netgroups(state, req); + table = state->new_netgroups; + /* If there is a member netgroup, we always have to + * ask for both member users and hosts + * -> now schedule users + */ + next_call = ipa_netgr_fetch_users; + } else if (state->current_entity == ENTITY_USER) { + /* We just received a batch of users */ + state->user_base_iter++; + ret = ipa_netgr_fetch_users(state, req); + table = state->new_users; + if (state->entities_found & ENTITY_HOST || + state->entities_found & ENTITY_NG) { + next_call = ipa_netgr_fetch_hosts; + } + } else if (state->current_entity == ENTITY_HOST) { + /* We just received a batch of hosts */ + state->host_base_iter++; + ret = ipa_netgr_fetch_hosts(state, req); + table = state->new_hosts; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid entity type given for processing: %d\n", + state->current_entity); + ret = EINVAL; + goto fail; + } + + if (ret == EOK) { + /* Next search base has been scheduled for inspection, + * don't try to look for other type of entities + */ + next_batch_scheduled = true; + } else if (ret != ENOENT) { + goto fail; + } + + /* Process all member entities and store them in the designated hash table */ + key.type = HASH_KEY_STRING; + value.type = HASH_VALUE_PTR; + for (i = 0; i < count; i++) { + ret = sysdb_attrs_get_string(entities[i], SYSDB_ORIG_DN, &orig_dn); + if (ret != EOK) { + goto fail; + } + + key.str = talloc_strdup(table, orig_dn); + if (key.str == NULL) { + ret = ENOMEM; + goto fail; + } + + value.ptr = entities[i]; + ret = hash_enter(table, &key, &value); + if (ret != HASH_SUCCESS) { + goto fail; + } + } + + if (next_batch_scheduled) { + /* The next search base is already scheduled to be searched */ + return; + } + + if (next_call) { + /* There is another class of members that has to be retrieved + * - schedule the lookup + */ + ret = next_call(state, req); + if (ret != EOK) goto fail; + } else { + /* All members, that could have been fetched, were fetched */ + ret = ipa_netgr_process_all(state); + if (ret != EOK) goto fail; + + tevent_req_done(req); + } + + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static bool extract_netgroups(hash_entry_t *entry, void *pvt) +{ + struct ipa_get_netgroups_state *state; + state = talloc_get_type(pvt, struct ipa_get_netgroups_state); + + state->netgroups[state->netgroups_count] = talloc_get_type(entry->value.ptr, + struct sysdb_attrs); + state->netgroups_count++; + + return true; +} + +struct extract_state { + const char *group; + const char *appropriateMemberOf; + + const char **entries; + int entries_count; +}; + +static bool extract_entities(hash_entry_t *entry, void *pvt) +{ + int ret; + struct extract_state *state; + struct sysdb_attrs *member; + struct ldb_message_element *el; + struct ldb_message_element *name_el; + + state = talloc_get_type(pvt, struct extract_state); + member = talloc_get_type(entry->value.ptr, struct sysdb_attrs); + + ret = sysdb_attrs_get_el(member, state->appropriateMemberOf, &el); + if (ret != EOK) { + return false; + } + + ret = sysdb_attrs_get_el(member, SYSDB_NAME, &name_el); + if (ret != EOK || name_el == NULL || name_el->num_values == 0) { + return false; + } + + for (int j = 0; j < el->num_values; j++) { + if (strcmp((char *)el->values[j].data, state->group) == 0) { + state->entries = talloc_realloc(state, state->entries, + const char *, + state->entries_count + 1); + if (state->entries == NULL) { + return false; + } + + state->entries[state->entries_count] = (char *)name_el->values[0].data; + state->entries_count++; + break; + } + } + + return true; +} + +static int extract_members(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *netgroup, + const char *member_type, + const char *appropriateMemberOf, + hash_table_t *lookup_table, + const char ***_ret_array, + int *_ret_count) +{ + struct extract_state *state; + struct ldb_message_element *el; + struct sysdb_attrs *member; + hash_key_t key; + hash_value_t value; + const char **process = NULL; + const char **ret_array = NULL; + int process_count = 0; + int ret_count = 0; + int ret, i, pi; + + key.type = HASH_KEY_STRING; + value.type = HASH_VALUE_PTR; + + state = talloc_zero(mem_ctx, struct extract_state); + if (state == NULL) { + ret = ENOMEM; + goto done; + } + + state->appropriateMemberOf = appropriateMemberOf; + + ret = sysdb_attrs_get_el(netgroup, member_type, &el); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + if (ret == EOK) { + for (i = 0; i < el->num_values; i++) { + key.str = (char *)el->values[i].data; + ret = hash_lookup(lookup_table, &key, &value); + if (ret != HASH_SUCCESS && ret != HASH_ERROR_KEY_NOT_FOUND) { + ret = ENOENT; + goto done; + } + + if (ret == HASH_ERROR_KEY_NOT_FOUND) { + process = talloc_realloc(mem_ctx, process, const char *, process_count + 1); + if (process == NULL) { + ret = ENOMEM; + goto done; + } + + process[process_count] = (char *)el->values[i].data; + process_count++; + } else { + ret_array = talloc_realloc(mem_ctx, ret_array, const char *, ret_count + 1); + if (ret_array == NULL) { + ret = ENOMEM; + goto done; + } + member = talloc_get_type(value.ptr, struct sysdb_attrs); + ret = sysdb_attrs_get_string(member, SYSDB_NAME, &ret_array[ret_count]); + if (ret != EOK) { + goto done; + } + ret_count++; + } + + for (pi = 0; pi < process_count; pi++) { + state->group = process[pi]; + hash_iterate(lookup_table, extract_entities, state); + if (state->entries_count > 0) { + ret_array = talloc_realloc(mem_ctx, ret_array, const char *, + ret_count + state->entries_count); + if (ret_array == NULL) { + ret = ENOMEM; + goto done; + } + memcpy(&ret_array[ret_count], state->entries, + state->entries_count*sizeof(const char *)); + ret_count += state->entries_count; + } + state->entries_count = 0; + talloc_zfree(state->entries); + } + } + } else { + ret_array = NULL; + } + + *_ret_array = ret_array; + *_ret_count = ret_count; + ret = EOK; + +done: + return ret; +} + +static int ipa_netgr_process_all(struct ipa_get_netgroups_state *state) +{ + int i, j, k, ret; + const char **members; + struct sysdb_attrs *member; + const char *member_name; + struct extract_state *extract_state; + struct ldb_message_element *external_hosts; + const char *dash[] = {"-"}; + const char **uids = NULL; + const char **hosts = NULL; + int uids_count = 0; + int hosts_count = 0; + hash_key_t key; + hash_value_t value; + const char *domain; + char *triple; + + state->netgroups = talloc_zero_array(state, struct sysdb_attrs *, + hash_count(state->new_netgroups)); + if (state->netgroups == NULL) { + return ENOMEM; + } + + extract_state = talloc_zero(state, struct extract_state); + if (extract_state == NULL) { + ret = ENOMEM; + goto done; + } + + key.type = HASH_KEY_STRING; + value.type = HASH_VALUE_PTR; + + hash_iterate(state->new_netgroups, extract_netgroups, state); + for (i = 0; i < state->netgroups_count; i++) { + /* Make sure these attributes always exist, so we can remove them if + * there are no members. */ + ret = sysdb_attrs_add_empty(state->netgroups[i], SYSDB_NETGROUP_MEMBER); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_add_empty(state->netgroups[i], SYSDB_NETGROUP_TRIPLE); + if (ret != EOK) { + goto done; + } + + /* load all its member netgroups, translate */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Extracting netgroup members of netgroup %d\n", i); + ret = sysdb_attrs_get_string_array(state->netgroups[i], + SYSDB_ORIG_NETGROUP_MEMBER, + state, &members); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + j = 0; + if (ret == EOK) { + for (j = 0; members[j]; j++) { + key.str = discard_const(members[j]); + ret = hash_lookup(state->new_netgroups, &key, &value); + if (ret != HASH_SUCCESS) { + ret = ENOENT; + goto done; + } + + member = talloc_get_type(value.ptr, struct sysdb_attrs); + ret = sysdb_attrs_get_string(member, SYSDB_NAME, &member_name); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_add_string(state->netgroups[i], + SYSDB_NETGROUP_MEMBER, + member_name); + if (ret != EOK) { + goto done; + } + } + talloc_zfree(members); + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Extracted %d netgroup members\n", j); + + /* Load all UIDs */ + DEBUG(SSSDBG_TRACE_ALL, "Extracting user members of netgroup %d\n", i); + ret = extract_members(state, state->netgroups[i], + SYSDB_ORIG_MEMBER_USER, + state->ipa_opts->id->user_map[SDAP_AT_USER_MEMBEROF].sys_name, + state->new_users, + &uids, &uids_count); + if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Extracted %d user members\n", uids_count); + + DEBUG(SSSDBG_TRACE_ALL, "Extracting host members of netgroup %d\n", i); + ret = extract_members(state, state->netgroups[i], + SYSDB_ORIG_MEMBER_HOST, + state->ipa_opts->id->host_map[SDAP_AT_HOST_MEMBER_OF].sys_name, + state->new_hosts, + &hosts, &hosts_count); + if (ret != EOK) { + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Extracted %d host members\n", hosts_count); + + ret = sysdb_attrs_get_el(state->netgroups[i], + SYSDB_ORIG_NETGROUP_EXTERNAL_HOST, + &external_hosts); + if (ret != EOK) { + goto done; + } + + if (external_hosts->num_values > 0) { + hosts = talloc_realloc(state, hosts, const char *, + hosts_count + external_hosts->num_values); + if (hosts == NULL) { + ret = ENOMEM; + goto done; + } + + for (j = 0; j < external_hosts->num_values; j++) { + hosts[hosts_count] = talloc_strdup(hosts, (char *)external_hosts->values[j].data); + if (hosts[hosts_count] == NULL) { + ret = ENOMEM; + goto done; + } + hosts_count++; + } + } + + ret = sysdb_attrs_get_string(state->netgroups[i], SYSDB_NETGROUP_DOMAIN, + &domain); + if (ret == ENOENT) { + domain = NULL; + } else if (ret != EOK) { + goto done; + } + + if (uids_count > 0 || hosts_count > 0) { + if (uids_count == 0) { + uids_count = 1; + uids = dash; + } + + if (hosts_count == 0) { + hosts_count = 1; + hosts = dash; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Putting together triples of " + "netgroup %d\n", i); + for (j = 0; j < uids_count; j++) { + for (k = 0; k < hosts_count; k++) { + triple = talloc_asprintf(state, "(%s,%s,%s)", + hosts[k], uids[j], + domain ? domain : ""); + if (triple == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(state->netgroups[i], + SYSDB_NETGROUP_TRIPLE, + triple); + if (ret != EOK) { + goto done; + } + } + } + } + + ret = ipa_save_netgroup(state, state->dom, + state->opts, state->netgroups[i]); + if (ret != EOK) { + goto done; + } + } + + ret = EOK; +done: + return ret; +} + +int ipa_get_netgroups_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *reply_count, + struct sysdb_attrs ***reply) +{ + struct ipa_get_netgroups_state *state = tevent_req_data(req, + struct ipa_get_netgroups_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (reply_count) { + *reply_count = state->netgroups_count; + } + + if (reply) { + *reply = talloc_steal(mem_ctx, state->netgroups); + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_opts.c b/src/providers/ipa/ipa_opts.c new file mode 100644 index 0000000..97cddb1 --- /dev/null +++ b/src/providers/ipa/ipa_opts.c @@ -0,0 +1,428 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2012 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "src/providers/data_provider.h" +#include "db/sysdb.h" +#include "db/sysdb_sudo.h" +#include "db/sysdb_autofs.h" +#include "db/sysdb_services.h" +#include "db/sysdb_selinux.h" +#include "db/sysdb_subid.h" +#include "providers/ldap/ldap_common.h" + +struct dp_option ipa_basic_opts[] = { + { "ipa_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_backup_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_hostname", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_hbac_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING}, + { "ipa_host_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_selinux_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_subdomains_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_master_domain_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING}, + { "ipa_hbac_refresh", DP_OPT_NUMBER, { .number = 5 }, NULL_NUMBER }, + { "ipa_selinux_refresh", DP_OPT_NUMBER, { .number = 5 }, NULL_NUMBER }, + { "ipa_hbac_support_srchost", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ipa_automount_location", DP_OPT_STRING, { "default" }, NULL_STRING }, + { "ipa_ranges_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_enable_dns_sites", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ipa_server_mode", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ipa_views_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_confd_path", DP_OPT_STRING, { KRB5_MAPPING_DIR }, NULL_STRING }, + { "ipa_deskprofile_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_deskprofile_refresh", DP_OPT_NUMBER, { .number = 5 }, NULL_NUMBER }, + { "ipa_deskprofile_request_interval", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, + { "ipa_subid_ranges_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ipa_access_order", DP_OPT_STRING, { "expire" }, NULL_STRING }, + DP_OPTION_TERMINATOR +}; + +struct dp_option ipa_dyndns_opts[] = { + { "dyndns_update", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "dyndns_update_per_family", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "dyndns_refresh_interval", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER }, + { "dyndns_refresh_interval_offset", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER }, + { "dyndns_iface", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "dyndns_ttl", DP_OPT_NUMBER, { .number = 1200 }, NULL_NUMBER }, + { "dyndns_update_ptr", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "dyndns_force_tcp", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "dyndns_auth", DP_OPT_STRING, { "gss-tsig" }, NULL_STRING }, + { "dyndns_auth_ptr", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "dyndns_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + DP_OPTION_TERMINATOR +}; + +struct dp_option ipa_def_ldap_opts[] = { + { "ldap_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_backup_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_default_bind_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_default_authtok_type", DP_OPT_STRING, NULL_STRING, NULL_STRING}, + { "ldap_default_authtok", DP_OPT_BLOB, NULL_BLOB, NULL_BLOB }, + { "ldap_search_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "ldap_network_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "ldap_opt_timeout", DP_OPT_NUMBER, { .number = 8 }, NULL_NUMBER }, + { "ldap_tls_reqcert", DP_OPT_STRING, { "hard" }, NULL_STRING }, + { "ldap_user_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_user_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING }, + { "ldap_user_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_user_extra_attrs", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING }, + { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_host_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_service_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_full_refresh_interval", DP_OPT_NUMBER, { .number = 21600 }, NULL_NUMBER }, + { "ldap_sudo_smart_refresh_interval", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, /* 15 mins */ + { "ldap_sudo_random_offset", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, /* disabled */ + { "ldap_sudo_use_host_filter", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ldap_sudo_hostnames", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_ip", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sudo_include_netgroups", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ldap_sudo_include_regexp", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_autofs_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_autofs_map_master_name", DP_OPT_STRING, { "auto.master" }, NULL_STRING }, + { "ldap_iphost_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_ipnetwork_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_schema", DP_OPT_STRING, { "ipa_v1" }, NULL_STRING }, + { "ldap_pwmodify_mode", DP_OPT_STRING, { "exop" }, NULL_STRING }, + { "ldap_offline_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, + { "ldap_force_upper_case_realm", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "ldap_enumeration_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER }, + { "ldap_enumeration_refresh_offset", DP_OPT_NUMBER, { .number = 30 }, NULL_NUMBER }, + { "ldap_purge_cache_timeout", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_purge_cache_offset", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_tls_cacert", DP_OPT_STRING, { "/etc/ipa/ca.crt" }, NULL_STRING }, + { "ldap_tls_cacertdir", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_cert", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_key", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_tls_cipher_suite", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_id_use_start_tls", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_id_mapping", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_sasl_mech", DP_OPT_STRING, { "GSSAPI" } , NULL_STRING }, + { "ldap_sasl_authid", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sasl_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_sasl_minssf", DP_OPT_NUMBER, { .number = 56 }, NULL_NUMBER }, + { "ldap_sasl_maxssf", DP_OPT_NUMBER, { .number = -1 }, NULL_NUMBER }, + { "ldap_krb5_keytab", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_krb5_init_creds", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + /* use the same parm name as the krb5 module so we set it only once */ + { "krb5_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_backup_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_canonicalize", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_use_kdcinfo", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_kdcinfo_lookahead", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_pwd_policy", DP_OPT_STRING, { "none" } , NULL_STRING }, + { "ldap_referrals", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "account_cache_expiration", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_dns_service_name", DP_OPT_STRING, { SSS_LDAP_SRV_NAME }, NULL_STRING }, + { "ldap_krb5_ticket_lifetime", DP_OPT_NUMBER, { .number = (24 * 60 * 60) }, NULL_NUMBER }, + { "ldap_access_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_netgroup_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_group_nesting_level", DP_OPT_NUMBER, { .number = 2 }, NULL_NUMBER }, + { "ldap_deref", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_account_expire_policy", DP_OPT_STRING, { "ipa" }, NULL_STRING }, + { "ldap_access_order", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_backup_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_dns_service_name", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_chpass_update_last_change", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_enumeration_search_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER }, + /* Do not include ldap_auth_disable_tls_never_use_in_production in the + * manpages or SSSDConfig API + */ + { "ldap_auth_disable_tls_never_use_in_production", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_page_size", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER }, + { "ldap_deref_threshold", DP_OPT_NUMBER, { .number = 10 }, NULL_NUMBER }, + { "ldap_ignore_unreadable_references", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_sasl_canonicalize", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_connection_expire_timeout", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, + { "ldap_connection_expire_offset", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER }, + { "ldap_connection_idle_timeout", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, + { "ldap_disable_paging", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_idmap_range_min", DP_OPT_NUMBER, { .number = 200000 }, NULL_NUMBER }, + { "ldap_idmap_range_max", DP_OPT_NUMBER, { .number = 2000200000LL }, NULL_NUMBER }, + { "ldap_idmap_range_size", DP_OPT_NUMBER, { .number = 200000 }, NULL_NUMBER }, + { "ldap_idmap_autorid_compat", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_idmap_default_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_idmap_default_domain_sid", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ldap_idmap_helper_table_size", DP_OPT_NUMBER, { .number = 10 }, NULL_NUMBER }, + { "ldap_use_tokengroups", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE}, + { "ldap_rfc2307_fallback_to_local_users", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_disable_range_retrieval", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "ldap_min_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + { "ldap_max_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + { "ldap_pwdlockout_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "wildcard_limit", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER}, + { "ldap_library_debug_level", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER}, + DP_OPTION_TERMINATOR +}; + +struct sdap_attr_map ipa_attr_map[] = { + { "ldap_entry_usn", "entryUSN", SYSDB_USN, NULL }, + { "ldap_rootdse_last_usn", "lastUSN", SYSDB_HIGH_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_user_map[] = { + { "ldap_user_object_class", "posixAccount", SYSDB_USER_CLASS, NULL }, + { "ldap_user_name", "uid", SYSDB_NAME, NULL }, + { "ldap_user_pwd", "userPassword", SYSDB_PWD, NULL }, + { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL }, + { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL }, + { "ldap_user_home_directory", "homeDirectory", SYSDB_HOMEDIR, NULL }, + { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL }, + { "ldap_user_principal", "krbPrincipalName", SYSDB_UPN, NULL }, + { "ldap_user_fullname", "cn", SYSDB_FULLNAME, NULL }, + { "ldap_user_member_of", "memberOf", SYSDB_MEMBEROF, NULL }, + { "ldap_user_uuid", "ipaUniqueID", SYSDB_UUID, NULL }, + { "ldap_user_objectsid", "ipaNTSecurityIdentifier", SYSDB_SID_STR, NULL }, + { "ldap_user_primary_group", NULL, SYSDB_PRIMARY_GROUP, NULL }, + { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_user_entry_usn", NULL, SYSDB_USN, NULL }, + { "ldap_user_shadow_last_change", "shadowLastChange", SYSDB_SHADOWPW_LASTCHANGE, NULL }, + { "ldap_user_shadow_min", "shadowMin", SYSDB_SHADOWPW_MIN, NULL }, + { "ldap_user_shadow_max", "shadowMax", SYSDB_SHADOWPW_MAX, NULL }, + { "ldap_user_shadow_warning", "shadowWarning", SYSDB_SHADOWPW_WARNING, NULL }, + { "ldap_user_shadow_inactive", "shadowInactive", SYSDB_SHADOWPW_INACTIVE, NULL }, + { "ldap_user_shadow_expire", "shadowExpire", SYSDB_SHADOWPW_EXPIRE, NULL }, + { "ldap_user_shadow_flag", "shadowFlag", SYSDB_SHADOWPW_FLAG, NULL }, + { "ldap_user_krb_last_pwd_change", "krbLastPwdChange", SYSDB_KRBPW_LASTCHANGE, NULL }, + { "ldap_user_krb_password_expiration", "krbPasswordExpiration", SYSDB_KRBPW_EXPIRATION, NULL }, + { "ldap_pwd_attribute", "pwdAttribute", SYSDB_PWD_ATTRIBUTE, NULL }, + { "ldap_user_authorized_service", "authorizedService", SYSDB_AUTHORIZED_SERVICE, NULL }, + { "ldap_user_ad_account_expires", "accountExpires", SYSDB_AD_ACCOUNT_EXPIRES, NULL}, + { "ldap_user_ad_user_account_control", "userAccountControl", SYSDB_AD_USER_ACCOUNT_CONTROL, NULL}, + { "ldap_ns_account_lock", "nsAccountLock", SYSDB_NS_ACCOUNT_LOCK, NULL}, + { "ldap_user_authorized_host", "host", SYSDB_AUTHORIZED_HOST, NULL }, + { "ldap_user_authorized_rhost", NULL, SYSDB_AUTHORIZED_RHOST, NULL }, + { "ldap_user_nds_login_disabled", "loginDisabled", SYSDB_NDS_LOGIN_DISABLED, NULL }, + { "ldap_user_nds_login_expiration_time", "loginExpirationTime", SYSDB_NDS_LOGIN_EXPIRATION_TIME, NULL }, + { "ldap_user_nds_login_allowed_time_map", "loginAllowedTimeMap", SYSDB_NDS_LOGIN_ALLOWED_TIME_MAP, NULL }, + { "ldap_user_ssh_public_key", "ipaSshPubKey", SYSDB_SSH_PUBKEY, NULL }, + { "ldap_user_auth_type", "ipaUserAuthType", SYSDB_AUTH_TYPE, NULL }, + { "ldap_user_certificate", "userCertificate;binary", SYSDB_USER_CERT, NULL }, + { "ldap_user_email", "mail", SYSDB_USER_EMAIL, NULL }, + { "ldap_user_passkey", "ipaPassKey", SYSDB_USER_PASSKEY, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_group_map[] = { + { "ldap_group_object_class", "ipaUserGroup", SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_object_class_alt", "posixGroup", SYSDB_GROUP_CLASS, NULL }, + { "ldap_group_name", "cn", SYSDB_NAME, NULL }, + { "ldap_group_pwd", "userPassword", SYSDB_PWD, NULL }, + { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_group_member", "member", SYSDB_MEMBER, NULL }, + { "ldap_group_uuid", "ipaUniqueID", SYSDB_UUID, NULL }, + { "ldap_group_objectsid", "ipaNTSecurityIdentifier", SYSDB_SID_STR, NULL }, + { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_group_entry_usn", NULL, SYSDB_USN, NULL }, + { "ldap_group_type", NULL, SYSDB_GROUP_TYPE, NULL }, + { "ldap_group_external_member", "ipaExternalMember", SYSDB_EXTERNAL_MEMBER, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_netgroup_map[] = { + { "ipa_netgroup_object_class", "ipaNisNetgroup", SYSDB_NETGROUP_CLASS, NULL }, + { "ipa_netgroup_name", "cn", SYSDB_NAME, NULL }, + { "ipa_netgroup_member", "member", SYSDB_ORIG_NETGROUP_MEMBER, NULL }, + { "ipa_netgroup_member_of", "memberOf", SYSDB_MEMBEROF, NULL }, + { "ipa_netgroup_member_user", "memberUser", SYSDB_ORIG_MEMBER_USER, NULL }, + { "ipa_netgroup_member_host", "memberHost", SYSDB_ORIG_MEMBER_HOST, NULL }, + { "ipa_netgroup_member_ext_host", "externalHost", SYSDB_ORIG_NETGROUP_EXTERNAL_HOST, NULL }, + { "ipa_netgroup_domain", "nisDomainName", SYSDB_NETGROUP_DOMAIN, NULL }, + { "ipa_netgroup_uuid", "ipaUniqueID", SYSDB_UUID, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_subid_map[] = { + { "ipa_subuid_object_class", "ipasubordinateid", SYSDB_SUBID_RANGE_OC, NULL }, + { "ipa_subuid_count", "ipaSubUidCount", SYSDB_SUBID_UID_COUND, NULL }, + { "ipa_subgid_count", "ipaSubGidCount", SYSDB_SUBID_GID_COUNT, NULL }, + { "ipa_subuid_number", "ipaSubUidNumber", SYSDB_SUBID_UID_NUMBER, NULL }, + { "ipa_subgid_number", "ipaSubGidNumber", SYSDB_SUBID_GID_NUMBER, NULL }, + { "ipa_owner", "ipaOwner", SYSDB_SUBID_OWNER, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_host_map[] = { + { "ipa_host_object_class", "ipaHost", SYSDB_HOST_CLASS, NULL }, + { "ipa_host_name", "cn", SYSDB_NAME, NULL }, + { "ipa_host_fqdn", "fqdn", SYSDB_FQDN, NULL }, + { "ipa_host_serverhostname", "serverHostname", SYSDB_SERVERHOSTNAME, NULL }, + { "ipa_host_member_of", "memberOf", SYSDB_ORIG_MEMBEROF, NULL }, + { "ipa_host_ssh_public_key", "ipaSshPubKey", SYSDB_SSH_PUBKEY, NULL }, + { "ipa_host_uuid", "ipaUniqueID", SYSDB_UUID, NULL}, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_hostgroup_map[] = { + { "ipa_hostgroup_objectclass", "ipaHostgroup", SYSDB_HOSTGROUP_CLASS, NULL}, + { "ipa_hostgroup_name", "cn", SYSDB_NAME, NULL}, + { "ipa_hostgroup_memberof", "memberOf", SYSDB_ORIG_MEMBEROF, NULL}, + { "ipa_hostgroup_uuid", "ipaUniqueID", SYSDB_UUID, NULL}, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_selinux_user_map[] = { + { "ipa_selinux_usermap_object_class", "ipaselinuxusermap", SYSDB_SELINUX_USERMAP_CLASS, NULL}, + { "ipa_selinux_usermap_name", "cn", SYSDB_NAME, NULL}, + { "ipa_selinux_usermap_member_user", "memberUser", SYSDB_ORIG_MEMBER_USER, NULL}, + { "ipa_selinux_usermap_member_host", "memberHost", SYSDB_ORIG_MEMBER_HOST, NULL}, + { "ipa_selinux_usermap_see_also", "seeAlso", SYSDB_SELINUX_SEEALSO, NULL}, + { "ipa_selinux_usermap_selinux_user", "ipaSELinuxUser", SYSDB_SELINUX_USER, NULL}, + { "ipa_selinux_usermap_enabled", "ipaEnabledFlag", SYSDB_SELINUX_ENABLED, NULL}, + { "ipa_selinux_usermap_user_category", "userCategory", SYSDB_USER_CATEGORY, NULL}, + { "ipa_selinux_usermap_host_category", "hostCategory", SYSDB_HOST_CATEGORY, NULL}, + { "ipa_selinux_usermap_uuid", "ipaUniqueID", SYSDB_UUID, NULL}, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_view_map[] = { + { "ipa_view_class", "nsContainer", SYSDB_VIEW_CLASS, NULL}, + { "ipa_view_name", "cn", SYSDB_VIEW_NAME, NULL}, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_override_map[] = { + { "ipa_override_object_class", "ipaOverrideAnchor", SYSDB_OVERRIDE_CLASS, NULL}, + { "ipa_anchor_uuid", "ipaAnchorUUID", SYSDB_OVERRIDE_ANCHOR_UUID, NULL}, + { "ipa_user_override_object_class", "ipaUserOverride", SYSDB_OVERRIDE_USER_CLASS, NULL}, + { "ipa_group_override_object_class", "ipaGroupOverride", SYSDB_OVERRIDE_GROUP_CLASS, NULL}, + { "ldap_user_name", "uid", SYSDB_NAME, NULL }, + { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL }, + { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL }, + { "ldap_user_home_directory", "homeDirectory", SYSDB_HOMEDIR, NULL }, + { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL }, + { "ldap_group_name", "cn", SYSDB_NAME, NULL }, + { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL }, + { "ldap_user_ssh_public_key", "ipaSshPubKey", SYSDB_SSH_PUBKEY, NULL }, + { "ldap_user_certificate", "userCertificate;binary", SYSDB_USER_CERT, NULL }, + { "", "objectClass", SYSDB_ORIG_OBJECTCLASS, NULL }, /* We don't want this to be configurable */ + SDAP_ATTR_MAP_TERMINATOR +}; + +struct dp_option ipa_def_krb5_opts[] = { + { "krb5_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_backup_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_ccachedir", DP_OPT_STRING, { DEFAULT_CCACHE_DIR }, NULL_STRING }, + { "krb5_ccname_template", DP_OPT_STRING, NULL_STRING, NULL_STRING}, + { "krb5_auth_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, + { "krb5_keytab", DP_OPT_STRING, { "/etc/krb5.keytab" }, NULL_STRING }, + { "krb5_validate", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_kpasswd", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_backup_kpasswd", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_store_password_if_offline", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "krb5_renewable_lifetime", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_lifetime", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_renew_interval", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_use_fast", DP_OPT_STRING, { "try" }, NULL_STRING }, + { "krb5_fast_principal", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_fast_use_anonymous_pkinit", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "krb5_canonicalize", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_use_enterprise_principal", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "krb5_use_kdcinfo", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }, + { "krb5_kdcinfo_lookahead", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_map_user", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_use_subdomain_realm", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + DP_OPTION_TERMINATOR +}; + +struct sdap_attr_map ipa_service_map[] = { + { "ldap_service_object_class", "ipService", SYSDB_SVC_CLASS, NULL }, + { "ldap_service_name", "cn", SYSDB_NAME, NULL }, + { "ldap_service_port", "ipServicePort", SYSDB_SVC_PORT, NULL }, + { "ldap_service_proto", "ipServiceProtocol", SYSDB_SVC_PROTO, NULL }, + { "ldap_service_entry_usn", NULL, SYSDB_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_autofs_mobject_map[] = { + { "ldap_autofs_map_object_class", "automountMap", SYSDB_AUTOFS_MAP_OC, NULL }, + { "ldap_autofs_map_name", "automountMapName", SYSDB_AUTOFS_MAP_NAME, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_autofs_entry_map[] = { + { "ldap_autofs_entry_object_class", "automount", SYSDB_AUTOFS_ENTRY_OC, NULL }, + { "ldap_autofs_entry_key", "automountKey", SYSDB_AUTOFS_ENTRY_KEY, NULL }, + { "ldap_autofs_entry_value", "automountInformation", SYSDB_AUTOFS_ENTRY_VALUE, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_sudorule_map[] = { + { "ipa_sudorule_object_class", "ipasudorule", SYSDB_IPA_SUDORULE_OC, NULL }, + { "ipa_sudorule_name", "cn", SYSDB_NAME, NULL }, + { "ipa_sudorule_uuid", "ipaUniqueID", SYSDB_UUID, NULL }, + { "ipa_sudorule_enabled_flag", "ipaEnabledFlag", SYSDB_IPA_SUDORULE_ENABLED, NULL }, + { "ipa_sudorule_option", "ipaSudoOpt", SYSDB_IPA_SUDORULE_OPTION, NULL }, + { "ipa_sudorule_runasuser", "ipaSudoRunAs", SYSDB_IPA_SUDORULE_RUNASUSER, NULL }, + { "ipa_sudorule_runasgroup", "ipaSudoRunAsGroup", SYSDB_IPA_SUDORULE_RUNASGROUP, NULL }, + { "ipa_sudorule_allowcmd", "memberAllowCmd", SYSDB_IPA_SUDORULE_ALLOWCMD, NULL }, + { "ipa_sudorule_denycmd", "memberDenyCmd", SYSDB_IPA_SUDORULE_DENYCMD, NULL }, + { "ipa_sudorule_host", "memberHost", SYSDB_IPA_SUDORULE_HOST, NULL }, + { "ipa_sudorule_user", "memberUser", SYSDB_IPA_SUDORULE_USER, NULL }, + { "ipa_sudorule_notafter", "sudoNotAfter", SYSDB_IPA_SUDORULE_NOTAFTER, NULL }, + { "ipa_sudorule_notbefore", "sudoNotBefore", SYSDB_IPA_SUDORULE_NOTBEFORE, NULL }, + { "ipa_sudorule_sudoorder", "sudoOrder", SYSDB_IPA_SUDORULE_SUDOORDER, NULL }, + { "ipa_sudorule_cmdcategory", "cmdCategory", SYSDB_IPA_SUDORULE_CMDCATEGORY, NULL }, + { "ipa_sudorule_hostcategory", "hostCategory", SYSDB_IPA_SUDORULE_HOSTCATEGORY, NULL }, + { "ipa_sudorule_usercategory", "userCategory", SYSDB_IPA_SUDORULE_USERCATEGORY, NULL }, + { "ipa_sudorule_runasusercategory", "ipaSudoRunAsUserCategory", SYSDB_IPA_SUDORULE_RUNASUSERCATEGORY, NULL }, + { "ipa_sudorule_runasgroupcategory", "ipaSudoRunAsGroupCategory", SYSDB_IPA_SUDORULE_RUNASGROUPCATEGORY, NULL }, + { "ipa_sudorule_runasextuser", "ipaSudoRunAsExtUser", SYSDB_IPA_SUDORULE_RUNASEXTUSER, NULL }, + { "ipa_sudorule_runasextgroup", "ipaSudoRunAsExtGroup", SYSDB_IPA_SUDORULE_RUNASEXTGROUP, NULL }, + { "ipa_sudorule_runasextusergroup", "ipaSudoRunAsExtUserGroup", SYSDB_IPA_SUDORULE_RUNASEXTUSERGROUP, NULL }, + { "ipa_sudorule_externaluser", "externalUser", SYSDB_IPA_SUDORULE_EXTUSER, NULL }, + { "ipa_sudorule_entry_usn", "entryUSN", SYSDB_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_sudocmdgroup_map[] = { + { "ipa_sudocmdgroup_object_class", "ipasudocmdgrp", SYSDB_IPA_SUDOCMDGROUP_OC, NULL }, + { "ipa_sudocmdgroup_uuid", "ipaUniqueID", SYSDB_UUID, NULL }, + { "ipa_sudocmdgroup_name", "cn", SYSDB_NAME, NULL }, + { "ipa_sudocmdgroup_member", "member", SYSDB_MEMBER, NULL }, + { "ipa_sudocmdgroup_entry_usn", "entryUSN", SYSDB_USN, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct sdap_attr_map ipa_sudocmd_map[] = { + { "ipa_sudocmd_object_class", "ipasudocmd", SYSDB_IPA_SUDOCMD_OC, NULL }, + { "ipa_sudocmd_uuid", "ipaUniqueID", SYSDB_UUID, NULL }, + { "ipa_sudocmd_sudoCmd", "sudoCmd", SYSDB_IPA_SUDOCMD_SUDOCMD, NULL }, + { "ipa_sudocmd_memberof", "memberOf", SYSDB_MEMBEROF, NULL }, + SDAP_ATTR_MAP_TERMINATOR +}; + +struct dp_option ipa_cli_ad_subdom_opts [] = { + { "ad_server", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "ad_site", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + DP_OPTION_TERMINATOR +}; diff --git a/src/providers/ipa/ipa_opts.h b/src/providers/ipa/ipa_opts.h new file mode 100644 index 0000000..6f54e57 --- /dev/null +++ b/src/providers/ipa/ipa_opts.h @@ -0,0 +1,71 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2012 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef IPA_OPTS_H_ +#define IPA_OPTS_H_ + +#include "src/providers/data_provider.h" +#include "providers/ldap/ldap_common.h" + +extern struct dp_option ipa_basic_opts[]; + +extern struct dp_option ipa_dyndns_opts[]; + +extern struct dp_option ipa_def_ldap_opts[]; + +extern struct sdap_attr_map ipa_attr_map[]; + +extern struct sdap_attr_map ipa_user_map[]; + +extern struct sdap_attr_map ipa_group_map[]; + +extern struct sdap_attr_map ipa_netgroup_map[]; + +extern struct sdap_attr_map ipa_subid_map[]; + +extern struct sdap_attr_map ipa_host_map[]; + +extern struct sdap_attr_map ipa_hostgroup_map[]; + +extern struct sdap_attr_map ipa_selinux_user_map[]; + +extern struct sdap_attr_map ipa_view_map[]; + +extern struct sdap_attr_map ipa_override_map[]; + +extern struct dp_option ipa_def_krb5_opts[]; + +extern struct sdap_attr_map ipa_service_map[]; + +extern struct sdap_attr_map ipa_autofs_mobject_map[]; + +extern struct sdap_attr_map ipa_autofs_entry_map[]; + +extern struct sdap_attr_map ipa_sudorule_map[]; + +extern struct sdap_attr_map ipa_sudocmdgroup_map[]; + +extern struct sdap_attr_map ipa_sudocmd_map[]; + +extern struct dp_option ipa_cli_ad_subdom_opts[]; + +#endif /* IPA_OPTS_H_ */ diff --git a/src/providers/ipa/ipa_refresh.c b/src/providers/ipa/ipa_refresh.c new file mode 100644 index 0000000..64f8db8 --- /dev/null +++ b/src/providers/ipa/ipa_refresh.c @@ -0,0 +1,220 @@ +/* + Copyright (C) 2019 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <talloc.h> +#include <tevent.h> + +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_id.h" + +struct ipa_refresh_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct dp_id_data *account_req; + struct ipa_id_ctx *id_ctx; + struct sss_domain_info *domain; + char **names; + size_t index; +}; + +static errno_t ipa_refresh_step(struct tevent_req *req); +static void ipa_refresh_done(struct tevent_req *subreq); + +static struct tevent_req *ipa_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + int entry_type, + char **names, + void *pvt) +{ + struct ipa_refresh_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (names == NULL) { + ret = EOK; + goto immediately; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->domain = domain; + state->id_ctx = talloc_get_type(pvt, struct ipa_id_ctx); + state->names = names; + state->index = 0; + + state->account_req = be_refresh_acct_req(state, entry_type, + BE_FILTER_NAME, domain); + if (state->account_req == NULL) { + ret = ENOMEM; + goto immediately; + } + + ret = ipa_refresh_step(req); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Nothing to refresh\n"); + goto immediately; + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_CRIT_FAILURE, "ipa_refresh_step() failed " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t ipa_refresh_step(struct tevent_req *req) +{ + struct ipa_refresh_state *state = NULL; + struct tevent_req *subreq = NULL; + errno_t ret; + + state = tevent_req_data(req, struct ipa_refresh_state); + + if (state->names == NULL) { + ret = EOK; + goto done; + } + + state->account_req->filter_value = state->names[state->index]; + if (state->account_req->filter_value == NULL) { + ret = EOK; + goto done; + } + + subreq = ipa_account_info_send(state, state->be_ctx, state->id_ctx, + state->account_req); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ipa_refresh_done, req); + + state->index++; + ret = EAGAIN; + +done: + return ret; +} + +static void ipa_refresh_done(struct tevent_req *subreq) +{ + struct ipa_refresh_state *state = NULL; + struct tevent_req *req = NULL; + errno_t dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_refresh_state); + + ret = ipa_account_info_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to refresh %s [dp_error: %d, " + "errno: %d]\n", be_req2str(state->account_req->entry_type), + dp_error, ret); + goto done; + } + + if (state->account_req->entry_type == BE_REQ_INITGROUPS) { + ret = sysdb_set_initgr_expire_timestamp(state->domain, + state->account_req->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to set initgroups expiration for [%s]\n", + state->account_req->filter_value); + } + } + + ret = ipa_refresh_step(req); + if (ret == EAGAIN) { + return; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_refresh_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +REFRESH_SEND_RECV_FNS(ipa_refresh_initgroups, ipa_refresh, BE_REQ_INITGROUPS); +REFRESH_SEND_RECV_FNS(ipa_refresh_users, ipa_refresh, BE_REQ_USER); +REFRESH_SEND_RECV_FNS(ipa_refresh_groups, ipa_refresh, BE_REQ_GROUP); +REFRESH_SEND_RECV_FNS(ipa_refresh_netgroups, ipa_refresh, BE_REQ_NETGROUP); + +errno_t ipa_refresh_init(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx) +{ + errno_t ret; + struct be_refresh_cb ipa_refresh_callbacks[] = { + { .send_fn = ipa_refresh_initgroups_send, + .recv_fn = ipa_refresh_initgroups_recv, + .pvt = id_ctx, + }, + { .send_fn = ipa_refresh_users_send, + .recv_fn = ipa_refresh_users_recv, + .pvt = id_ctx, + }, + { .send_fn = ipa_refresh_groups_send, + .recv_fn = ipa_refresh_groups_recv, + .pvt = id_ctx, + }, + { .send_fn = ipa_refresh_netgroups_send, + .recv_fn = ipa_refresh_netgroups_recv, + .pvt = id_ctx, + }, + }; + + ret = be_refresh_ctx_init_with_callbacks(be_ctx, + SYSDB_NAME, + ipa_refresh_callbacks); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize background refresh\n"); + return ret; + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_rules_common.c b/src/providers/ipa/ipa_rules_common.c new file mode 100644 index 0000000..1182347 --- /dev/null +++ b/src/providers/ipa/ipa_rules_common.c @@ -0,0 +1,455 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2011 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "providers/ipa/ipa_rules_common.h" + +static errno_t +ipa_common_save_list(struct sss_domain_info *domain, + bool delete_subdir, + const char *subdir, + const char *naming_attribute, + size_t count, + struct sysdb_attrs **list) +{ + int ret; + size_t c; + struct ldb_dn *base_dn; + const char *object_name; + struct ldb_message_element *el; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + if (delete_subdir) { + base_dn = sysdb_custom_subtree_dn(tmp_ctx, domain, subdir); + if (base_dn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_delete_recursive(domain->sysdb, base_dn, true); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_delete_recursive failed.\n"); + goto done; + } + } + + for (c = 0; c < count; c++) { + ret = sysdb_attrs_get_el(list[c], naming_attribute, &el); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_get_el failed.\n"); + goto done; + } + if (el->num_values == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "[%s] not found.\n", naming_attribute); + ret = EINVAL; + goto done; + } + object_name = talloc_strndup(tmp_ctx, (const char *)el->values[0].data, + el->values[0].length); + if (object_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Object name: [%s].\n", object_name); + + ret = sysdb_store_custom(domain, object_name, subdir, list[c]); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_store_custom failed.\n"); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ipa_common_entries_and_groups_sysdb_save(struct sss_domain_info *domain, + const char *primary_subdir, + const char *attr_name, + size_t primary_count, + struct sysdb_attrs **primary, + const char *group_subdir, + const char *groupattr_name, + size_t group_count, + struct sysdb_attrs **groups) +{ + errno_t ret, sret; + bool in_transaction = false; + + if ((primary_count == 0 || primary == NULL) + || (group_count > 0 && groups == NULL)) { + /* There always has to be at least one + * primary entry. + */ + return EINVAL; + } + + /* Save the entries and groups to the cache */ + ret = sysdb_transaction_start(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + }; + in_transaction = true; + + /* First, save the specific entries */ + ret = ipa_common_save_list(domain, true, primary_subdir, + attr_name, primary_count, primary); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not save %s. [%d][%s]\n", + primary_subdir, ret, strerror(ret)); + goto done; + } + + /* Second, save the groups */ + if (group_count > 0) { + ret = ipa_common_save_list(domain, true, group_subdir, + groupattr_name, group_count, groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not save %s. [%d][%s]\n", + group_subdir, ret, strerror(ret)); + goto done; + } + } + + ret = sysdb_transaction_commit(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(domain->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not cancel sysdb transaction\n"); + } + } + + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Error [%d][%s]\n", ret, strerror(ret)); + } + return ret; +} + +errno_t +ipa_common_get_cached_rules(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule, + const char *subtree_name, + const char **attrs, + size_t *_rule_count, + struct sysdb_attrs ***_rules) +{ + errno_t ret; + struct ldb_message **msgs; + struct sysdb_attrs **rules; + size_t rule_count; + TALLOC_CTX *tmp_ctx; + char *filter; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + filter = talloc_asprintf(tmp_ctx, "(objectClass=%s)", rule); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_custom(tmp_ctx, domain, filter, + subtree_name, attrs, + &rule_count, &msgs); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error looking up HBAC rules\n"); + goto done; + } + + if (ret == ENOENT) { + rule_count = 0; + } + + ret = sysdb_msg2attrs(tmp_ctx, rule_count, msgs, &rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not convert ldb message to sysdb_attrs\n"); + goto done; + } + + if (_rules) { + *_rules = talloc_steal(mem_ctx, rules); + } + + if (_rule_count) { + *_rule_count = rule_count; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ipa_common_purge_rules(struct sss_domain_info *domain, + const char *subtree_name) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *base_dn; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + base_dn = sysdb_custom_subtree_dn(tmp_ctx, domain, subtree_name); + if (base_dn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_delete_recursive(domain->sysdb, base_dn, true); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_delete_recursive failed.\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t ipa_common_save_rules(struct sss_domain_info *domain, + struct ipa_common_entries *hosts, + struct ipa_common_entries *services, + struct ipa_common_entries *rules, + time_t *last_update) +{ + bool in_transaction = false; + errno_t ret; + errno_t sret; + + ret = sysdb_transaction_start(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not start transaction\n"); + goto done; + } + in_transaction = true; + + /* Save the hosts */ + if (hosts != NULL) { + ret = ipa_common_entries_and_groups_sysdb_save(domain, + hosts->entry_subdir, + SYSDB_FQDN, + hosts->entry_count, + hosts->entries, + hosts->group_subdir, + SYSDB_NAME, + hosts->group_count, + hosts->groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error saving hosts [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + /* Save the services */ + if (services != NULL) { + ret = ipa_common_entries_and_groups_sysdb_save(domain, + services->entry_subdir, + IPA_CN, + services->entry_count, + services->entries, + services->group_subdir, + IPA_CN, + services->group_count, + services->groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error saving services [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + /* Save the rules */ + if (rules != NULL) { + ret = ipa_common_entries_and_groups_sysdb_save(domain, + rules->entry_subdir, + IPA_UNIQUE_ID, + rules->entry_count, + rules->entries, + NULL, NULL, 0, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error saving rules [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + ret = sysdb_transaction_commit(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + *last_update = time(NULL); + + ret = EOK; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(domain->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not cancel transaction\n"); + } + } + + return ret; +} + +errno_t +ipa_common_get_hostgroupname(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *host_dn, + char **_hostgroupname) +{ + errno_t ret; + struct ldb_dn *dn; + const char *rdn_name; + const char *hostgroup_comp_name; + const char *account_comp_name; + const struct ldb_val *rdn_val; + const struct ldb_val *hostgroup_comp_val; + const struct ldb_val *account_comp_val; + + /* This is an IPA-specific hack. It may not + * work for non-IPA servers and will need to + * be changed if SSSD ever supports HBAC on + * a non-IPA server. + */ + *_hostgroupname = NULL; + + dn = ldb_dn_new(mem_ctx, sysdb_ctx_get_ldb(sysdb), host_dn); + if (dn == NULL) { + ret = ENOMEM; + goto done; + } + + if (!ldb_dn_validate(dn)) { + ret = ERR_MALFORMED_ENTRY; + goto done; + } + + if (ldb_dn_get_comp_num(dn) < 4) { + /* RDN, hostgroups, accounts, and at least one DC= */ + /* If it's fewer, it's not a group DN */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* If the RDN name is 'cn' */ + rdn_name = ldb_dn_get_rdn_name(dn); + if (rdn_name == NULL) { + /* Shouldn't happen if ldb_dn_validate() + * passed, but we'll be careful. + */ + ret = ERR_MALFORMED_ENTRY; + goto done; + } + + if (strcasecmp("cn", rdn_name) != 0) { + /* RDN has the wrong attribute name. + * It's not a host. + */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* and the second component is "cn=hostgroups" */ + hostgroup_comp_name = ldb_dn_get_component_name(dn, 1); + if (strcasecmp("cn", hostgroup_comp_name) != 0) { + /* The second component name is not "cn" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + hostgroup_comp_val = ldb_dn_get_component_val(dn, 1); + if (strncasecmp("hostgroups", + (const char *) hostgroup_comp_val->data, + hostgroup_comp_val->length) != 0) { + /* The second component value is not "hostgroups" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* and the third component is "accounts" */ + account_comp_name = ldb_dn_get_component_name(dn, 2); + if (strcasecmp("cn", account_comp_name) != 0) { + /* The third component name is not "cn" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + account_comp_val = ldb_dn_get_component_val(dn, 2); + if (strncasecmp("accounts", + (const char *) account_comp_val->data, + account_comp_val->length) != 0) { + /* The third component value is not "accounts" */ + ret = ERR_UNEXPECTED_ENTRY_TYPE; + goto done; + } + + /* Then the value of the RDN is the group name */ + rdn_val = ldb_dn_get_rdn_val(dn); + *_hostgroupname = talloc_strndup(mem_ctx, + (const char *)rdn_val->data, + rdn_val->length); + if (*_hostgroupname == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + talloc_free(dn); + return ret; +} diff --git a/src/providers/ipa/ipa_rules_common.h b/src/providers/ipa/ipa_rules_common.h new file mode 100644 index 0000000..6cf57eb --- /dev/null +++ b/src/providers/ipa/ipa_rules_common.h @@ -0,0 +1,89 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2011 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef IPA_RULES_COMMON_H_ +#define IPA_RULES_COMMON_H_ + +#include "providers/backend.h" + +#define IPA_UNIQUE_ID "ipauniqueid" + +#define OBJECTCLASS "objectclass" +#define IPA_MEMBER_USER "memberUser" +#define IPA_USER_CATEGORY "userCategory" +#define IPA_EXTERNAL_HOST "externalHost" +#define IPA_ENABLED_FLAG "ipaenabledflag" +#define IPA_MEMBER_HOST "memberHost" +#define IPA_HOST_CATEGORY "hostCategory" +#define IPA_CN "cn" +#define IPA_TRUE_VALUE "TRUE" + +/* From ipa_rules_common.c */ + +struct ipa_common_entries { + const char *entry_subdir; + size_t entry_count; + struct sysdb_attrs **entries; + + const char *group_subdir; + size_t group_count; + struct sysdb_attrs **groups; +}; + +errno_t +ipa_common_entries_and_groups_sysdb_save(struct sss_domain_info *domain, + const char *primary_subdir, + const char *attr_name, + size_t primary_count, + struct sysdb_attrs **primary, + const char *group_subdir, + const char *groupattr_name, + size_t group_count, + struct sysdb_attrs **groups); + +errno_t +ipa_common_get_cached_rules(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *rule, + const char *subtree_name, + const char **attrs, + size_t *_rule_count, + struct sysdb_attrs ***_rules); + +errno_t +ipa_common_purge_rules(struct sss_domain_info *domain, + const char *subtree_name); + +errno_t +ipa_common_save_rules(struct sss_domain_info *domain, + struct ipa_common_entries *hosts, + struct ipa_common_entries *services, + struct ipa_common_entries *rules, + time_t *last_update); + +errno_t +ipa_common_get_hostgroupname(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + const char *host_dn, + char **_hostgroupname); + +#endif /* IPA_RULES_COMMON_H_ */ diff --git a/src/providers/ipa/ipa_s2n_exop.c b/src/providers/ipa/ipa_s2n_exop.c new file mode 100644 index 0000000..ec944d9 --- /dev/null +++ b/src/providers/ipa/ipa_s2n_exop.c @@ -0,0 +1,3228 @@ +/* + SSSD + + IPA Helper routines - external users and groups with s2n plugin + + Copyright (C) Sumit Bose <sbose@redhat.com> - 2011 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "util/sss_nss.h" +#include "util/strtonum.h" +#include "util/crypto/sss_crypto.h" +#include "providers/ldap/sdap_async_private.h" +#include "providers/ldap/sdap_async_ad.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ipa/ipa_id.h" +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ad/ad_pac.h" +#include "db/sysdb.h" + +enum input_types { + INP_SID = 1, + INP_NAME, + INP_POSIX_UID, + INP_POSIX_GID, + INP_CERT, + INP_USERNAME, + INP_GROUPNAME +}; + +enum request_types { + REQ_SIMPLE = 1, + REQ_FULL, + REQ_FULL_WITH_MEMBERS +}; + +enum response_types { + RESP_SID = 1, + RESP_NAME, + RESP_USER, + RESP_GROUP, + RESP_USER_GROUPLIST, + RESP_GROUP_MEMBERS, + RESP_NAME_LIST +}; + +struct extdom_protocol_map_item { + int protocol; + const char *oid; +}; + +static struct extdom_protocol_map_item extdom_protocol_map[] = { + { EXTDOM_V2, EXOP_SID2NAME_V2_OID }, + { EXTDOM_V1, EXOP_SID2NAME_V1_OID }, + { EXTDOM_V0, EXOP_SID2NAME_OID }, + { EXTDOM_INVALID_VERSION, NULL } +}; + +static const char* extdom_protocol_to_oid(enum extdom_protocol protocol) +{ + int i; + + for (i = 0; extdom_protocol_map[i].protocol != EXTDOM_INVALID_VERSION; ++i) { + if (extdom_protocol_map[i].protocol == protocol) { + return extdom_protocol_map[i].oid; + } + } + + return NULL; +} + +static enum extdom_protocol extdom_oid_to_protocol(const char *oid) +{ + int i; + + if (oid == NULL) { + return EXTDOM_INVALID_VERSION; + } + + for (i = 0; extdom_protocol_map[i].protocol != EXTDOM_INVALID_VERSION; ++i) { + if (strcmp(extdom_protocol_map[i].oid, oid) == 0) { + return extdom_protocol_map[i].protocol; + } + } + + return EXTDOM_INVALID_VERSION; +} + +static enum extdom_protocol extdom_preferred_protocol(struct sdap_handle *sh) { + if (sdap_is_extension_supported(sh, EXOP_SID2NAME_V2_OID)) { + return EXTDOM_V2; + } + + if (sdap_is_extension_supported(sh, EXOP_SID2NAME_V1_OID)) { + return EXTDOM_V1; + } + + if (sdap_is_extension_supported(sh, EXOP_SID2NAME_OID)) { + return EXTDOM_V0; + } + + return EXTDOM_INVALID_VERSION; +} + +static const char *ipa_s2n_reqtype2str(enum request_types request_type) +{ + switch (request_type) { + case REQ_SIMPLE: + return "REQ_SIMPLE"; + case REQ_FULL: + return "REQ_FULL"; + case REQ_FULL_WITH_MEMBERS: + return "REQ_FULL_WITH_MEMBERS"; + default: + break; + } + + return "Unknown request type"; +} + +/* ==Sid2Name Extended Operation============================================= */ +struct ipa_s2n_exop_state { + struct sdap_handle *sh; + + struct sdap_op *op; + + char *retoid; + struct berval *retdata; +}; + +static void ipa_s2n_exop_done(struct sdap_op *op, + struct sdap_msg *reply, + int error, void *pvt); + +static struct tevent_req *ipa_s2n_exop_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + enum extdom_protocol protocol, + int timeout, + struct berval *bv, + const char *stat_info_in) +{ + struct tevent_req *req = NULL; + struct ipa_s2n_exop_state *state; + int ret; + int msgid; + char *stat_info; + + req = tevent_req_create(mem_ctx, &state, struct ipa_s2n_exop_state); + if (!req) return NULL; + + state->sh = sh; + state->retoid = NULL; + state->retdata = NULL; + + DEBUG(SSSDBG_TRACE_FUNC, "Executing extended operation\n"); + + ret = ldap_extended_operation(state->sh->ldap, + extdom_protocol_to_oid(protocol), + bv, NULL, NULL, &msgid); + 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] %s", + sdap_get_server_peer_str_safe(state->sh), + stat_info_in != NULL ? stat_info_in + : "IPA EXOP"); + 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, + ipa_s2n_exop_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 ipa_s2n_exop_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 ipa_s2n_exop_state *state = tevent_req_data(req, + struct ipa_s2n_exop_state); + int ret; + char *errmsg = NULL; + char *retoid = NULL; + struct berval *retdata = NULL; + int result; + + if (error) { + tevent_req_error(req, error); + return; + } + + ret = ldap_parse_result(state->sh->ldap, reply->msg, + &result, NULL, &errmsg, NULL, + NULL, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_result failed (%d)\n", + sdap_op_get_msgid(state->op)); + ret = ERR_NETWORK_IO; + goto done; + } + + DEBUG(((result == LDAP_SUCCESS) || (result == LDAP_NO_SUCH_OBJECT)) ? + SSSDBG_TRACE_FUNC : SSSDBG_OP_FAILURE, + "ldap_extended_operation result: %s(%d), %s.\n", + sss_ldap_err2string(result), result, errmsg); + + if (result != LDAP_SUCCESS) { + if (result == LDAP_NO_SUCH_OBJECT) { + ret = ENOENT; + } else { + DEBUG(SSSDBG_OP_FAILURE, "ldap_extended_operation failed, server " \ + "logs might contain more details.\n"); + ret = ERR_NETWORK_IO; + } + goto done; + } + + ret = ldap_parse_extended_result(state->sh->ldap, reply->msg, + &retoid, &retdata, 0); + if (ret != LDAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_extendend_result failed (%d)\n", + ret); + ret = ERR_NETWORK_IO; + goto done; + } + if (retdata == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing exop result data.\n"); + ret = EINVAL; + goto done; + } + + state->retoid = talloc_strdup(state, retoid); + if (state->retoid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + state->retdata = talloc(state, struct berval); + if (state->retdata == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto done; + } + state->retdata->bv_len = retdata->bv_len; + state->retdata->bv_val = talloc_memdup(state->retdata, retdata->bv_val, + retdata->bv_len); + if (state->retdata->bv_val == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_memdup failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + ldap_memfree(errmsg); + ldap_memfree(retoid); + ber_bvfree(retdata); + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static int ipa_s2n_exop_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + char **retoid, struct berval **retdata) +{ + struct ipa_s2n_exop_state *state = tevent_req_data(req, + struct ipa_s2n_exop_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *retoid = talloc_steal(mem_ctx, state->retoid); + *retdata = talloc_steal(mem_ctx, state->retdata); + + return EOK; +} + +static errno_t talloc_ber_flatten(TALLOC_CTX *mem_ctx, BerElement *ber, + struct berval **_bv) +{ + int ret; + struct berval *bv = NULL; + struct berval *tbv = NULL; + + ret = ber_flatten(ber, &bv); + if (ret == -1) { + ret = EFAULT; + goto done; + } + + tbv = talloc_zero(mem_ctx, struct berval); + if (tbv == NULL) { + ret = ENOMEM; + goto done; + } + + tbv->bv_len = bv->bv_len; + tbv->bv_val = talloc_memdup(tbv, bv->bv_val, bv->bv_len); + if (tbv->bv_val == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + ber_bvfree(bv); + if (ret == EOK) { + *_bv = tbv; + } else { + talloc_free(tbv); + } + + return ret; +} + +/* The extended operation expect the following ASN.1 encoded request data: + * + * ExtdomRequestValue ::= SEQUENCE { + * inputType ENUMERATED { + * sid (1), + * name (2), + * posix uid (3), + * posix gid (3) + * }, + * requestType ENUMERATED { + * simple (1), + * full (2) + * full_with_members (3) + * }, + * data InputData + * } + * + * InputData ::= CHOICE { + * sid OCTET STRING, + * name NameDomainData + * uid PosixUid, + * gid PosixGid + * } + * + * NameDomainData ::= SEQUENCE { + * domain_name OCTET STRING, + * object_name OCTET STRING + * } + * + * PosixUid ::= SEQUENCE { + * domain_name OCTET STRING, + * uid INTEGER + * } + * + * PosixGid ::= SEQUENCE { + * domain_name OCTET STRING, + * gid INTEGER + * } + * + */ + +static errno_t s2n_encode_request(TALLOC_CTX *mem_ctx, + const char *domain_name, + int entry_type, + enum request_types request_type, + struct req_input *req_input, + enum extdom_protocol protocol, + struct berval **_bv, + char **stat_info) +{ + BerElement *ber = NULL; + int ret; + char *info = NULL; + + if (protocol == EXTDOM_INVALID_VERSION) { + return EINVAL; + } + + ber = ber_alloc_t( LBER_USE_DER ); + if (ber == NULL) { + return ENOMEM; + } + + switch (entry_type) { + case BE_REQ_USER: + case BE_REQ_USER_AND_GROUP: /* the extdom V0/V1 exop does not care if + the ID belongs to a user or a group */ + if (req_input->type == REQ_INP_NAME) { + ret = ber_printf(ber, "{ee{ss}}", + (protocol == EXTDOM_V2 + ? INP_USERNAME : INP_NAME), + request_type, + domain_name, + req_input->inp.name); + info = talloc_asprintf(mem_ctx, + "EXTDOM EXPO request: [%s] domain: [%s] name: [%s]", + ipa_s2n_reqtype2str(request_type), + domain_name, req_input->inp.name); + } else if (req_input->type == REQ_INP_ID) { + ret = ber_printf(ber, "{ee{si}}", INP_POSIX_UID, request_type, + domain_name, + req_input->inp.id); + info = talloc_asprintf(mem_ctx, + "EXTDOM EXPO request: [%s] domain: [%s] id: [%" PRIu32 "]", + ipa_s2n_reqtype2str(request_type), + domain_name, req_input->inp.id); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected input type [%d].\n", + req_input->type); + ret = EINVAL; + goto done; + } + break; + case BE_REQ_GROUP: + if (req_input->type == REQ_INP_NAME) { + ret = ber_printf(ber, "{ee{ss}}", + (protocol == EXTDOM_V2 + ? INP_GROUPNAME : INP_NAME), + request_type, + domain_name, + req_input->inp.name); + info = talloc_asprintf(mem_ctx, + "EXTDOM EXPO request: [%s] domain: [%s] name: [%s]", + ipa_s2n_reqtype2str(request_type), + domain_name, req_input->inp.name); + } else if (req_input->type == REQ_INP_ID) { + ret = ber_printf(ber, "{ee{si}}", INP_POSIX_GID, request_type, + domain_name, + req_input->inp.id); + info = talloc_asprintf(mem_ctx, + "EXTDOM EXPO request: [%s] domain: [%s] id: [%" PRIu32 "]", + ipa_s2n_reqtype2str(request_type), + domain_name, req_input->inp.id); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected input type [%d].\n", + req_input->type); + ret = EINVAL; + goto done; + } + break; + case BE_REQ_BY_SECID: + if (req_input->type == REQ_INP_SECID) { + ret = ber_printf(ber, "{ees}", INP_SID, request_type, + req_input->inp.secid); + info = talloc_asprintf(mem_ctx, + "EXTDOM EXPO request: [%s] sid: [%s]", + ipa_s2n_reqtype2str(request_type), + req_input->inp.secid); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected input type [%d].\n", + req_input->type); + ret = EINVAL; + goto done; + } + break; + case BE_REQ_BY_CERT: + if (req_input->type == REQ_INP_CERT) { + ret = ber_printf(ber, "{ees}", INP_CERT, request_type, + req_input->inp.cert); + info = talloc_asprintf(mem_ctx, + "EXTDOM EXPO request: [%s] cert: [%s]", + ipa_s2n_reqtype2str(request_type), + req_input->inp.cert); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected input type [%d].\n", + req_input->type); + ret = EINVAL; + goto done; + } + break; + default: + ret = EINVAL; + goto done; + } + if (ret == -1) { + ret = EFAULT; + goto done; + } + + ret = talloc_ber_flatten(mem_ctx, ber, _bv); + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + ber_free(ber, 1); + if (ret != EOK || (*stat_info == NULL)) { + talloc_free(info); + } else { + *stat_info = info; + } + + return ret; +} + +/* If the extendend operation is successful it returns the following ASN.1 + * encoded response: + * + * ExtdomResponseValue ::= SEQUENCE { + * responseType ENUMERATED { + * sid (1), + * name (2), + * posix_user (3), + * posix_group (4), + * posix_user_grouplist (5), + * posix_group_members (6) + * }, + * data OutputData + * } + * + * OutputData ::= CHOICE { + * sid OCTET STRING, + * name NameDomainData, + * user PosixUser, + * group PosixGroup, + * usergrouplist PosixUserGrouplist, + * groupmembers PosixGroupMembers + * + * } + * + * NameDomainData ::= SEQUENCE { + * domain_name OCTET STRING, + * object_name OCTET STRING + * } + * + * PosixUser ::= SEQUENCE { + * domain_name OCTET STRING, + * user_name OCTET STRING, + * uid INTEGER + * gid INTEGER + * } + * + * PosixGroup ::= SEQUENCE { + * domain_name OCTET STRING, + * group_name OCTET STRING, + * gid INTEGER + * } + * + * PosixUserGrouplist ::= SEQUENCE { + * domain_name OCTET STRING, + * user_name OCTET STRING, + * uid INTEGER, + * gid INTEGER, + * gecos OCTET STRING, + * home_directory OCTET STRING, + * shell OCTET STRING, + * grouplist GroupNameList + * } + * + * GroupNameList ::= SEQUENCE OF OCTET STRING + * + * PosixGroupMembers ::= SEQUENCE { + * domain_name OCTET STRING, + * group_name OCTET STRING, + * gid INTEGER, + * members GroupMemberList + * } + * + * GroupMemberList ::= SEQUENCE OF OCTET STRING + */ + +struct name_list { + char *domain_name; + char *name; +}; + +struct resp_attrs { + enum response_types response_type; + char *domain_name; + union { + struct passwd user; + struct group group; + char *sid_str; + char *name; + } a; + size_t ngroups; + char **groups; + struct sysdb_attrs *sysdb_attrs; + char **name_list; +}; + +static errno_t get_extra_attrs(BerElement *ber, struct resp_attrs *resp_attrs) +{ + ber_tag_t tag; + ber_len_t ber_len; + char *ber_cookie; + char *name; + struct berval **values; + struct ldb_val v; + int ret; + size_t c; + + if (resp_attrs->sysdb_attrs == NULL) { + resp_attrs->sysdb_attrs = sysdb_new_attrs(resp_attrs); + if (resp_attrs->sysdb_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + return ENOMEM; + } + } + + DEBUG(SSSDBG_TRACE_ALL, "Found new sequence.\n"); + for (tag = ber_first_element(ber, &ber_len, &ber_cookie); + tag != LBER_DEFAULT; + tag = ber_next_element(ber, &ber_len, ber_cookie)) { + + tag = ber_scanf(ber, "{a{V}}", &name, &values); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + return EINVAL; + } + DEBUG(SSSDBG_TRACE_ALL, "Extra attribute [%s].\n", name); + + for (c = 0; values[c] != NULL; c++) { + + if (strcmp(name, SYSDB_USER_CERT) == 0) { + if (values[c]->bv_val[values[c]->bv_len] != '\0') { + DEBUG(SSSDBG_OP_FAILURE, + "base64 encoded certificate not 0-terminated.\n"); + return EINVAL; + } + + v.data = sss_base64_decode(NULL, values[c]->bv_val, &v.length); + if (v.data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n"); + return EINVAL; + } + } else { + v.data = (uint8_t *)values[c]->bv_val; + v.length = values[c]->bv_len; + } + + ret = sysdb_attrs_add_val_safe(resp_attrs->sysdb_attrs, name, &v); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_val_safe failed.\n"); + ldap_memfree(name); + ber_bvecfree(values); + return ret; + } + } + + ldap_memfree(name); + ber_bvecfree(values); + } + + return EOK; +} + +static errno_t add_v1_user_data(struct sss_domain_info *dom, + BerElement *ber, + struct resp_attrs *attrs) +{ + ber_tag_t tag; + ber_len_t ber_len; + int ret; + char *gecos = NULL; + char *homedir = NULL; + char *name = NULL; + char *domain = NULL; + char *shell = NULL; + char **list = NULL; + size_t c, gc; + struct sss_domain_info *parent_domain; + struct sss_domain_info *obj_domain; + + tag = ber_scanf(ber, "aaa", &gecos, &homedir, &shell); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + if (gecos == NULL || *gecos == '\0') { + attrs->a.user.pw_gecos = NULL; + } else { + attrs->a.user.pw_gecos = talloc_strdup(attrs, gecos); + if (attrs->a.user.pw_gecos == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + + if (homedir == NULL || *homedir == '\0') { + attrs->a.user.pw_dir = NULL; + } else { + attrs->a.user.pw_dir = talloc_strdup(attrs, homedir); + if (attrs->a.user.pw_dir == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + + if (shell == NULL || *shell == '\0') { + attrs->a.user.pw_shell = NULL; + } else { + attrs->a.user.pw_shell = talloc_strdup(attrs, shell); + if (attrs->a.user.pw_shell == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + + tag = ber_scanf(ber, "{v}", &list); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + for (attrs->ngroups = 0; list[attrs->ngroups] != NULL; + attrs->ngroups++); + + if (attrs->ngroups > 0) { + attrs->groups = talloc_zero_array(attrs, char *, attrs->ngroups + 1); + if (attrs->groups == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + attrs->ngroups = 0; + ret = ENOMEM; + goto done; + } + + parent_domain = get_domains_head(dom); + + for (c = 0, gc = 0; c < attrs->ngroups; c++) { + ret = sss_parse_name(attrs, dom->names, list[c], + &domain, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot parse member %s\n", list[c]); + continue; + } + + if (domain != NULL) { + obj_domain = find_domain_by_name_ex(parent_domain, domain, true, SSS_GND_ALL_DOMAINS); + if (obj_domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name_ex failed.\n"); + attrs->ngroups = gc; + ret = ENOMEM; + goto done; + } else if (sss_domain_get_state(obj_domain) == DOM_DISABLED) { + /* skipping objects from disabled domains */ + DEBUG(SSSDBG_TRACE_ALL, + "Skipping object [%s] from disabled domain.\n", + list[c]); + continue; + } + } else { + obj_domain = parent_domain; + } + + attrs->groups[gc] = sss_create_internal_fqname(attrs->groups, + name, obj_domain->name); + if (attrs->groups[gc] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_create_internal_fqname failed.\n"); + attrs->ngroups = gc; + ret = ENOMEM; + goto done; + } + gc++; + } + attrs->ngroups = gc; + } + + tag = ber_peek_tag(ber, &ber_len); + DEBUG(SSSDBG_TRACE_ALL, "BER tag is [%d]\n", (int) tag); + if (tag == LBER_SEQUENCE) { + ret = get_extra_attrs(ber, attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_extra_attrs failed.\n"); + goto done; + } + } + + + ret = EOK; + +done: + ber_memfree(gecos); + ber_memfree(homedir); + ber_memfree(shell); + ber_memvfree((void **) list); + + return ret; +} + +static errno_t add_v1_group_data(BerElement *ber, + struct sss_domain_info *dom, + struct resp_attrs *attrs) +{ + ber_tag_t tag; + ber_len_t ber_len; + int ret; + char **list = NULL; + size_t c, mc; + char *name = NULL; + char *domain = NULL; + + tag = ber_scanf(ber, "{v}", &list); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + if (list != NULL) { + for (attrs->ngroups = 0; list[attrs->ngroups] != NULL; + attrs->ngroups++); + + if (attrs->ngroups > 0) { + attrs->a.group.gr_mem = talloc_zero_array(attrs, char *, + attrs->ngroups + 1); + if (attrs->a.group.gr_mem == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + ret = ENOMEM; + goto done; + } + + for (c = 0, mc=0; c < attrs->ngroups; c++) { + ret = sss_parse_name(attrs, dom->names, list[c], + &domain, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot parse member %s\n", list[c]); + continue; + } + + if (domain == NULL) { + domain = dom->name; + } + + attrs->a.group.gr_mem[mc] = + sss_create_internal_fqname(attrs->a.group.gr_mem, + name, domain); + if (attrs->a.group.gr_mem[mc] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + mc++; + } + } + } else { + attrs->a.group.gr_mem = talloc_zero_array(attrs, char *, 1); + if (attrs->a.group.gr_mem == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + ret = ENOMEM; + goto done; + } + } + + tag = ber_peek_tag(ber, &ber_len); + DEBUG(SSSDBG_TRACE_ALL, "BER tag is [%d]\n", (int) tag); + if (tag == LBER_SEQUENCE) { + ret = get_extra_attrs(ber, attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_extra_attrs failed.\n"); + goto done; + } + } + + ret = EOK; + +done: + ber_memvfree((void **) list); + + return ret; +} + +static char *s2n_response_to_attrs_fqname(TALLOC_CTX *mem_ctx, + enum extdom_protocol protocol, + const char *domain_name, + const char *name) +{ + char *lc_name; + char *out_name; + + if (protocol == EXTDOM_V0) { + /* Compatibility with older IPA servers that may use winbind instead + * of SSSD's server mode. + * + * Winbind is not consistent with the case of the returned user + * name. In general all names should be lower case but there are + * bug in some version of winbind which might lead to upper case + * letters in the name. To be on the safe side we explicitly + * lowercase the name. + */ + + lc_name = sss_tc_utf8_str_tolower(NULL, name); + if (lc_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + return NULL; + } + + out_name = sss_create_internal_fqname(mem_ctx, lc_name, domain_name); + talloc_free(lc_name); + } else { + /* Keep the original casing to support case_sensitive=Preserving */ + out_name = sss_create_internal_fqname(mem_ctx, name, domain_name); + } + + if (out_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + return NULL; + } + + return out_name; +} + +static errno_t ipa_s2n_save_objects(struct sss_domain_info *dom, + struct req_input *req_input, + struct resp_attrs *attrs, + struct resp_attrs *simple_attrs, + const char *view_name, + struct sysdb_attrs *override_attrs, + struct sysdb_attrs *mapped_attrs, + bool update_initgr_timeout); + +static errno_t s2n_response_to_attrs(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + char *retoid, + struct berval *retdata, + struct resp_attrs **resp_attrs) +{ + BerElement *ber = NULL; + ber_tag_t tag; + int ret; + enum response_types type; + char *domain_name = NULL; + char *name = NULL; + uid_t uid; + gid_t gid; + struct resp_attrs *attrs = NULL; + char *sid_str; + enum extdom_protocol protocol; + char **name_list = NULL; + ber_len_t ber_len; + char *fq_name = NULL; + struct sss_domain_info *root_domain = NULL; + + if (retoid == NULL || retdata == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing OID or data.\n"); + return EINVAL; + } + + protocol = extdom_oid_to_protocol(retoid); + if (protocol == EXTDOM_INVALID_VERSION) { + DEBUG(SSSDBG_OP_FAILURE, + "Result has wrong OID, expected [%s], [%s] or [%s], got [%s].\n", + EXOP_SID2NAME_OID, EXOP_SID2NAME_V1_OID, + EXOP_SID2NAME_V2_OID, retoid); + return EINVAL; + } + + ber = ber_init(retdata); + if (ber == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ber_init failed.\n"); + return EINVAL; + } + + tag = ber_scanf(ber, "{e", &type); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + attrs = talloc_zero(mem_ctx, struct resp_attrs); + if (attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + + switch (type) { + case RESP_USER: + case RESP_USER_GROUPLIST: + tag = ber_scanf(ber, "{aaii", &domain_name, &name, &uid, &gid); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + attrs->a.user.pw_name = s2n_response_to_attrs_fqname(attrs, + protocol, + domain_name, + name); + if (attrs->a.user.pw_name == NULL) { + ret = ENOMEM; + goto done; + } + + attrs->a.user.pw_uid = uid; + attrs->a.user.pw_gid = gid; + + if (protocol > EXTDOM_V0 && type == RESP_USER_GROUPLIST) { + ret = add_v1_user_data(dom, ber, attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_v1_user_data failed.\n"); + goto done; + } + } + + tag = ber_scanf(ber, "}}"); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + break; + case RESP_GROUP: + case RESP_GROUP_MEMBERS: + tag = ber_scanf(ber, "{aai", &domain_name, &name, &gid); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + attrs->a.group.gr_name = s2n_response_to_attrs_fqname(attrs, + protocol, + domain_name, + name); + if (attrs->a.group.gr_name == NULL) { + ret = ENOMEM; + goto done; + } + + attrs->a.group.gr_gid = gid; + + if (protocol > EXTDOM_V0 && type == RESP_GROUP_MEMBERS) { + ret = add_v1_group_data(ber, dom, attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_v1_group_data failed.\n"); + goto done; + } + } + + tag = ber_scanf(ber, "}}"); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + break; + case RESP_SID: + tag = ber_scanf(ber, "a}", &sid_str); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + attrs->a.sid_str = talloc_strdup(attrs, sid_str); + if (attrs->a.sid_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + break; + case RESP_NAME: + tag = ber_scanf(ber, "{aa}", &domain_name, &name); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + attrs->a.name = sss_tc_utf8_str_tolower(attrs, name); + if (attrs->a.name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_tc_utf8_str_tolower failed.\n"); + ret = ENOMEM; + goto done; + } + break; + case RESP_NAME_LIST: + tag = ber_scanf(ber, "{"); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + root_domain = get_domains_head(dom); + + while (ber_peek_tag(ber, &ber_len) == LBER_SEQUENCE) { + tag = ber_scanf(ber, "{aa}", &domain_name, &name); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + + fq_name = sss_create_internal_fqname(attrs, name, domain_name); + if (fq_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_create_internal_fqname failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "[%s][%s][%s].\n", domain_name, name, + fq_name); + + if (strcasecmp(root_domain->name, domain_name) != 0) { + ret = add_string_to_list(attrs, fq_name, &name_list); + } else { + DEBUG(SSSDBG_TRACE_ALL, + "[%s] from root domain, skipping.\n", fq_name); + ret = EOK; /* Free resources and continue in the loop */ + } + ber_memfree(domain_name); + ber_memfree(name); + talloc_free(fq_name); + domain_name = NULL; + name = NULL; + fq_name = NULL; + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_to_name_list failed.\n"); + goto done; + } + } + + tag = ber_scanf(ber, "}}"); + if (tag == LBER_ERROR) { + DEBUG(SSSDBG_OP_FAILURE, "ber_scanf failed.\n"); + ret = EINVAL; + goto done; + } + attrs->name_list = name_list; + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unexpected response type [%d].\n", + type); + ret = EINVAL; + goto done; + } + + attrs->response_type = type; + if (type != RESP_SID && type != RESP_NAME_LIST) { + attrs->domain_name = talloc_strdup(attrs, domain_name); + if (attrs->domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + + ret = EOK; + +done: + ber_memfree(domain_name); + ber_memfree(name); + talloc_free(fq_name); + ber_free(ber, 1); + + if (ret == EOK) { + *resp_attrs = attrs; + } else { + talloc_free(attrs); + } + + return ret; +} + +static const char *ipa_s2n_reqinp2str(TALLOC_CTX *mem_ctx, + struct req_input *req_input) +{ + const char *str = NULL; + + switch (req_input->type) { + case REQ_INP_NAME: + str = talloc_strdup(mem_ctx, req_input->inp.name); + break; + case REQ_INP_SECID: + str = talloc_strdup(mem_ctx, req_input->inp.secid); + break; + case REQ_INP_CERT: + str = talloc_strdup(mem_ctx, req_input->inp.cert); + break; + case REQ_INP_ID: + str = talloc_asprintf(mem_ctx, "%u", req_input->inp.id); + break; + } + + if (str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + } + + return str; +} + +struct ipa_s2n_get_list_state { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct sss_domain_info *dom; + struct sdap_handle *sh; + enum extdom_protocol protocol; + struct req_input req_input; + char **list; + size_t list_idx; + int exop_timeout; + int entry_type; + enum request_types request_type; + struct resp_attrs *attrs; + struct sss_domain_info *obj_domain; + struct sysdb_attrs *override_attrs; + struct sysdb_attrs *mapped_attrs; +}; + +static errno_t ipa_s2n_get_list_step(struct tevent_req *req); +static void ipa_s2n_get_list_get_override_done(struct tevent_req *subreq); +static void ipa_s2n_get_list_next(struct tevent_req *subreq); +static void ipa_s2n_get_list_ipa_next(struct tevent_req *subreq); +static errno_t ipa_s2n_get_list_save_step(struct tevent_req *req); + +static struct tevent_req *ipa_s2n_get_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sss_domain_info *dom, + struct sdap_handle *sh, + int exop_timeout, + int entry_type, + enum request_types request_type, + enum req_input_type list_type, + char **list, + struct sysdb_attrs *mapped_attrs) +{ + int ret; + struct ipa_s2n_get_list_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, struct ipa_s2n_get_list_state); + if (req == NULL) { + return NULL; + } + + if ((entry_type == BE_REQ_BY_SECID && list_type != REQ_INP_SECID) + || (entry_type != BE_REQ_BY_SECID && list_type == REQ_INP_SECID)) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid parameter combination [%d][%d].\n", + request_type, list_type); + ret = EINVAL; + goto done; + } + + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->dom = dom; + state->sh = sh; + state->protocol = extdom_preferred_protocol(sh); + state->list = list; + state->list_idx = 0; + state->req_input.type = list_type; + state->req_input.inp.name = NULL; + state->exop_timeout = exop_timeout; + state->entry_type = entry_type; + state->request_type = request_type; + state->attrs = NULL; + state->override_attrs = NULL; + state->mapped_attrs = mapped_attrs; + + ret = ipa_s2n_get_list_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_get_list_step failed.\n"); + goto done; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static errno_t ipa_s2n_get_list_step(struct tevent_req *req) +{ + int ret; + struct ipa_s2n_get_list_state *state = tevent_req_data(req, + struct ipa_s2n_get_list_state); + struct berval *bv_req; + struct tevent_req *subreq; + struct sss_domain_info *parent_domain; + char *short_name = NULL; + char *domain_name = NULL; + uint32_t id; + char *endptr; + struct dp_id_data *ar; + char *stat_info = NULL; + + parent_domain = get_domains_head(state->dom); + switch (state->req_input.type) { + case REQ_INP_NAME: + + ret = sss_parse_name(state, state->dom->names, state->list[state->list_idx], + &domain_name, &short_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse name '%s' [%d]: %s\n", + state->list[state->list_idx], + ret, sss_strerror(ret)); + return ret; + } + + if (domain_name) { + state->obj_domain = find_domain_by_name(parent_domain, + domain_name, true); + if (state->obj_domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n"); + return ENOMEM; + } + } else { + state->obj_domain = parent_domain; + } + + state->req_input.inp.name = short_name; + + if (strcmp(state->obj_domain->name, + state->ipa_ctx->sdap_id_ctx->be->domain->name) == 0) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Looking up IPA object [%s] from LDAP.\n", + state->list[state->list_idx]); + ret = get_dp_id_data_for_user_name(state, + state->list[state->list_idx], + state->obj_domain->name, + &ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to create lookup date for IPA object [%s].\n", + state->list[state->list_idx]); + return ret; + } + ar->entry_type = state->entry_type; + + subreq = ipa_id_get_account_info_send(state, state->ev, + state->ipa_ctx, ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_id_get_account_info_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_s2n_get_list_ipa_next, req); + + return EOK; + } + + break; + case REQ_INP_ID: + id = strtouint32(state->list[state->list_idx], &endptr, 10); + if (errno != 0 || *endptr != '\0' + || (state->list[state->list_idx] == endptr)) { + DEBUG(SSSDBG_OP_FAILURE, "strtouint32 failed.\n"); + return EINVAL; + } + state->req_input.inp.id = id; + state->obj_domain = state->dom; + + break; + case REQ_INP_SECID: + state->req_input.inp.secid = state->list[state->list_idx]; + state->obj_domain = find_domain_by_sid(parent_domain, + state->req_input.inp.secid); + if (state->obj_domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "find_domain_by_sid failed for SID [%s].\n", + state->req_input.inp.secid); + return EINVAL; + } + + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unexpected input type [%d].\n", + state->req_input.type); + return EINVAL; + } + + ret = s2n_encode_request(state, state->obj_domain->name, state->entry_type, + state->request_type, &state->req_input, + state->protocol, &bv_req, &stat_info); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "s2n_encode_request failed.\n"); + return ret; + } + + if (state->request_type == REQ_FULL_WITH_MEMBERS && state->protocol == EXTDOM_V0) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_exop failed, protocol > V0 needed for this request.\n"); + return EINVAL; + } + + if (state->req_input.type == REQ_INP_NAME + && state->req_input.inp.name != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "Sending request_type: [%s] for object [%s].\n", + ipa_s2n_reqtype2str(state->request_type), + state->list[state->list_idx]); + } + + subreq = ipa_s2n_exop_send(state, state->ev, state->sh, state->protocol, + state->exop_timeout, bv_req, stat_info); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_exop_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_s2n_get_list_next, req); + + return EOK; +} + +static void ipa_s2n_get_list_next(struct tevent_req *subreq) +{ + int ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_s2n_get_list_state *state = tevent_req_data(req, + struct ipa_s2n_get_list_state); + char *retoid = NULL; + struct berval *retdata = NULL; + const char *sid_str; + struct dp_id_data *ar; + + ret = ipa_s2n_exop_recv(subreq, state, &retoid, &retdata); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "s2n exop request failed.\n"); + goto fail; + } + + talloc_zfree(state->attrs); + ret = s2n_response_to_attrs(state, state->dom, retoid, retdata, + &state->attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "s2n_response_to_attrs failed.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Received [%s] attributes from IPA server.\n", + state->attrs->a.name); + + if (is_default_view(state->ipa_ctx->view_name)) { + ret = ipa_s2n_get_list_save_step(req); + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_get_list_save_step failed.\n"); + goto fail; + } + + return; + } + + ret = sysdb_attrs_get_string(state->attrs->sysdb_attrs, SYSDB_SID_STR, + &sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Object [%s] has no SID, please check the " + "ipaNTSecurityIdentifier attribute on the server-side", + state->attrs->a.name); + goto fail; + } + + ret = get_dp_id_data_for_sid(state, sid_str, state->obj_domain->name, &ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + goto fail; + } + + subreq = ipa_get_ad_override_send(state, state->ev, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->ipa_options, + dp_opt_get_string(state->ipa_ctx->ipa_options->basic, + IPA_KRB5_REALM), + state->ipa_ctx->view_name, + ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, ipa_s2n_get_list_get_override_done, req); + + return; + +fail: + tevent_req_error(req,ret); + return; +} + +static void ipa_s2n_get_list_ipa_next(struct tevent_req *subreq) +{ + int ret; + int dp_error; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_s2n_get_list_state *state = tevent_req_data(req, + struct ipa_s2n_get_list_state); + + ret = ipa_id_get_account_info_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_id_get_account_info failed: %d %d\n", ret, + dp_error); + goto done; + } + + state->list_idx++; + if (state->list[state->list_idx] == NULL) { + tevent_req_done(req); + return; + } + + ret = ipa_s2n_get_list_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_get_list_step failed.\n"); + goto done; + } + + return; + +done: + tevent_req_error(req,ret); + return; +} + +static void ipa_s2n_get_list_get_override_done(struct tevent_req *subreq) +{ + int ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_s2n_get_list_state *state = tevent_req_data(req, + struct ipa_s2n_get_list_state); + + ret = ipa_get_ad_override_recv(subreq, NULL, state, &state->override_attrs); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); + goto fail; + } + + ret = ipa_s2n_get_list_save_step(req); + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_get_list_save_step failed.\n"); + goto fail; + } + + return; + +fail: + tevent_req_error(req,ret); + return; +} + +static errno_t ipa_s2n_get_list_save_step(struct tevent_req *req) +{ + int ret; + struct ipa_s2n_get_list_state *state = tevent_req_data(req, + struct ipa_s2n_get_list_state); + + ret = ipa_s2n_save_objects(state->dom, &state->req_input, state->attrs, + NULL, state->ipa_ctx->view_name, + state->override_attrs, state->mapped_attrs, + false); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_save_objects failed.\n"); + return ret; + } + + state->list_idx++; + if (state->list[state->list_idx] == NULL) { + return EOK; + } + + ret = ipa_s2n_get_list_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_get_list_step failed.\n"); + return ret; + } + + return EAGAIN; +} + +static int ipa_s2n_get_list_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_s2n_get_user_state { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct sdap_options *opts; + struct sss_domain_info *dom; + struct sdap_handle *sh; + enum extdom_protocol protocol; + struct req_input *req_input; + int entry_type; + enum request_types request_type; + struct resp_attrs *attrs; + struct resp_attrs *simple_attrs; + struct sysdb_attrs *override_attrs; + struct sysdb_attrs *mapped_attrs; + int exop_timeout; +}; + +static void ipa_s2n_get_user_done(struct tevent_req *subreq); + +struct tevent_req *ipa_s2n_get_acct_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sdap_options *opts, + struct sss_domain_info *dom, + struct sysdb_attrs *override_attrs, + struct sdap_handle *sh, + int entry_type, + struct req_input *req_input) +{ + struct ipa_s2n_get_user_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + struct berval *bv_req = NULL; + const char *input; + int ret = EFAULT; + char *stat_info = NULL; + + req = tevent_req_create(mem_ctx, &state, struct ipa_s2n_get_user_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->opts = opts; + state->dom = dom; + state->sh = sh; + state->protocol = extdom_preferred_protocol(sh); + state->req_input = req_input; + state->entry_type = entry_type; + state->attrs = NULL; + state->simple_attrs = NULL; + state->exop_timeout = dp_opt_get_int(opts->basic, SDAP_SEARCH_TIMEOUT); + state->override_attrs = override_attrs; + + if (state->protocol == EXTDOM_V1 || state->protocol == EXTDOM_V2) { + state->request_type = REQ_FULL_WITH_MEMBERS; + } else if (state->protocol == EXTDOM_V0) { + state->request_type = REQ_FULL; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Extdom not supported on the server, " + "cannot resolve objects from trusted domains.\n"); + ret = EIO; + goto fail; + } + + if (entry_type == BE_REQ_BY_CERT) { + /* Only REQ_SIMPLE is supported for BE_REQ_BY_CERT */ + state->request_type = REQ_SIMPLE; + } + + ret = s2n_encode_request(state, dom->name, entry_type, state->request_type, + req_input, state->protocol, &bv_req, &stat_info); + if (ret != EOK) { + goto fail; + } + + input = ipa_s2n_reqinp2str(state, req_input); + DEBUG(SSSDBG_TRACE_FUNC, + "Sending request_type: [%s] for trust user [%s] to IPA server\n", + ipa_s2n_reqtype2str(state->request_type), + input); + talloc_zfree(input); + + subreq = ipa_s2n_exop_send(state, state->ev, state->sh, state->protocol, + state->exop_timeout, bv_req, stat_info); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_exop_send failed.\n"); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, ipa_s2n_get_user_done, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static errno_t process_members(struct sss_domain_info *domain, + bool is_default_view, + struct sysdb_attrs *group_attrs, + char **members, + TALLOC_CTX *mem_ctx, char ***_missing_members) +{ + int ret; + size_t c; + TALLOC_CTX *tmp_ctx; + struct ldb_message *msg; + const char *dn_str; + struct sss_domain_info *obj_domain; + struct sss_domain_info *parent_domain; + char **missing_members = NULL; + size_t miss_count = 0; + const char *attrs[] = {SYSDB_NAME, SYSDB_OVERRIDE_DN, NULL}; + + if (members == NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, "No members\n"); + if (_missing_members != NULL) { + *_missing_members = NULL; + } + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + if (_missing_members != NULL && mem_ctx != NULL) { + /* count members */ + for (c = 0; members[c] != NULL; c++); + missing_members = talloc_zero_array(tmp_ctx, char *, c + 1); + if (missing_members == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array_zero failed.\n"); + ret = ENOMEM; + goto done; + } + } + + parent_domain = get_domains_head(domain); + + for (c = 0; members[c] != NULL; c++) { + obj_domain = find_domain_by_object_name_ex(parent_domain, members[c], + false, SSS_GND_ALL_DOMAINS); + if (obj_domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_object_name failed.\n"); + ret = ENOMEM; + goto done; + } else if (sss_domain_get_state(obj_domain) == DOM_DISABLED) { + /* skip members from disabled domains */ + continue; + } + + ret = sysdb_search_user_by_name(tmp_ctx, obj_domain, members[c], attrs, + &msg); + if (ret == EOK || ret == ENOENT) { + if (ret == ENOENT + || (!is_default_view + && ldb_msg_find_attr_as_string(msg, SYSDB_OVERRIDE_DN, + NULL) == NULL)) { + /* only add ghost if the member is really missing */ + if (group_attrs != NULL && ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "Adding ghost member [%s]\n", + members[c]); + + /* There were cases where the server returned the same user + * multiple times */ + ret = sysdb_attrs_add_string_safe(group_attrs, SYSDB_GHOST, + members[c]); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_string failed.\n"); + goto done; + } + } + + if (missing_members != NULL) { + missing_members[miss_count] = talloc_strdup(missing_members, + members[c]); + if (missing_members[miss_count] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + miss_count++; + } + } else { + if (group_attrs != NULL) { + dn_str = ldb_dn_get_linearized(msg->dn); + if (dn_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_get_linearized failed.\n"); + ret = EINVAL; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "Adding member [%s][%s]\n", + members[c], dn_str); + + ret = sysdb_attrs_add_string_safe(group_attrs, SYSDB_MEMBER, + dn_str); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_string_safe failed.\n"); + goto done; + } + } + } + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_name failed.\n"); + goto done; + } + } + + if (_missing_members != NULL) { + if (miss_count == 0) { + *_missing_members = NULL; + } else { + if (mem_ctx != NULL) { + *_missing_members = talloc_steal(mem_ctx, missing_members); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing memory context for missing members list.\n"); + ret = EINVAL; + goto done; + } + } + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t get_group_dn_list(TALLOC_CTX *mem_ctx, + bool is_default_view, + struct sss_domain_info *dom, + size_t ngroups, char **groups, + struct ldb_dn ***_dn_list, + char ***_missing_groups) +{ + int ret; + size_t c; + TALLOC_CTX *tmp_ctx; + struct ldb_dn **dn_list = NULL; + char **missing_groups = NULL; + struct ldb_message *msg = NULL; + size_t n_dns = 0; + size_t n_missing = 0; + struct sss_domain_info *obj_domain; + struct sss_domain_info *parent_domain; + const char *attrs[] = {SYSDB_NAME, SYSDB_OVERRIDE_DN, NULL}; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + dn_list = talloc_zero_array(tmp_ctx, struct ldb_dn *, ngroups + 1); + missing_groups = talloc_zero_array(tmp_ctx, char *, ngroups + 1); + if (dn_list == NULL || missing_groups == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array_zero failed.\n"); + ret = ENOMEM; + goto done; + } + + parent_domain = (dom->parent == NULL) ? dom : dom->parent; + + for (c = 0; c < ngroups; c++) { + obj_domain = find_domain_by_object_name(parent_domain, groups[c]); + if (obj_domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_object_name failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_group_by_name(tmp_ctx, obj_domain, groups[c], attrs, + &msg); + if (ret == EOK || ret == ENOENT) { + if (ret == ENOENT + || (!is_default_view + && ldb_msg_find_attr_as_string(msg, SYSDB_OVERRIDE_DN, + NULL) == NULL)) { + missing_groups[n_missing] = talloc_strdup(missing_groups, + groups[c]); + if (missing_groups[n_missing] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + n_missing++; + + } else { + dn_list[n_dns] = ldb_dn_copy(dn_list, msg->dn); + if (dn_list[n_dns] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_copy failed.\n"); + ret = ENOMEM; + goto done; + } + n_dns++; + } + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_group_by_name failed.\n"); + goto done; + } + } + + if (n_missing != 0) { + *_missing_groups = talloc_steal(mem_ctx, missing_groups); + } else { + *_missing_groups = NULL; + } + + if (n_dns != 0) { + *_dn_list = talloc_steal(mem_ctx, dn_list); + } else { + *dn_list = NULL; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t s2n_remove_missing_object(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + int entry_type, + struct req_input *req_input) +{ + int ret; + bool name_is_upn = false; + char *id_str = NULL; + char *fq_name = NULL; + + if (req_input->type == REQ_INP_ID) { + id_str = talloc_asprintf(mem_ctx, "%"SPRIuid, req_input->inp.id); + if (id_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + } + + switch (entry_type) { + case BE_REQ_USER_AND_GROUP: + case BE_REQ_USER: + if (req_input->type == REQ_INP_NAME) { + name_is_upn = strchr(req_input->inp.name, '@') == NULL ? false + : true; + /* Expand to fully-qualified internal name */ + if (!name_is_upn) { + fq_name = sss_create_internal_fqname(mem_ctx, + req_input->inp.name, + domain->name); + if (fq_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_create_internal_fqname failed.\n"); + ret = ENOMEM; + goto done; + } + } + ret = users_get_handle_no_user(mem_ctx, domain, BE_FILTER_NAME, + fq_name != NULL ? fq_name + : req_input->inp.name, + name_is_upn); + } else if (req_input->type == REQ_INP_ID) { + ret = users_get_handle_no_user(mem_ctx, domain, BE_FILTER_IDNUM, + id_str, false); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected input type [%d].\n", + req_input->type); + ret = EINVAL; + goto done; + } + if (ret != EOK || entry_type == BE_REQ_USER) { + break; + } + /* Fallthough if BE_REQ_USER_AND_GROUP */ + SSS_ATTRIBUTE_FALLTHROUGH; + case BE_REQ_GROUP: + if (req_input->type == REQ_INP_NAME) { + /* Expand to fully-qualified internal name */ + fq_name = sss_create_internal_fqname(mem_ctx, + req_input->inp.name, + domain->name); + if (fq_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_create_internal_fqname failed.\n"); + ret = ENOMEM; + goto done; + } + ret = groups_get_handle_no_group(mem_ctx, domain, BE_FILTER_NAME, + fq_name); + } else if (req_input->type == REQ_INP_ID) { + ret = groups_get_handle_no_group(mem_ctx, domain,BE_FILTER_IDNUM, + id_str); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected input type [%d].\n", + req_input->type); + ret = EINVAL; + goto done; + } + break; + case BE_REQ_BY_SECID: + ret = EOK; + break; + case BE_REQ_BY_CERT: + ret = EOK; + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unexpected entry type [%d].\n", entry_type); + ret = EINVAL; + } + +done: + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Error while trying to remove user or group from cache.\n"); + } + + talloc_free(id_str); + talloc_free(fq_name); + return ret; +} + +static void ipa_s2n_get_list_done(struct tevent_req *subreq); +static void ipa_s2n_get_user_get_override_done(struct tevent_req *subreq); +static void ipa_s2n_get_user_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_s2n_get_user_state *state = tevent_req_data(req, + struct ipa_s2n_get_user_state); + int ret; + char *retoid = NULL; + struct berval *retdata = NULL; + struct resp_attrs *attrs = NULL; + struct berval *bv_req = NULL; + char **missing_list = NULL; + struct ldb_dn **group_dn_list = NULL; + const char *sid_str; + struct dp_id_data *ar; + char *stat_info = NULL; + + ret = ipa_s2n_exop_recv(subreq, state, &retoid, &retdata); + talloc_zfree(subreq); + if (ret != EOK) { + if (ret == ENOENT) { + ret = s2n_remove_missing_object(state, state->dom, + state->entry_type, + state->req_input); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "s2n_remove_missing_object failed [%d].\n", ret); + } + } else { + DEBUG(SSSDBG_OP_FAILURE, "s2n exop request failed.\n"); + if (state->req_input->type == REQ_INP_CERT) { + DEBUG(SSSDBG_OP_FAILURE, + "Maybe the server does not support lookups by " + "certificates.\n"); + } + } + goto done; + } + + switch (state->request_type) { + case REQ_FULL_WITH_MEMBERS: + case REQ_FULL: + ret = s2n_response_to_attrs(state, state->dom, retoid, retdata, + &attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "s2n_response_to_attrs failed.\n"); + goto done; + } + + if (!(strcasecmp(state->dom->name, attrs->domain_name) == 0 || + (state->dom->flat_name != NULL && + strcasecmp(state->dom->flat_name, attrs->domain_name) == 0))) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected domain name returned, " + "expected [%s] or [%s], got [%s].\n", + state->dom->name, + state->dom->flat_name == NULL ? "" : + state->dom->flat_name, + attrs->domain_name); + ret = EINVAL; + goto done; + } + + state->attrs = attrs; + + if (attrs->response_type == RESP_USER_GROUPLIST) { + + DEBUG(SSSDBG_TRACE_FUNC, "Received [%zu] groups in group list " + "from IPA Server\n", attrs->ngroups); + + for (size_t c = 0; c < attrs->ngroups; c++) { + DEBUG(SSSDBG_TRACE_FUNC, "[%s].\n", attrs->groups[c]); + } + + + ret = get_group_dn_list(state, + is_default_view(state->ipa_ctx->view_name), + state->dom, + attrs->ngroups, attrs->groups, + &group_dn_list, &missing_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_group_dn_list failed.\n"); + goto done; + } + + if (missing_list != NULL) { + subreq = ipa_s2n_get_list_send(state, state->ev, + state->ipa_ctx, state->dom, + state->sh, state->exop_timeout, + BE_REQ_GROUP, + REQ_FULL_WITH_MEMBERS, + REQ_INP_NAME, + missing_list, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_s2n_get_list_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_s2n_get_list_done, + req); + + return; + } + break; + } else if (attrs->response_type == RESP_GROUP_MEMBERS) { + ret = process_members(state->dom, + is_default_view(state->ipa_ctx->view_name), + NULL, attrs->a.group.gr_mem, state, + &missing_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "process_members failed.\n"); + goto done; + } + + if (missing_list != NULL) { + subreq = ipa_s2n_get_list_send(state, state->ev, + state->ipa_ctx, state->dom, + state->sh, state->exop_timeout, + BE_REQ_USER, + REQ_FULL_WITH_MEMBERS, + REQ_INP_NAME, + missing_list, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_s2n_get_list_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_s2n_get_list_done, + req); + + return; + } + break; + } + + if (state->req_input->type == REQ_INP_SECID) { + /* We already know the SID, we do not have to read it. */ + break; + } + + state->request_type = REQ_SIMPLE; + + ret = s2n_encode_request(state, state->dom->name, state->entry_type, + state->request_type, state->req_input, + state->protocol, + &bv_req, &stat_info); + if (ret != EOK) { + goto done; + } + + subreq = ipa_s2n_exop_send(state, state->ev, state->sh, false, + state->exop_timeout, bv_req, stat_info); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_exop_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_s2n_get_user_done, req); + + return; + + case REQ_SIMPLE: + ret = s2n_response_to_attrs(state, state->dom, retoid, retdata, + &state->simple_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "s2n_response_to_attrs failed.\n"); + goto done; + } + + if (state->simple_attrs->response_type == RESP_NAME_LIST + && state->req_input->type == REQ_INP_CERT) { + + if (state->simple_attrs->name_list == NULL) { + /* No results from sub-domains, nothing to do */ + ret = EOK; + goto done; + } + + state->mapped_attrs = sysdb_new_attrs(state); + if (state->mapped_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_base64_blob(state->mapped_attrs, + SYSDB_USER_MAPPED_CERT, + state->req_input->inp.cert); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_base64_blob failed.\n"); + goto done; + } + + subreq = ipa_s2n_get_list_send(state, state->ev, + state->ipa_ctx, state->dom, + state->sh, state->exop_timeout, + BE_REQ_USER, + REQ_FULL_WITH_MEMBERS, + REQ_INP_NAME, + state->simple_attrs->name_list, + state->mapped_attrs); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_s2n_get_list_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_s2n_get_list_done, + req); + + return; + } + + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected request type %d.\n", state->request_type); + ret = EINVAL; + goto done; + } + + if (state->attrs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing data of full request.\n"); + ret = EINVAL; + goto done; + } + + if (state->simple_attrs != NULL + && state->simple_attrs->response_type == RESP_SID) { + sid_str = state->simple_attrs->a.sid_str; + ret = EOK; + } else if (state->attrs->sysdb_attrs != NULL) { + ret = sysdb_attrs_get_string(state->attrs->sysdb_attrs, SYSDB_SID_STR, + &sid_str); + } else if (state->req_input->type == REQ_INP_SECID) { + sid_str = state->req_input->inp.secid; + ret = EOK; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "No SID available.\n"); + ret = ENOENT; + } + + if (ret == ENOENT || is_default_view(state->ipa_ctx->view_name)) { + ret = ipa_s2n_save_objects(state->dom, state->req_input, state->attrs, + state->simple_attrs, NULL, NULL, NULL, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_save_objects failed.\n"); + goto done; + } + } else if (ret == EOK) { + ret = get_dp_id_data_for_sid(state, sid_str, state->dom->name, &ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + goto done; + } + + subreq = ipa_get_ad_override_send(state, state->ev, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->ipa_options, + dp_opt_get_string(state->ipa_ctx->ipa_options->basic, + IPA_KRB5_REALM), + state->ipa_ctx->view_name, + ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_s2n_get_user_get_override_done, + req); + + return; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + return; +} + +static errno_t get_groups_dns(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, + char **name_list, char ***_dn_list) +{ + int ret; + TALLOC_CTX *tmp_ctx; + int c; + struct sss_domain_info *root_domain; + char **dn_list; + size_t dn_list_c; + struct ldb_message *msg; + struct ldb_dn *user_base_dn = NULL; + + if (name_list == NULL) { + *_dn_list = NULL; + return EOK; + } + + /* To handle cross-domain memberships we have to check the domain for + * each group the member should be added or deleted. Since sub-domains + * use fully-qualified names by default any short name can only belong + * to the root/head domain. find_domain_by_object_name() will return + * the domain given in the first argument if the second argument is a + * a short name hence we always use root_domain as first argument. */ + root_domain = get_domains_head(dom); + if (root_domain->fqnames) { + DEBUG(SSSDBG_TRACE_FUNC, + "Root domain uses fully-qualified names, " \ + "objects might not be correctly added to groups with " \ + "short names.\n"); + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + for (c = 0; name_list[c] != NULL; c++); + + dn_list = talloc_zero_array(tmp_ctx, char *, c + 1); + if (dn_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + dn_list_c = 0; + for (c = 0; name_list[c] != NULL; c++) { + dom = find_domain_by_object_name(root_domain, name_list[c]); + if (dom == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot find domain for [%s].\n", name_list[c]); + ret = ENOENT; + goto done; + } + + /* If the group name is overridden in the default view we have to + * search for the name and cannot construct it because the extdom + * plugin will return the overridden name but the DN of the related + * group object in the cache will contain the original name. */ + + ret = sysdb_search_group_by_name(tmp_ctx, dom, name_list[c], NULL, + &msg); + if (ret == EOK) { + talloc_free(user_base_dn); + user_base_dn = sysdb_user_base_dn(tmp_ctx, dom); + if (user_base_dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_user_base_dn failed.\n"); + ret = ENOMEM; + goto done; + } + if (ldb_dn_compare_base(user_base_dn, msg->dn) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Skipping user private group [%s].\n", + ldb_dn_get_linearized(msg->dn)); + continue; + } + + dn_list[dn_list_c] = ldb_dn_alloc_linearized(dn_list, msg->dn); + } else { + /* best effort, try to construct the DN */ + DEBUG(SSSDBG_TRACE_FUNC, + "sysdb_search_group_by_name failed with [%d], " + "generating DN for [%s] in domain [%s].\n", + ret, name_list[c], dom->name); + dn_list[dn_list_c] = sysdb_group_strdn(dn_list, dom->name, + name_list[c]); + } + if (dn_list[dn_list_c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_alloc_linearized failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "Added [%s][%s].\n", name_list[c], + dn_list[dn_list_c]); + dn_list_c++; + } + + *_dn_list = talloc_steal(mem_ctx, dn_list); + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t ipa_s2n_save_objects(struct sss_domain_info *dom, + struct req_input *req_input, + struct resp_attrs *attrs, + struct resp_attrs *simple_attrs, + const char *view_name, + struct sysdb_attrs *override_attrs, + struct sysdb_attrs *mapped_attrs, + bool update_initgr_timeout) +{ + int ret; + time_t now; + struct sss_nss_homedir_ctx homedir_ctx; + char *name = NULL; + char *upn = NULL; + gid_t gid; + gid_t orig_gid = 0; + TALLOC_CTX *tmp_ctx; + const char *sid_str; + const char *tmp_str; + struct ldb_result *res; + enum sysdb_member_type type; + char **sysdb_grouplist; + char **add_groups_dns; + char **del_groups_dns; + char **groups_dns; + bool in_transaction = false; + int tret; + struct sysdb_attrs *gid_override_attrs = NULL; + struct ldb_message *msg; + struct ldb_message_element *el = NULL; + + /* The list of elements that might be missing are: + * - SYSDB_ORIG_MEMBEROF + * - SYSDB_SSH_PUBKEY + * - SYSDB_USER_CERT + * Note that the list includes the trailing NULL at the end. */ + size_t missing_count = 0; + const char *missing[] = {NULL, NULL, NULL, NULL}; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + now = time(NULL); + + if (attrs->sysdb_attrs == NULL) { + attrs->sysdb_attrs = sysdb_new_attrs(attrs); + if (attrs->sysdb_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + } + + if (attrs->sysdb_attrs != NULL) { + ret = sysdb_attrs_get_string(attrs->sysdb_attrs, + ORIGINALAD_PREFIX SYSDB_NAME, &tmp_str); + if (ret == EOK) { + name = talloc_strdup(tmp_ctx, tmp_str); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found original AD name [%s].\n", name); + } else if (ret == ENOENT) { + name = NULL; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_string(attrs->sysdb_attrs, + SYSDB_DEFAULT_OVERRIDE_NAME, &tmp_str); + if (ret == EOK) { + ret = sysdb_attrs_add_lc_name_alias_safe(attrs->sysdb_attrs, + tmp_str); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_lc_name_alias_safe failed.\n"); + goto done; + } + } else if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_string(attrs->sysdb_attrs, SYSDB_UPN, &tmp_str); + if (ret == EOK) { + upn = talloc_strdup(tmp_ctx, tmp_str); + if (upn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found original AD upn [%s].\n", upn); + } else if (ret == ENOENT) { + upn = NULL; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + } + + if (strcmp(dom->name, attrs->domain_name) != 0) { + dom = find_domain_by_name(get_domains_head(dom), + attrs->domain_name, true); + if (dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot find domain: [%s]\n", attrs->domain_name); + ret = EINVAL; + goto done; + } + } + + switch (attrs->response_type) { + case RESP_USER: + case RESP_USER_GROUPLIST: + type = SYSDB_MEMBER_USER; + if (dom->subdomain_homedir + && attrs->a.user.pw_dir == NULL) { + memset(&homedir_ctx, 0, sizeof(homedir_ctx)); + homedir_ctx.username = attrs->a.user.pw_name; + homedir_ctx.uid = attrs->a.user.pw_uid; + homedir_ctx.domain = dom->name; + homedir_ctx.flatname = dom->flat_name; + homedir_ctx.config_homedir_substr = dom->homedir_substr; + + attrs->a.user.pw_dir = expand_homedir_template(attrs, + dom->subdomain_homedir, + dom->case_preserve, + &homedir_ctx); + if (attrs->a.user.pw_dir == NULL) { + ret = ENOMEM; + goto done; + } + } + + if (name == NULL) { + name = attrs->a.user.pw_name; + } + + ret = sysdb_attrs_add_lc_name_alias_safe(attrs->sysdb_attrs, name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_lc_name_alias_safe failed.\n"); + goto done; + } + + if (req_input->type == REQ_INP_SECID) { + ret = sysdb_attrs_add_string_safe(attrs->sysdb_attrs, + SYSDB_SID_STR, + req_input->inp.secid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_string failed.\n"); + goto done; + } + } + + if (simple_attrs != NULL + && simple_attrs->response_type == RESP_SID) { + ret = sysdb_attrs_add_string_safe(attrs->sysdb_attrs, + SYSDB_SID_STR, + simple_attrs->a.sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_string failed.\n"); + goto done; + } + } + + if (attrs->response_type == RESP_USER_GROUPLIST + && update_initgr_timeout) { + /* Since RESP_USER_GROUPLIST contains all group memberships it + * is effectively an initgroups request hence + * SYSDB_INITGR_EXPIRE will be set.*/ + ret = sysdb_attrs_add_time_t(attrs->sysdb_attrs, + SYSDB_INITGR_EXPIRE, + time(NULL) + dom->user_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_time_t failed.\n"); + goto done; + } + } + + gid = 0; + if (sss_domain_is_mpg(dom) == false) { + gid = attrs->a.user.pw_gid; + } else { + /* The extdom plugin always returns the objects with the + * default view applied. Since the GID is handled specially + * for MPG domains we have add any overridden GID separately. + */ + ret = sysdb_attrs_get_uint32_t(attrs->sysdb_attrs, + ORIGINALAD_PREFIX SYSDB_GIDNUM, + &orig_gid); + if (ret == EOK || ret == ENOENT) { + if ((orig_gid != 0 && orig_gid != attrs->a.user.pw_gid) + || attrs->a.user.pw_uid != attrs->a.user.pw_gid) { + + gid_override_attrs = sysdb_new_attrs(tmp_ctx); + if (gid_override_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_uint32(gid_override_attrs, + SYSDB_GIDNUM, + attrs->a.user.pw_gid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_uint32 failed.\n"); + goto done; + } + } + } else { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_uint32_t failed.\n"); + goto done; + } + } + + ret = sysdb_attrs_get_el_ext(attrs->sysdb_attrs, + SYSDB_ORIG_MEMBEROF, false, &el); + if (ret == ENOENT) { + missing[missing_count++] = SYSDB_ORIG_MEMBEROF; + } + + ret = sysdb_attrs_get_el_ext(attrs->sysdb_attrs, + SYSDB_SSH_PUBKEY, false, &el); + if (ret == ENOENT) { + missing[missing_count++] = SYSDB_SSH_PUBKEY; + } + + ret = sysdb_attrs_get_el_ext(attrs->sysdb_attrs, + SYSDB_USER_CERT, false, &el); + if (ret == ENOENT) { + missing[missing_count++] = SYSDB_USER_CERT; + } + + ret = sysdb_transaction_start(dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + ret = sysdb_store_user(dom, name, NULL, + attrs->a.user.pw_uid, + gid, attrs->a.user.pw_gecos, + attrs->a.user.pw_dir, attrs->a.user.pw_shell, + NULL, attrs->sysdb_attrs, + missing[0] == NULL ? NULL + : discard_const(missing), + dom->user_timeout, now); + if (ret == EEXIST && sss_domain_is_mpg(dom) == true) { + /* This handles the case where getgrgid() was called for + * this user, so a group was created in the cache + */ + ret = sysdb_search_group_by_name(tmp_ctx, dom, name, NULL, &msg); + if (ret != EOK) { + /* Fail even on ENOENT, the group must be around */ + DEBUG(SSSDBG_OP_FAILURE, + "Could not delete MPG group [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_delete_group(dom, NULL, attrs->a.user.pw_uid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_delete_group failed for MPG group [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_store_user(dom, name, NULL, + attrs->a.user.pw_uid, + gid, attrs->a.user.pw_gecos, + attrs->a.user.pw_dir, + attrs->a.user.pw_shell, + NULL, attrs->sysdb_attrs, NULL, + dom->user_timeout, now); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_store_user failed for MPG user [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_store_user failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (mapped_attrs != NULL) { + ret = sysdb_set_user_attr(dom, name, mapped_attrs, + SYSDB_MOD_ADD); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_user_attr failed.\n"); + goto done; + } + } + + if (gid_override_attrs != NULL) { + ret = sysdb_set_user_attr(dom, name, gid_override_attrs, + SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_user_attr failed.\n"); + goto done; + } + } + + if (attrs->response_type == RESP_USER_GROUPLIST) { + ret = get_sysdb_grouplist_dn(tmp_ctx, dom->sysdb, dom, name, + &sysdb_grouplist); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_sysdb_grouplist failed.\n"); + goto done; + } + + ret = get_groups_dns(tmp_ctx, dom, attrs->groups, &groups_dns); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_groups_dns failed.\n"); + goto done; + } + + ret = diff_string_lists(tmp_ctx, groups_dns, + sysdb_grouplist, &add_groups_dns, + &del_groups_dns, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "diff_string_lists failed.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Updating memberships for %s\n", + name); + ret = sysdb_update_members_dn(dom, name, SYSDB_MEMBER_USER, + (const char *const *) add_groups_dns, + (const char *const *) del_groups_dns); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Membership update failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + ret = sysdb_transaction_commit(dom->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + break; + case RESP_GROUP: + case RESP_GROUP_MEMBERS: + type = SYSDB_MEMBER_GROUP; + + if (name == NULL) { + name = attrs->a.group.gr_name; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Processing group %s\n", name); + + ret = sysdb_attrs_add_lc_name_alias_safe(attrs->sysdb_attrs, name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_lc_name_alias_safe failed.\n"); + goto done; + } + + /* We might already have the SID from other sources hence + * sysdb_attrs_add_string_safe is used to avoid double entries. */ + if (req_input->type == REQ_INP_SECID) { + ret = sysdb_attrs_add_string_safe(attrs->sysdb_attrs, + SYSDB_SID_STR, + req_input->inp.secid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_string failed.\n"); + goto done; + } + } + + if (simple_attrs != NULL + && simple_attrs->response_type == RESP_SID) { + ret = sysdb_attrs_add_string_safe(attrs->sysdb_attrs, + SYSDB_SID_STR, + simple_attrs->a.sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_string failed.\n"); + goto done; + } + } + + ret = process_members(dom, is_default_view(view_name), + attrs->sysdb_attrs, attrs->a.group.gr_mem, + NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "process_members failed.\n"); + goto done; + } + + ret = sysdb_store_group(dom, name, attrs->a.group.gr_gid, + attrs->sysdb_attrs, dom->group_timeout, + now); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_store_group failed.\n"); + goto done; + } + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unexpected response type [%d].\n", + attrs->response_type); + ret = EINVAL; + goto done; + } + + ret = sysdb_attrs_get_string(attrs->sysdb_attrs, SYSDB_SID_STR, &sid_str); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot find SID of object.\n"); + if (name != NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Object [%s] has no SID, please check the " + "ipaNTSecurityIdentifier attribute on the server-side.\n", + name); + } + goto done; + } + + ret = sysdb_search_object_by_sid(tmp_ctx, dom, sid_str, NULL, &res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot find object with override with SID [%s].\n", sid_str); + goto done; + } + + if (!is_default_view(view_name)) { + /* For the default view the data return by the extdom plugin already + * contains all needed data and it is not expected to have a separate + * override object. */ + ret = sysdb_store_override(dom, view_name, type, override_attrs, + res->msgs[0]->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_store_override failed.\n"); + goto done; + } + } + +done: + if (in_transaction) { + tret = sysdb_transaction_cancel(dom->sysdb); + if (tret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + + talloc_free(tmp_ctx); + + return ret; +} + +static void ipa_s2n_get_list_done(struct tevent_req *subreq) +{ + int ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_s2n_get_user_state *state = tevent_req_data(req, + struct ipa_s2n_get_user_state); + const char *sid_str; + struct dp_id_data *ar; + + ret = ipa_s2n_get_list_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "s2n get_fqlist request failed.\n"); + tevent_req_error(req, ret); + return; + } + + if (state->attrs == NULL) { + /* If this is a request by certificate we are done */ + if (state->req_input->type == REQ_INP_CERT) { + tevent_req_done(req); + } else { + tevent_req_error(req, EINVAL); + } + return; + } + + ret = sysdb_attrs_get_string(state->attrs->sysdb_attrs, SYSDB_SID_STR, + &sid_str); + if (ret == ENOENT) { + ret = ipa_s2n_save_objects(state->dom, state->req_input, state->attrs, + state->simple_attrs, NULL, NULL, NULL, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_save_objects failed.\n"); + goto fail; + } + tevent_req_done(req); + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto fail; + } + + ret = get_dp_id_data_for_sid(state, sid_str, state->dom->name, &ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + goto fail; + } + + if (state->override_attrs == NULL + && !is_default_view(state->ipa_ctx->view_name)) { + subreq = ipa_get_ad_override_send(state, state->ev, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->ipa_options, + dp_opt_get_string(state->ipa_ctx->ipa_options->basic, + IPA_KRB5_REALM), + state->ipa_ctx->view_name, + ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, ipa_s2n_get_user_get_override_done, + req); + } else { + ret = ipa_s2n_save_objects(state->dom, state->req_input, state->attrs, + state->simple_attrs, + state->ipa_ctx->view_name, + state->override_attrs, NULL, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_save_objects failed.\n"); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + } + + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static void ipa_s2n_get_user_get_override_done(struct tevent_req *subreq) +{ + int ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_s2n_get_user_state *state = tevent_req_data(req, + struct ipa_s2n_get_user_state); + struct sysdb_attrs *override_attrs = NULL; + + ret = ipa_get_ad_override_recv(subreq, NULL, state, &override_attrs); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); + tevent_req_error(req, ret); + return; + } + + ret = ipa_s2n_save_objects(state->dom, state->req_input, state->attrs, + state->simple_attrs, state->ipa_ctx->view_name, + override_attrs, NULL, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_save_objects failed.\n"); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +int ipa_s2n_get_acct_info_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_get_subdom_acct_process_pac_state { + struct tevent_context *ev; + struct sdap_handle *sh; + struct sss_domain_info *dom; + char *username; + + size_t num_missing_sids; + char **missing_sids; + size_t num_cached_groups; + char **cached_groups; +}; + +static void ipa_get_subdom_acct_process_pac_done(struct tevent_req *subreq); + +struct tevent_req *ipa_get_subdom_acct_process_pac_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_handle *sh, + struct ipa_id_ctx *ipa_ctx, + struct sss_domain_info *dom, + struct ldb_message *user_msg) +{ + int ret; + struct ipa_get_subdom_acct_process_pac_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + char *user_sid; + char *primary_group_sid; + size_t num_sids; + char **group_sids; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_get_subdom_acct_process_pac_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->sh = sh; + state->dom = dom; + + ret = ad_get_pac_data_from_user_entry(state, user_msg, + ipa_ctx->sdap_id_ctx->opts->idmap_ctx->map, + &state->username, + &user_sid, &primary_group_sid, + &num_sids, &group_sids); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ad_get_pac_data_from_user_entry failed.\n"); + goto done; + } + + ret = sdap_ad_tokengroups_get_posix_members(state, state->dom, + num_sids, group_sids, + &state->num_missing_sids, + &state->missing_sids, + &state->num_cached_groups, + &state->cached_groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sdap_ad_tokengroups_get_posix_members failed.\n"); + goto done; + } + + + if (state->num_missing_sids == 0) { + ret = sdap_ad_tokengroups_update_members(state->username, + state->dom->sysdb, + state->dom, + state->cached_groups); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Membership update failed [%d]: %s\n", + ret, strerror(ret)); + } + + goto done; + } + + + subreq = ipa_s2n_get_list_send(state, state->ev, ipa_ctx, state->dom, + state->sh, + dp_opt_get_int(ipa_ctx->sdap_id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + BE_REQ_BY_SECID, REQ_FULL, REQ_INP_SECID, + state->missing_sids, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_s2n_get_list_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_get_subdom_acct_process_pac_done, req); + + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void ipa_get_subdom_acct_process_pac_done(struct tevent_req *subreq) +{ + int ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_subdom_acct_process_pac_state *state = tevent_req_data(req, + struct ipa_get_subdom_acct_process_pac_state); + char **cached_groups; + size_t num_cached_groups; + + ret = ipa_s2n_get_list_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "s2n get_fqlist request failed.\n"); + tevent_req_error(req, ret); + return; + } + + /* from ad_pac.c */ + ret = sdap_ad_tokengroups_get_posix_members(state, state->dom, + state->num_missing_sids, + state->missing_sids, + NULL, NULL, + &num_cached_groups, + &cached_groups); + if (ret != EOK){ + DEBUG(SSSDBG_MINOR_FAILURE, + "sdap_ad_tokengroups_get_posix_members failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + state->cached_groups = concatenate_string_array(state, + state->cached_groups, + state->num_cached_groups, + cached_groups, + num_cached_groups); + if (state->cached_groups == NULL) { + ret = ENOMEM; + goto done; + } + + /* update membership of existing groups */ + ret = sdap_ad_tokengroups_update_members(state->username, + state->dom->sysdb, + state->dom, + state->cached_groups); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Membership update failed [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + return; +} + +errno_t ipa_get_subdom_acct_process_pac_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ipa/ipa_selinux.c b/src/providers/ipa/ipa_selinux.c new file mode 100644 index 0000000..16a8d7b --- /dev/null +++ b/src/providers/ipa/ipa_selinux.c @@ -0,0 +1,1698 @@ +/* + SSSD + + IPA Backend Module -- selinux loading + + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2012 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ +#include <security/pam_modules.h> + +#include "db/sysdb_selinux.h" +#include "util/child_common.h" +#include "util/sss_selinux.h" +#include "util/sss_chain_id.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_config.h" +#include "providers/ipa/ipa_selinux.h" +#include "providers/ipa/ipa_hosts.h" +#include "providers/ipa/ipa_hbac_rules.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ipa/ipa_access.h" +#include "providers/ipa/ipa_selinux_maps.h" +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ipa/ipa_rules_common.h" + +#ifndef SELINUX_CHILD_DIR +#ifndef SSSD_LIBEXEC_PATH +#error "SSSD_LIBEXEC_PATH not defined" +#endif /* SSSD_LIBEXEC_PATH */ + +#define SELINUX_CHILD_DIR SSSD_LIBEXEC_PATH +#endif /* SELINUX_CHILD_DIR */ + +#define SELINUX_CHILD SELINUX_CHILD_DIR"/selinux_child" +#define SELINUX_CHILD_LOG_FILE "selinux_child" + +#include <selinux/selinux.h> + +static struct tevent_req * +ipa_get_selinux_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sysdb_attrs *user, + struct sysdb_attrs *host, + struct ipa_selinux_ctx *selinux_ctx); +static errno_t ipa_get_selinux_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *count, + struct sysdb_attrs ***maps, + size_t *hbac_count, + struct sysdb_attrs ***hbac_rules, + char **default_user, + char **map_order); + +static void ipa_get_selinux_connect_done(struct tevent_req *subreq); +static void ipa_get_selinux_hosts_done(struct tevent_req *subreq); +static void ipa_get_config_step(struct tevent_req *req); +static void ipa_get_selinux_config_done(struct tevent_req *subreq); +static void ipa_get_selinux_maps_done(struct tevent_req *subreq); +static void ipa_get_selinux_hbac_done(struct tevent_req *subreq); +static errno_t ipa_selinux_process_maps(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *user, + struct sysdb_attrs *host, + struct sysdb_attrs **selinux_maps, + size_t selinux_map_count, + struct sysdb_attrs **hbac_rules, + size_t hbac_rule_count, + struct sysdb_attrs ***usermaps); + +static errno_t +ipa_save_user_maps(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + size_t map_count, + struct sysdb_attrs **maps) +{ + errno_t ret; + errno_t sret; + bool in_transaction = false; + int i; + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + for (i = 0; i < map_count; i++) { + ret = sysdb_store_selinux_usermap(domain, maps[i]); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store user map %d. " + "Ignoring.\n", i); + } else { + DEBUG(SSSDBG_TRACE_FUNC, "User map %d processed.\n", i); + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction!\n"); + goto done; + } + in_transaction = false; + ret = EOK; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + return ret; +} + +struct map_order_ctx { + char *order; + char **order_array; + size_t order_count; +}; + +struct selinux_child_input { + const char *seuser; + const char *mls_range; + const char *username; +}; + +static errno_t +ipa_selinux_process_seealso_maps(struct sysdb_attrs *user, + struct sysdb_attrs *host, + struct sysdb_attrs **seealso_rules, + size_t seealso_rules_count, + struct sysdb_attrs **hbac_rules, + size_t hbac_rule_count, + uint32_t top_priority, + struct sysdb_attrs **usermaps, + size_t best_match_maps_cnt); +static errno_t +ipa_selinux_process_maps(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *user, + struct sysdb_attrs *host, + struct sysdb_attrs **selinux_maps, + size_t selinux_map_count, + struct sysdb_attrs **hbac_rules, + size_t hbac_rule_count, + struct sysdb_attrs ***_usermaps) +{ + TALLOC_CTX *tmp_ctx; + int i; + errno_t ret; + uint32_t priority = 0; + uint32_t top_priority = 0; + struct sysdb_attrs **seealso_rules; + size_t num_seealso_rules = 0; + const char *seealso_str; + struct sysdb_attrs **usermaps; + size_t best_match_maps_cnt = 0; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + seealso_rules = talloc_zero_array(tmp_ctx, struct sysdb_attrs *, + selinux_map_count + 1); + if (seealso_rules == NULL) { + ret = ENOMEM; + goto done; + } + + usermaps = talloc_zero_array(tmp_ctx, struct sysdb_attrs *, selinux_map_count + 1); + if (usermaps == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < selinux_map_count; i++) { + if (sss_selinux_match(selinux_maps[i], user, host, &priority)) { + if (priority < top_priority) { + /* This rule has lower priority than what we already have, + * skip it. */ + continue; + } else if (priority > top_priority) { + /* This rule has higher priority, drop what we already have */ + while (best_match_maps_cnt > 0) { + best_match_maps_cnt--; + usermaps[best_match_maps_cnt] = NULL; + } + top_priority = priority; + } + + usermaps[best_match_maps_cnt] = selinux_maps[i]; + best_match_maps_cnt++; + + continue; + } + + /* SELinux map did not matched -> check sealso attribute for + * possible HBAC match */ + ret = sysdb_attrs_get_string(selinux_maps[i], + SYSDB_SELINUX_SEEALSO, &seealso_str); + if (ret == ENOENT) { + continue; + } else if (ret != EOK) { + goto done; + } + + seealso_rules[num_seealso_rules] = selinux_maps[i]; + num_seealso_rules++; + } + + ret = ipa_selinux_process_seealso_maps(user, host, + seealso_rules, num_seealso_rules, + hbac_rules, hbac_rule_count, + top_priority, usermaps, best_match_maps_cnt); + if (ret != EOK) { + goto done; + } + + *_usermaps = talloc_steal(mem_ctx, usermaps); + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ipa_selinux_process_seealso_maps(struct sysdb_attrs *user, + struct sysdb_attrs *host, + struct sysdb_attrs **seealso_rules, + size_t seealso_rules_count, + struct sysdb_attrs **hbac_rules, + size_t hbac_rule_count, + uint32_t top_priority, + struct sysdb_attrs **usermaps, + size_t best_match_maps_cnt) +{ + int i, j; + errno_t ret; + struct ldb_message_element *el; + struct sysdb_attrs *usermap; + const char *seealso_dn; + const char *hbac_dn; + uint32_t priority; + + for (i = 0; i < hbac_rule_count; i++) { + ret = sysdb_attrs_get_string(hbac_rules[i], SYSDB_ORIG_DN, &hbac_dn); + if (ret != EOK) { + return ret; + } + + /* We need to do this translation for further processing. We have to + * do it manually because no map was used to retrieve HBAC rules. + */ + ret = sysdb_attrs_get_el(hbac_rules[i], IPA_MEMBER_HOST, &el); + if (ret != EOK) return ret; + el->name = SYSDB_ORIG_MEMBER_HOST; + + ret = sysdb_attrs_get_el(hbac_rules[i], IPA_MEMBER_USER, &el); + if (ret != EOK) return ret; + el->name = SYSDB_ORIG_MEMBER_USER; + + DEBUG(SSSDBG_TRACE_ALL, + "Matching HBAC rule %s with SELinux mappings\n", hbac_dn); + + if (!sss_selinux_match(hbac_rules[i], user, host, &priority)) { + DEBUG(SSSDBG_TRACE_ALL, "Rule did not match\n"); + continue; + } + + /* HBAC rule matched, find if it is in the "possible" list */ + for (j = 0; j < seealso_rules_count; j++) { + usermap = seealso_rules[j]; + if (usermap == NULL) { + continue; + } + + ret = sysdb_attrs_get_string(usermap, SYSDB_SELINUX_SEEALSO, &seealso_dn); + if (ret != EOK) { + return ret; + } + + if (strcasecmp(hbac_dn, seealso_dn) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "HBAC rule [%s] matched, copying its" + "attributes to SELinux user map [%s]\n", + hbac_dn, seealso_dn); + + /* Selinux maps priority evaluation removed --DELETE this comment before pushing*/ + if (priority < top_priority) { + /* This rule has lower priority than what we already have, + * skip it. */ + continue; + } else if (priority > top_priority) { + /* This rule has higher priority, drop what we already have */ + while (best_match_maps_cnt > 0) { + best_match_maps_cnt--; + usermaps[best_match_maps_cnt] = NULL; + } + top_priority = priority; + } + + usermaps[best_match_maps_cnt] = usermap; + best_match_maps_cnt++; + + ret = sysdb_attrs_copy_values(hbac_rules[i], usermap, SYSDB_ORIG_MEMBER_USER); + if (ret != EOK) { + return ret; + } + + ret = sysdb_attrs_copy_values(hbac_rules[i], usermap, SYSDB_USER_CATEGORY); + if (ret != EOK) { + return ret; + } + + /* Speed up the next iteration */ + seealso_rules[j] = NULL; + } + } + } + + return EOK; +} + +static errno_t init_map_order_ctx(TALLOC_CTX *mem_ctx, const char *map_order, + struct map_order_ctx **_mo_ctx) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + int i; + int len; + struct map_order_ctx *mo_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + mo_ctx = talloc(tmp_ctx, struct map_order_ctx); + if (mo_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + /* The "order" string contains one or more SELinux user records + * separated by $. Now we need to create an array of string from + * this one string. First find out how many elements in the array + * will be. This way only one alloc will be necessary for the array + */ + mo_ctx->order_count = 1; + len = strlen(map_order); + for (i = 0; i < len; i++) { + if (map_order[i] == '$') mo_ctx->order_count++; + } + + mo_ctx->order_array = talloc_array(mo_ctx, char *, mo_ctx->order_count); + if (mo_ctx->order_array == NULL) { + ret = ENOMEM; + goto done; + } + + mo_ctx->order = talloc_strdup(mo_ctx, map_order); + if (mo_ctx->order == NULL) { + ret = ENOMEM; + goto done; + } + + /* Now fill the array with pointers to the original string. Also + * use binary zeros to make multiple string out of the one. + */ + mo_ctx->order_array[0] = mo_ctx->order; + mo_ctx->order_count = 1; + for (i = 0; i < len; i++) { + if (mo_ctx->order[i] == '$') { + mo_ctx->order[i] = '\0'; + mo_ctx->order_array[mo_ctx->order_count] = &mo_ctx->order[i+1]; + mo_ctx->order_count++; + } + } + + *_mo_ctx = talloc_steal(mem_ctx, mo_ctx); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t selinux_child_setup(TALLOC_CTX *mem_ctx, + const char *orig_name, + struct sss_domain_info *dom, + const char *seuser_mls_string, + struct selinux_child_input **_sci); + +/* Choose best selinux user based on given order and write + * the user to selinux login file. */ +static errno_t choose_best_seuser(TALLOC_CTX *mem_ctx, + struct sysdb_attrs **usermaps, + struct pam_data *pd, + struct sss_domain_info *user_domain, + struct map_order_ctx *mo_ctx, + const char *default_user, + struct selinux_child_input **_sci) +{ + TALLOC_CTX *tmp_ctx; + char *seuser_mls_str = NULL; + const char *tmp_str; + errno_t ret; + int i, j; + struct selinux_child_input *sci; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + /* If no maps match, we'll use the default SELinux user from the + * config */ + seuser_mls_str = talloc_strdup(tmp_ctx, default_user ? default_user : ""); + if (seuser_mls_str == NULL) { + ret = ENOMEM; + goto done; + } + + /* Iterate through the order array and try to find SELinux users + * in fetched maps. The order array contains all SELinux users + * allowed in the domain in the same order they should appear + * in the SELinux config file. If any user from the order array + * is not in fetched user maps, it means it should not be allowed + * for the user who is just logging in. + * + * Right now we have empty content of the SELinux config file, + * we shall add only those SELinux users that are present both in + * the order array and user maps applicable to the user who is + * logging in. + */ + for (i = 0; i < mo_ctx->order_count; i++) { + for (j = 0; usermaps[j] != NULL; j++) { + tmp_str = sss_selinux_map_get_seuser(usermaps[j]); + + if (tmp_str && !strcasecmp(tmp_str, mo_ctx->order_array[i])) { + /* If seuser_mls_str contained something, overwrite it. + * This record has higher priority. + */ + talloc_zfree(seuser_mls_str); + seuser_mls_str = talloc_strdup(tmp_ctx, tmp_str); + if (seuser_mls_str == NULL) { + ret = ENOMEM; + goto done; + } + break; + } + } + } + + ret = selinux_child_setup(tmp_ctx, pd->user, user_domain, seuser_mls_str, &sci); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set up child input buffer\n"); + goto done; + } + + *_sci = talloc_steal(mem_ctx, sci); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +selinux_child_setup(TALLOC_CTX *mem_ctx, + const char *orig_name, + struct sss_domain_info *dom, + const char *seuser_mls_string, + struct selinux_child_input **_sci) +{ + errno_t ret; + char *seuser; + const char *mls_range; + char *ptr; + char *username_final; + TALLOC_CTX *tmp_ctx; + struct selinux_child_input *sci; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Split seuser and mls_range */ + seuser = talloc_strdup(tmp_ctx, seuser_mls_string); + if (seuser == NULL) { + ret = ENOMEM; + goto done; + } + + ptr = seuser; + while (*ptr != ':' && *ptr != '\0') { + ptr++; + } + if (*ptr == '\0') { + /* No mls_range specified */ + mls_range = ""; + } else { + *ptr = '\0'; /* split */ + mls_range = ptr + 1; + } + + /* pam_selinux needs the username in the same format getpwnam() would + * return it + */ + username_final = sss_output_name(tmp_ctx, orig_name, + dom->case_preserve, 0); + if (dom->fqnames) { + username_final = sss_tc_fqname(tmp_ctx, dom->names, dom, username_final); + if (username_final == NULL) { + ret = ENOMEM; + goto done; + } + } + + sci = talloc(tmp_ctx, struct selinux_child_input); + if (sci == NULL) { + ret = ENOMEM; + goto done; + } + + sci->seuser = talloc_strdup(sci, seuser); + sci->mls_range = talloc_strdup(sci, mls_range); + sci->username = talloc_strdup(sci, username_final); + if (sci->seuser == NULL || sci->mls_range == NULL + || sci->username == NULL) { + ret = ENOMEM; + goto done; + } + + *_sci = talloc_steal(mem_ctx, sci); + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +struct selinux_child_state { + struct selinux_child_input *sci; + struct tevent_context *ev; + struct io_buffer *buf; + struct child_io_fds *io; +}; + +static errno_t selinux_child_create_buffer(struct selinux_child_state *state); +static errno_t selinux_fork_child(struct selinux_child_state *state); +static void selinux_child_step(struct tevent_req *subreq); +static void selinux_child_done(struct tevent_req *subreq); +static errno_t selinux_child_parse_response(uint8_t *buf, ssize_t len, + uint32_t *_child_result); + +static struct tevent_req *selinux_child_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct selinux_child_input *sci) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct selinux_child_state *state; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct selinux_child_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->sci = sci; + state->ev = ev; + state->io = talloc(state, struct child_io_fds); + state->buf = talloc(state, struct io_buffer); + if (state->io == NULL || state->buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto immediately; + } + + state->io->write_to_child_fd = -1; + state->io->read_from_child_fd = -1; + talloc_set_destructor((void *) state->io, child_io_destructor); + + ret = selinux_child_create_buffer(state); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create the send buffer\n"); + ret = ENOMEM; + goto immediately; + } + + ret = selinux_fork_child(state); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to fork the child\n"); + goto immediately; + } + + subreq = write_pipe_send(state, ev, state->buf->data, state->buf->size, + state->io->write_to_child_fd); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, selinux_child_step, req); + + ret = EOK; +immediately: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static errno_t selinux_child_create_buffer(struct selinux_child_state *state) +{ + size_t rp; + size_t seuser_len; + size_t mls_range_len; + size_t username_len; + + seuser_len = strlen(state->sci->seuser); + mls_range_len = strlen(state->sci->mls_range); + username_len = strlen(state->sci->username); + + state->buf->size = 3 * sizeof(uint32_t); + state->buf->size += seuser_len + mls_range_len + username_len; + + DEBUG(SSSDBG_TRACE_ALL, "buffer size: %zu\n", state->buf->size); + + state->buf->data = talloc_size(state->buf, state->buf->size); + if (state->buf->data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + rp = 0; + + /* seuser */ + SAFEALIGN_SET_UINT32(&state->buf->data[rp], seuser_len, &rp); + safealign_memcpy(&state->buf->data[rp], state->sci->seuser, + seuser_len, &rp); + + /* mls_range */ + SAFEALIGN_SET_UINT32(&state->buf->data[rp], mls_range_len, &rp); + safealign_memcpy(&state->buf->data[rp], state->sci->mls_range, + mls_range_len, &rp); + + /* username */ + SAFEALIGN_SET_UINT32(&state->buf->data[rp], username_len, &rp); + safealign_memcpy(&state->buf->data[rp], state->sci->username, + username_len, &rp); + + return EOK; +} + +static errno_t selinux_fork_child(struct selinux_child_state *state) +{ + int pipefd_to_child[2]; + int pipefd_from_child[2]; + pid_t pid; + errno_t ret; + const char **extra_args; + int c = 0; + + extra_args = talloc_array(state, const char *, 2); + + extra_args[c] = talloc_asprintf(extra_args, "--chain-id=%lu", + sss_chain_id_get()); + if (extra_args[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + return ret; + } + c++; + + extra_args[c] = NULL; + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe (from) failed [%d][%s].\n", errno, sss_strerror(errno)); + return ret; + } + + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe (to) failed [%d][%s].\n", errno, sss_strerror(errno)); + return ret; + } + + pid = fork(); + + if (pid == 0) { /* child */ + exec_child_ex(state, pipefd_to_child, pipefd_from_child, + SELINUX_CHILD, SELINUX_CHILD_LOG_FILE, extra_args, + false, STDIN_FILENO, STDOUT_FILENO); + DEBUG(SSSDBG_CRIT_FAILURE, "Could not exec selinux_child: [%d][%s].\n", + ret, sss_strerror(ret)); + return ret; + } else if (pid > 0) { /* parent */ + state->io->read_from_child_fd = pipefd_from_child[0]; + close(pipefd_from_child[1]); + state->io->write_to_child_fd = pipefd_to_child[1]; + close(pipefd_to_child[0]); + sss_fd_nonblocking(state->io->read_from_child_fd); + sss_fd_nonblocking(state->io->write_to_child_fd); + + ret = child_handler_setup(state->ev, pid, NULL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not set up child signal handler\n"); + return ret; + } + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fork failed [%d][%s].\n", errno, sss_strerror(errno)); + return ret; + } + + return EOK; +} + +static void selinux_child_step(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + struct selinux_child_state *state; + + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct selinux_child_state); + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + close(state->io->write_to_child_fd); + state->io->write_to_child_fd = -1; + + subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, selinux_child_done, req); +} + +static void selinux_child_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct selinux_child_state *state; + uint32_t child_result; + errno_t ret; + ssize_t len; + uint8_t *buf; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct selinux_child_state); + + ret = read_pipe_recv(subreq, state, &buf, &len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + close(state->io->read_from_child_fd); + state->io->read_from_child_fd = -1; + + ret = selinux_child_parse_response(buf, len, &child_result); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "selinux_child_parse_response failed: [%d][%s]\n", + ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } else if (child_result != 0){ + DEBUG(SSSDBG_CRIT_FAILURE, + "Error in selinux_child: [%d][%s]\n", + child_result, strerror(child_result)); + tevent_req_error(req, ERR_SELINUX_CONTEXT); + return; + } + + tevent_req_done(req); + return; +} + +static errno_t selinux_child_parse_response(uint8_t *buf, + ssize_t len, + uint32_t *_child_result) +{ + size_t p = 0; + uint32_t child_result; + + /* semanage retval */ + SAFEALIGN_COPY_UINT32_CHECK(&child_result, buf + p, len, &p); + + *_child_result = child_result; + return EOK; +} + +static errno_t selinux_child_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +/* A more generic request to gather all SELinux and HBAC rules. Updates + * cache if necessary + */ +struct ipa_get_selinux_state { + struct be_ctx *be_ctx; + struct ipa_selinux_ctx *selinux_ctx; + struct sdap_id_op *op; + + struct sysdb_attrs *host; + struct sysdb_attrs *user; + + struct sysdb_attrs *defaults; + struct sysdb_attrs **selinuxmaps; + size_t nmaps; + + struct sysdb_attrs **hbac_rules; + size_t hbac_rule_count; +}; + +static errno_t +ipa_get_selinux_maps_offline(struct tevent_req *req); + +static struct tevent_req * +ipa_get_selinux_send(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct sysdb_attrs *user, + struct sysdb_attrs *host, + struct ipa_selinux_ctx *selinux_ctx) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct ipa_get_selinux_state *state; + bool offline; + int ret = EOK; + time_t now; + time_t refresh_interval; + struct ipa_options *ipa_options = selinux_ctx->id_ctx->ipa_options; + + DEBUG(SSSDBG_TRACE_FUNC, "Retrieving SELinux user mapping\n"); + req = tevent_req_create(mem_ctx, &state, struct ipa_get_selinux_state); + if (req == NULL) { + return NULL; + } + + state->be_ctx = be_ctx; + state->selinux_ctx = selinux_ctx; + state->user = user; + state->host = host; + + offline = be_is_offline(be_ctx); + DEBUG(SSSDBG_TRACE_INTERNAL, "Connection status is [%s].\n", + offline ? "offline" : "online"); + + if (!offline) { + refresh_interval = dp_opt_get_int(ipa_options->basic, + IPA_SELINUX_REFRESH); + now = time(NULL); + if (now < selinux_ctx->last_update + refresh_interval) { + /* SELinux maps were recently updated -> force offline */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Performing cached SELinux processing\n"); + offline = true; + } + } + + if (!offline) { + state->op = sdap_id_op_create(state, + selinux_ctx->id_ctx->sdap_id_ctx->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto immediate; + } + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send failed: " + "%d(%s).\n", ret, strerror(ret)); + talloc_zfree(state->op); + goto immediate; + } + + tevent_req_set_callback(subreq, ipa_get_selinux_connect_done, req); + } else { + ret = ipa_get_selinux_maps_offline(req); + goto immediate; + } + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, be_ctx->ev); + return req; +} + +static void ipa_get_selinux_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_selinux_state *state = tevent_req_data(req, + struct ipa_get_selinux_state); + int dp_error = DP_ERR_FATAL; + int ret; + struct ipa_id_ctx *id_ctx = state->selinux_ctx->id_ctx; + struct dp_module *access_mod; + struct dp_module *selinux_mod; + const char *hostname; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (dp_error == DP_ERR_OFFLINE) { + talloc_zfree(state->op); + ret = ipa_get_selinux_maps_offline(req); + if (ret == EOK) { + tevent_req_done(req); + return; + } + goto fail; + } + + if (ret != EOK) { + goto fail; + } + + access_mod = dp_target_module(state->be_ctx->provider, DPT_ACCESS); + selinux_mod = dp_target_module(state->be_ctx->provider, DPT_SELINUX); + if (access_mod == selinux_mod && state->host != NULL) { + /* If the access control module is the same as the selinux module + * and the access control had already discovered the host + */ + return ipa_get_config_step(req); + } + + hostname = dp_opt_get_string(state->selinux_ctx->id_ctx->ipa_options->basic, + IPA_HOSTNAME); + if (hostname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot determine the host name\n"); + goto fail; + } + + subreq = ipa_host_info_send(state, state->be_ctx->ev, + sdap_id_op_handle(state->op), + id_ctx->sdap_id_ctx->opts, + hostname, + id_ctx->ipa_options->id->host_map, + NULL, + state->selinux_ctx->host_search_bases); + if (subreq == NULL) { + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, ipa_get_selinux_hosts_done, req); + return; + +fail: + tevent_req_error(req, ret); +} + +static errno_t +ipa_get_selinux_maps_offline(struct tevent_req *req) +{ + errno_t ret; + size_t nmaps; + struct ldb_message **maps; + struct ldb_message *defaults; + const char *attrs[] = { SYSDB_NAME, + SYSDB_USER_CATEGORY, + SYSDB_HOST_CATEGORY, + SYSDB_ORIG_MEMBER_USER, + SYSDB_ORIG_MEMBER_HOST, + SYSDB_SELINUX_SEEALSO, + SYSDB_SELINUX_USER, + NULL }; + const char **attrs_get_cached_rules; + const char *default_user; + const char *order; + + struct ipa_get_selinux_state *state = tevent_req_data(req, + struct ipa_get_selinux_state); + + /* read the config entry */ + ret = sysdb_search_selinux_config(state, state->be_ctx->domain, + NULL, &defaults); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_selinux_config failed [%d]: %s\n", + ret, strerror(ret)); + return ret; + } + + default_user = ldb_msg_find_attr_as_string(defaults, + SYSDB_SELINUX_DEFAULT_USER, + NULL); + order = ldb_msg_find_attr_as_string(defaults, SYSDB_SELINUX_DEFAULT_ORDER, + NULL); + + state->defaults = sysdb_new_attrs(state); + if (state->defaults == NULL) { + return ENOMEM; + } + + if (default_user) { + ret = sysdb_attrs_add_string(state->defaults, + IPA_CONFIG_SELINUX_DEFAULT_USER_CTX, + default_user); + if (ret != EOK) { + return ret; + } + } + + ret = sysdb_attrs_add_string(state->defaults, + IPA_CONFIG_SELINUX_MAP_ORDER, order); + if (ret != EOK) { + return ret; + } + + /* read all the SELinux rules */ + ret = sysdb_get_selinux_usermaps(state, state->be_ctx->domain, + attrs, &nmaps, &maps); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_selinux_usermaps failed [%d]: %s\n", + ret, strerror(ret)); + return ret; + } + + ret = sysdb_msg2attrs(state, nmaps, maps, &state->selinuxmaps); + if (ret != EOK) { + return ret; + } + state->nmaps = nmaps; + + /* read all the HBAC rules */ + attrs_get_cached_rules = hbac_get_attrs_to_get_cached_rules(state); + if (attrs_get_cached_rules == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "hbac_get_attrs_to_get_cached_rules() failed\n"); + return ENOMEM; + } + + ret = ipa_common_get_cached_rules(state, state->be_ctx->domain, + IPA_HBAC_RULE, HBAC_RULES_SUBDIR, + attrs_get_cached_rules, + &state->hbac_rule_count, + &state->hbac_rules); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_common_get_cached_rules failed [%d]: %s\n", + ret, strerror(ret)); + return ret; + } + + return EOK; +} + +static void ipa_get_selinux_hosts_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_selinux_state *state = tevent_req_data(req, + struct ipa_get_selinux_state); + size_t host_count, hostgroup_count; + struct sysdb_attrs **hostgroups; + struct sysdb_attrs **host; + + ret = ipa_host_info_recv(subreq, state, &host_count, &host, + &hostgroup_count, &hostgroups); + talloc_free(subreq); + if (ret != EOK) { + goto done; + } + state->host = host[0]; + + return ipa_get_config_step(req); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } +} + +static void ipa_get_config_step(struct tevent_req *req) +{ + const char *domain; + struct tevent_req *subreq; + struct ipa_get_selinux_state *state = tevent_req_data(req, + struct ipa_get_selinux_state); + struct ipa_id_ctx *id_ctx = state->selinux_ctx->id_ctx; + + domain = dp_opt_get_string(state->selinux_ctx->id_ctx->ipa_options->basic, + IPA_KRB5_REALM); + subreq = ipa_get_config_send(state, state->be_ctx->ev, + sdap_id_op_handle(state->op), + id_ctx->sdap_id_ctx->opts, + domain, NULL, NULL, NULL); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + } + tevent_req_set_callback(subreq, ipa_get_selinux_config_done, req); +} + +static void ipa_get_selinux_config_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_selinux_state *state = tevent_req_data(req, + struct ipa_get_selinux_state); + struct sdap_id_ctx *id_ctx = state->selinux_ctx->id_ctx->sdap_id_ctx; + errno_t ret; + + ret = ipa_get_config_recv(subreq, state, &state->defaults); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_IMPORTANT_INFO, "Could not get IPA config\n"); + goto done; + } + + subreq = ipa_selinux_get_maps_send(state, state->be_ctx->ev, + state->be_ctx->domain->sysdb, + sdap_id_op_handle(state->op), + id_ctx->opts, + state->selinux_ctx->id_ctx->ipa_options, + state->selinux_ctx->selinux_search_bases); + if (!subreq) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_get_selinux_maps_done, req); + return; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } +} + +static void ipa_get_selinux_maps_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ipa_get_selinux_state *state; + struct ipa_id_ctx *id_ctx; + struct dp_module *access_mod; + struct dp_module *selinux_mod; + const char **attrs_get_cached_rules; + const char *tmp_str; + bool check_hbac; + errno_t ret; + int i; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_get_selinux_state); + id_ctx = state->selinux_ctx->id_ctx; + + ret = ipa_selinux_get_maps_recv(subreq, state, + &state->nmaps, &state->selinuxmaps); + talloc_free(subreq); + if (ret != EOK) { + if (ret == ENOENT) { + /* This is returned if no SELinux mapping + * rules were found. In that case no error + * occurred, but we don't want any more processing.*/ + ret = EOK; + } + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Found %zu SELinux user maps\n", state->nmaps); + + check_hbac = false; + for (i = 0; i < state->nmaps; i++) { + ret = sysdb_attrs_get_string(state->selinuxmaps[i], + SYSDB_SELINUX_SEEALSO, &tmp_str); + if (ret == EOK) { + check_hbac = true; + break; + } + } + + if (check_hbac) { + access_mod = dp_target_module(state->be_ctx->provider, DPT_ACCESS); + selinux_mod = dp_target_module(state->be_ctx->provider, DPT_SELINUX); + if (access_mod == selinux_mod) { + attrs_get_cached_rules = hbac_get_attrs_to_get_cached_rules(state); + if (attrs_get_cached_rules == NULL) { + ret = ENOMEM; + goto done; + } + + ret = ipa_common_get_cached_rules(state, state->be_ctx->domain, + IPA_HBAC_RULE, HBAC_RULES_SUBDIR, + attrs_get_cached_rules, + &state->hbac_rule_count, + &state->hbac_rules); + /* Terminates the request */ + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "SELinux maps referenced an HBAC rule. " + "Need to refresh HBAC rules\n"); + subreq = ipa_hbac_rule_info_send(state, state->be_ctx->ev, + sdap_id_op_handle(state->op), + id_ctx->sdap_id_ctx->opts, + state->selinux_ctx->hbac_search_bases, + state->host); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ipa_get_selinux_hbac_done, req); + return; + } + + ret = EOK; +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static void ipa_get_selinux_hbac_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_selinux_state *state = tevent_req_data(req, + struct ipa_get_selinux_state); + errno_t ret; + + ret = ipa_hbac_rule_info_recv(subreq, state, &state->hbac_rule_count, + &state->hbac_rules); + DEBUG(SSSDBG_TRACE_INTERNAL, + "Received %zu HBAC rules\n", state->hbac_rule_count); + talloc_free(subreq); + + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } +} + +static errno_t +ipa_get_selinux_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *count, + struct sysdb_attrs ***maps, + size_t *hbac_count, + struct sysdb_attrs ***hbac_rules, + char **default_user, + char **map_order) +{ + struct ipa_get_selinux_state *state = + tevent_req_data(req, struct ipa_get_selinux_state); + const char *tmp_str; + errno_t ret; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + ret = sysdb_attrs_get_string(state->defaults, + IPA_CONFIG_SELINUX_DEFAULT_USER_CTX, + &tmp_str); + if (ret != EOK && ret != ENOENT) { + return ret; + } + + if (ret == EOK) { + *default_user = talloc_strdup(mem_ctx, tmp_str); + if (*default_user == NULL) { + return ENOMEM; + } + } + + ret = sysdb_attrs_get_string(state->defaults, IPA_CONFIG_SELINUX_MAP_ORDER, + &tmp_str); + if (ret != EOK) { + return ret; + } + + *map_order = talloc_strdup(mem_ctx, tmp_str); + if (*map_order == NULL) { + talloc_zfree(*default_user); + return ENOMEM; + } + + *count = state->nmaps; + *maps = talloc_steal(mem_ctx, state->selinuxmaps); + + *hbac_count = state->hbac_rule_count; + *hbac_rules = talloc_steal(mem_ctx, state->hbac_rules); + + return EOK; +} + +static errno_t +ipa_selinux_init_attrs(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *ipa_domain, + struct sss_domain_info *user_domain, + const char *username, + const char *hostname, + struct sysdb_attrs **_user, + struct sysdb_attrs **_host) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *host_dn; + const char *attrs[] = { SYSDB_ORIG_DN, + SYSDB_ORIG_MEMBEROF, + NULL }; + size_t count; + struct ldb_message **msgs; + struct sysdb_attrs **hosts; + struct sysdb_attrs *user = NULL; + struct sysdb_attrs *host = NULL; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sss_selinux_extract_user(tmp_ctx, user_domain, username, &user); + if (ret != EOK) { + goto done; + } + + host_dn = sysdb_custom_dn(tmp_ctx, ipa_domain, hostname, HBAC_HOSTS_SUBDIR); + if (host_dn == NULL) { + goto done; + } + + /* Look up the host to get its originalMemberOf entries */ + ret = sysdb_search_entry(tmp_ctx, sysdb, host_dn, LDB_SCOPE_BASE, NULL, + attrs, &count, &msgs); + if (ret == ENOENT || count == 0) { + host = NULL; + ret = EOK; + goto done; + } else if (ret != EOK) { + goto done; + } else if (count > 1) { + DEBUG(SSSDBG_OP_FAILURE, "More than one result for a BASE search!\n"); + goto done; + } + + ret = sysdb_msg2attrs(tmp_ctx, count, msgs, &hosts); + talloc_free(msgs); + if (ret != EOK) { + goto done; + } + + host = hosts[0]; + + ret = EOK; + +done: + if (ret == EOK) { + *_user = talloc_steal(mem_ctx, user); + *_host = talloc_steal(mem_ctx, host); + } + + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +ipa_selinux_store_config(struct sysdb_ctx *sysdb, + struct sss_domain_info *ipa_domain, + const char *default_user, + const char *map_order, + size_t map_count, + struct sysdb_attrs **maps) +{ + bool in_transaction = false; + errno_t sret; + errno_t ret; + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + ret = sysdb_delete_usermaps(ipa_domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot delete existing maps from sysdb\n"); + goto done; + } + + ret = sysdb_store_selinux_config(ipa_domain, default_user, map_order); + if (ret != EOK) { + goto done; + } + + if (map_count > 0) { + ret = ipa_save_user_maps(sysdb, ipa_domain, map_count, maps); + if (ret != EOK) { + goto done; + } + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not commit transaction\n"); + goto done; + } + in_transaction = false; + + ret = EOK; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not cancel transaction\n"); + } + } + + return ret; +} + +static errno_t +ipa_selinux_create_child_input(TALLOC_CTX *mem_ctx, + struct sysdb_attrs *user, + struct sysdb_attrs *host, + struct sysdb_attrs **maps, + size_t map_count, + struct sysdb_attrs **hbac_rules, + size_t hbac_count, + const char *map_order, + struct pam_data *pd, + struct sss_domain_info *user_domain, + const char *default_user, + struct selinux_child_input **_sci) +{ + struct sysdb_attrs **best_match_maps = NULL; + struct map_order_ctx *map_order_ctx = NULL; + struct selinux_child_input *sci = NULL; + errno_t ret; + + /* Process the maps and return list of best matches + * (maps with highest priority). */ + ret = ipa_selinux_process_maps(mem_ctx, user, host, maps, map_count, + hbac_rules, hbac_count, &best_match_maps); + if (ret != EOK) { + goto done; + } + + ret = init_map_order_ctx(mem_ctx, map_order, &map_order_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to create ordered SELinux users array.\n"); + goto done; + } + + ret = choose_best_seuser(mem_ctx, best_match_maps, pd, user_domain, + map_order_ctx, default_user, &sci); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to evaluate ordered SELinux users array.\n"); + goto done; + } + + *_sci = sci; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(best_match_maps); + talloc_free(map_order_ctx); + talloc_free(sci); + } + + return ret; +} + +struct ipa_selinux_handler_state { + struct be_ctx *be_ctx; + struct tevent_context *ev; + struct pam_data *pd; + + struct sss_domain_info *user_domain; + struct sss_domain_info *ipa_domain; + struct ipa_selinux_ctx *selinux_ctx; + + struct sysdb_attrs *user; + struct sysdb_attrs *host; +}; + +static void ipa_selinux_handler_get_done(struct tevent_req *subreq); +static void ipa_selinux_handler_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_selinux_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_selinux_ctx *selinux_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct ipa_selinux_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + const char *hostname; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_selinux_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->be_ctx = params->be_ctx; + state->ev = params->ev; + state->pd = pd; + state->user_domain = params->domain; + state->ipa_domain = params->be_ctx->domain; + state->selinux_ctx = selinux_ctx; + + pd->pam_status = PAM_SYSTEM_ERR; + + hostname = dp_opt_get_string(selinux_ctx->id_ctx->ipa_options->basic, + IPA_HOSTNAME); + if (hostname == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot determine this machine's host name\n"); + goto immediately; + } + + ret = ipa_selinux_init_attrs(state, state->user_domain->sysdb, + state->ipa_domain, state->user_domain, + pd->user, hostname, + &state->user, &state->host); + if (ret != EOK) { + goto immediately; + } + + subreq = ipa_get_selinux_send(state, params->be_ctx, state->user, + state->host, selinux_ctx); + if (subreq == NULL) { + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_selinux_handler_get_done, req); + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ipa_selinux_handler_get_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ipa_selinux_handler_state *state; + struct selinux_child_input *sci; + struct sysdb_attrs **hbac_rules = NULL; + struct sysdb_attrs **maps = NULL; + size_t map_count = 0; + size_t hbac_count = 0; + char *default_user = NULL; + char *map_order = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_selinux_handler_state); + + ret = ipa_get_selinux_recv(subreq, state, &map_count, &maps, + &hbac_count, &hbac_rules, + &default_user, &map_order); + talloc_free(subreq); + if (ret != EOK) { + goto done; + } + + ret = ipa_selinux_store_config(state->ipa_domain->sysdb, state->ipa_domain, + default_user, map_order, map_count, maps); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to store SELinux config [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ipa_selinux_create_child_input(state, state->user, state->host, + maps, map_count, hbac_rules, + hbac_count, map_order, state->pd, + state->user_domain, default_user, + &sci); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create child input [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Update the SELinux context in a privileged child as the back end is + * running unprivileged + */ + subreq = selinux_child_send(state, state->ev, sci); + if (subreq == NULL) { + goto done; + } + tevent_req_set_callback(subreq, ipa_selinux_handler_done, req); + return; + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +static void ipa_selinux_handler_done(struct tevent_req *subreq) +{ + struct ipa_selinux_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_selinux_handler_state); + + ret = selinux_child_recv(subreq); + talloc_free(subreq); + if (ret != EOK) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + if (!be_is_offline(state->be_ctx)) { + state->selinux_ctx->last_update = time(NULL); + } + + state->pd->pam_status = PAM_SUCCESS; + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +ipa_selinux_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct ipa_selinux_handler_state *state = NULL; + + state = tevent_req_data(req, struct ipa_selinux_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/ipa/ipa_selinux.h b/src/providers/ipa/ipa_selinux.h new file mode 100644 index 0000000..dea8775 --- /dev/null +++ b/src/providers/ipa/ipa_selinux.h @@ -0,0 +1,50 @@ +/* + SSSD + + IPA Backend Module -- selinux loading + + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2011 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef _IPA_SELINUX_H_ +#define _IPA_SELINUX_H_ + +#include "providers/ldap/ldap_common.h" + +struct ipa_selinux_ctx { + struct ipa_id_ctx *id_ctx; + time_t last_update; + + struct sdap_search_base **selinux_search_bases; + struct sdap_search_base **host_search_bases; + struct sdap_search_base **hbac_search_bases; +}; + +struct tevent_req * +ipa_selinux_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_selinux_ctx *selinux_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +ipa_selinux_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +#endif diff --git a/src/providers/ipa/ipa_selinux_maps.c b/src/providers/ipa/ipa_selinux_maps.c new file mode 100644 index 0000000..9abac4d --- /dev/null +++ b/src/providers/ipa/ipa_selinux_maps.c @@ -0,0 +1,222 @@ +/* + SSSD + + IPA Backend Module -- SELinux user maps (maps retrieval) + + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2012 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_selinux_maps.h" + +struct ipa_selinux_get_maps_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sdap_handle *sh; + struct sdap_options *opts; + struct ipa_options *ipa_opts; + const char **attrs; + + struct sdap_search_base **search_bases; + int search_base_iter; + + char *cur_filter; + char *maps_filter; + + size_t map_count; + struct sysdb_attrs **maps; +}; + +static errno_t +ipa_selinux_get_maps_next(struct tevent_req *req, + struct ipa_selinux_get_maps_state *state); +static void +ipa_selinux_get_maps_done(struct tevent_req *subreq); + +struct tevent_req *ipa_selinux_get_maps_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sysdb_ctx *sysdb, + struct sdap_handle *sh, + struct sdap_options *opts, + struct ipa_options *ipa_opts, + struct sdap_search_base **search_bases) +{ + struct tevent_req *req; + struct ipa_selinux_get_maps_state *state; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ipa_selinux_get_maps_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->sysdb = sysdb; + state->sh = sh; + state->opts = opts; + state->ipa_opts = ipa_opts; + state->search_bases = search_bases; + state->search_base_iter = 0; + state->map_count = 0; + state->maps = NULL; + + ret = build_attrs_from_map(state, ipa_opts->selinuxuser_map, + IPA_OPTS_SELINUX_USERMAP, NULL, + &state->attrs, NULL); + if (ret != EOK) goto fail; + + state->cur_filter = NULL; + state->maps_filter = talloc_asprintf(state, + "(&(objectclass=%s)(%s=TRUE))", + ipa_opts->selinuxuser_map[IPA_OC_SELINUX_USERMAP].name, + ipa_opts->selinuxuser_map[IPA_AT_SELINUX_USERMAP_ENABLED].name); + if (state->maps_filter == NULL) { + ret = ENOMEM; + goto fail; + } + + ret = ipa_selinux_get_maps_next(req, state); + if (ret == EOK) { + ret = EINVAL; + } + + if (ret != EAGAIN) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static errno_t +ipa_selinux_get_maps_next(struct tevent_req *req, + struct ipa_selinux_get_maps_state *state) +{ + struct sdap_search_base *base; + struct tevent_req *subreq; + + base = state->search_bases[state->search_base_iter]; + if (base == NULL) { + return EOK; + } + + talloc_zfree(state->cur_filter); + state->cur_filter = sdap_combine_filters(state, state->maps_filter, + base->filter); + if (state->cur_filter == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Trying to fetch SELinux maps with following " + "parameters: [%d][%s][%s]\n", base->scope, + state->cur_filter, base->basedn); + subreq = sdap_get_generic_send(state, state->ev, state->opts, + state->sh, base->basedn, + base->scope, state->cur_filter, + state->attrs, + state->ipa_opts->selinuxuser_map, + IPA_OPTS_SELINUX_USERMAP, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + true); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_selinux_get_maps_done, req); + return EAGAIN; +} + +static void ipa_selinux_get_maps_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_selinux_get_maps_state *state = tevent_req_data(req, + struct ipa_selinux_get_maps_state); + struct sysdb_attrs **results; + size_t total_count; + size_t count; + int i; + + ret = sdap_get_generic_recv(subreq, state, &count, &results); + if (ret != EOK) { + goto done; + } + + if (count > 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "Found %zu user maps in current search base\n", count); + + total_count = count + state->map_count; + state->maps = talloc_realloc(state, state->maps, struct sysdb_attrs *, total_count); + if (state->maps == NULL) { + ret = ENOMEM; + goto done; + } + + i = 0; + while (state->map_count < total_count) { + state->maps[state->map_count] = talloc_steal(state->maps, results[i]); + state->map_count++; + i++; + } + } + + state->search_base_iter++; + ret = ipa_selinux_get_maps_next(req, state); + if (ret == EAGAIN) { + return; + } else if (ret != EOK) { + goto done; + } + + if (state->map_count == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "No SELinux user maps found!\n"); + ret = ENOENT; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } +} + +errno_t +ipa_selinux_get_maps_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *count, + struct sysdb_attrs ***maps) +{ + struct ipa_selinux_get_maps_state *state = + tevent_req_data(req, struct ipa_selinux_get_maps_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *count = state->map_count; + *maps = talloc_steal(mem_ctx, state->maps); + + return EOK; +} diff --git a/src/providers/ipa/ipa_selinux_maps.h b/src/providers/ipa/ipa_selinux_maps.h new file mode 100644 index 0000000..d3abec1 --- /dev/null +++ b/src/providers/ipa/ipa_selinux_maps.h @@ -0,0 +1,45 @@ +/* + SSSD + + IPA Backend Module -- SELinux user maps (maps retrieval) + + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2012 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef IPA_SELINUX_MAPS_H_ +#define IPA_SELINUX_MAPS_H_ + +#include "providers/ldap/sdap_async.h" + +struct tevent_req * +ipa_selinux_get_maps_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sysdb_ctx *sysdb, + struct sdap_handle *sh, + struct sdap_options *opts, + struct ipa_options *ipa_opts, + struct sdap_search_base **search_bases); + +errno_t +ipa_selinux_get_maps_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + size_t *count, + struct sysdb_attrs ***maps); + +#endif /* IPA_SELINUX_MAPS_H_ */ diff --git a/src/providers/ipa/ipa_session.c b/src/providers/ipa/ipa_session.c new file mode 100644 index 0000000..bcd8055 --- /dev/null +++ b/src/providers/ipa/ipa_session.c @@ -0,0 +1,861 @@ +/* + SSSD + + IPA Backend Module -- Session Management + + Authors: + Fabiano Fidêncio <fidencio@redhat.com> + + Copyright (C) 2017 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ +#include <security/pam_modules.h> + +#include "util/child_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_config.h" +#include "providers/ipa/ipa_hosts.h" +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ipa/ipa_session.h" +#include "providers/ipa/ipa_rules_common.h" +#include "providers/ipa/ipa_deskprofile_private.h" +#include "providers/ipa/ipa_deskprofile_config.h" +#include "providers/ipa/ipa_deskprofile_rules.h" +#include "providers/ipa/ipa_deskprofile_rules_util.h" +#include "sss_iface/sss_iface_async.h" + + +/* Those here are used for sending a message to the deskprofile client + * informing that our side is done. */ +#define SSS_FLEETCOMMANDERCLIENT_BUS "org.freedesktop.FleetCommanderClient" +#define SSS_FLEETCOMMANDERCLIENT_PATH "/org/freedesktop/FleetCommanderClient" +#define SSS_FLEETCOMMANDERCLIENT_IFACE "org.freedesktop.FleetCommanderClient" + +#define MINUTE_IN_SECONDS 60 + +struct ipa_fetch_deskprofile_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct sdap_id_ctx *sdap_ctx; + struct ipa_session_ctx *session_ctx; + struct sdap_id_op *sdap_op; + struct dp_option *ipa_options; + struct sdap_search_base **search_bases; + const char *username; + + /* Hosts */ + struct ipa_common_entries *hosts; + struct sysdb_attrs *ipa_host; + + /* Rules */ + struct ipa_common_entries *rules; + struct sysdb_attrs *config; + uint16_t priority; +}; + +static errno_t ipa_fetch_deskprofile_retry(struct tevent_req *req); +static void ipa_fetch_deskprofile_connect_done(struct tevent_req *subreq); +static errno_t ipa_fetch_deskprofile_hostinfo(struct tevent_req *req); +static void ipa_fetch_deskprofile_hostinfo_done(struct tevent_req *subreq); +static void ipa_fetch_deskprofile_config_done(struct tevent_req *subreq); +static void ipa_fetch_deskprofile_rules_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_fetch_deskprofile_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_session_ctx *session_ctx, + const char *username) +{ + struct ipa_fetch_deskprofile_state *state; + struct tevent_req *req; + time_t now; + time_t refresh_interval; + time_t request_interval; + time_t next_request; + bool offline; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_fetch_deskprofile_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->session_ctx = session_ctx; + state->sdap_ctx = session_ctx->sdap_ctx; + state->ipa_options = session_ctx->ipa_options; + state->search_bases = session_ctx->deskprofile_search_bases; + state->username = username; + state->hosts = talloc_zero(state, struct ipa_common_entries); + if (state->hosts == NULL) { + ret = ENOMEM; + goto immediately; + } + state->rules = talloc_zero(state, struct ipa_common_entries); + if (state->rules == NULL) { + ret = ENOMEM; + goto immediately; + } + + if (state->search_bases == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No Desktop Profile search base found.\n"); + ret = EINVAL; + goto immediately; + } + + state->sdap_op = sdap_id_op_create(state, + state->sdap_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto immediately; + } + + now = time(NULL); + + request_interval = dp_opt_get_int(state->ipa_options, + IPA_DESKPROFILE_REQUEST_INTERVAL); + /* This value is in minutes ... */ + request_interval *= MINUTE_IN_SECONDS; + + if (state->session_ctx->no_rules_found && + now < session_ctx->last_request + request_interval) { + next_request = (session_ctx->last_request + request_interval - now); + /* This value is in seconds ... */ + next_request /= 60; + DEBUG(SSSDBG_TRACE_FUNC, + "No rules were found in the last request.\n" + "Next request will happen in any login after %"SPRItime" minutes\n", + next_request); + ret = ENOENT; + goto immediately; + } + + state->session_ctx->no_rules_found = false; + + offline = be_is_offline(be_ctx); + DEBUG(SSSDBG_TRACE_ALL, "Connection status is [%s].\n", + offline ? "offline" : "online"); + + refresh_interval = dp_opt_get_int(state->ipa_options, + IPA_DESKPROFILE_REFRESH); + + if (offline || now < session_ctx->last_update + refresh_interval) { + DEBUG(SSSDBG_TRACE_FUNC, + "Performing cached Desktop Profile evaluation\n"); + ret = EOK; + goto immediately; + } + + ret = ipa_fetch_deskprofile_retry(req); + if (ret != EAGAIN) { + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t +ipa_fetch_deskprofile_retry(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct ipa_fetch_deskprofile_state *state; + int ret; + + state = tevent_req_data(req, struct ipa_fetch_deskprofile_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sdap_id_op_connect_send() failed: %d (%s)\n", + ret, strerror(ret)); + + return ret; + } + + tevent_req_set_callback(subreq, ipa_fetch_deskprofile_connect_done, req); + + return EAGAIN; +} + +static void +ipa_fetch_deskprofile_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + ret = ipa_fetch_deskprofile_hostinfo(req); + if (ret == EAGAIN) { + return; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +static errno_t +ipa_fetch_deskprofile_hostinfo(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct ipa_fetch_deskprofile_state *state; + const char *hostname; + + state = tevent_req_data(req, struct ipa_fetch_deskprofile_state); + hostname = dp_opt_get_string(state->ipa_options, IPA_HOSTNAME); + + subreq = ipa_host_info_send(state, + state->ev, + sdap_id_op_handle(state->sdap_op), + state->sdap_ctx->opts, + hostname, + state->session_ctx->host_map, + state->session_ctx->hostgroup_map, + state->session_ctx->host_search_bases); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_fetch_deskprofile_hostinfo_done, req); + + return EAGAIN; +} + +static void +ipa_fetch_deskprofile_hostinfo_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ipa_fetch_deskprofile_state *state; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_fetch_deskprofile_state); + + ret = ipa_host_info_recv(subreq, state, + &state->hosts->entry_count, + &state->hosts->entries, + &state->hosts->group_count, + &state->hosts->groups); + state->hosts->entry_subdir = DESKPROFILE_HOSTS_SUBDIR; + state->hosts->group_subdir = DESKPROFILE_HOSTGROUPS_SUBDIR; + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + ret = ipa_get_host_attrs(state->ipa_options, + state->hosts->entry_count, + state->hosts->entries, + &state->ipa_host); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not locate IPA host.\n"); + goto done; + } + + subreq = ipa_deskprofile_get_config_send(state, + state->ev, + sdap_id_op_handle(state->sdap_op), + state->sdap_ctx->opts, + state->ipa_options); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ipa_fetch_deskprofile_config_done, req); + return; + +done: + tevent_req_error(req, ret); +} + +static void +ipa_fetch_deskprofile_config_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ipa_fetch_deskprofile_state *state; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_fetch_deskprofile_state); + + ret = ipa_deskprofile_get_config_recv(subreq, state, &state->config); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + ret = sysdb_store_custom(state->be_ctx->domain, IPA_DESKPROFILE_PRIORITY, + DESKPROFILE_CONFIG_SUBDIR, state->config); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save Desktop Profile policy\n"); + goto done; + } + + subreq = ipa_deskprofile_rule_info_send(state, + state->ev, + sdap_id_op_handle(state->sdap_op), + state->sdap_ctx->opts, + state->search_bases, + state->ipa_host, + state->be_ctx->domain, + state->username); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ipa_fetch_deskprofile_rules_done, req); + return; + +done: + tevent_req_error(req, ret); +} + +static void +ipa_fetch_deskprofile_rules_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ipa_fetch_deskprofile_state *state; + int dp_error; + errno_t ret; + bool found; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_fetch_deskprofile_state); + + ret = ipa_deskprofile_rule_info_recv(subreq, + state, + &state->rules->entry_count, + &state->rules->entries); + state->rules->entry_subdir = DESKPROFILE_RULES_SUBDIR; + talloc_zfree(subreq); + if (ret == ENOENT) { + /* Set ret to EOK so we can safely call sdap_id_op_done. */ + ret = EOK; + found = false; + } else if (ret == EOK) { + found = true; + } else { + goto done; + } + + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = ipa_fetch_deskprofile_retry(req); + if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* For now, let's completely purge the previous stored + * rules before saving the new ones */ + ret = ipa_common_purge_rules(state->be_ctx->domain, + DESKPROFILE_RULES_SUBDIR); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to remove Desktop Profile rules\n"); + goto done; + } + + if (!found) { + ret = ENOENT; + goto done; + } + + ret = ipa_common_save_rules(state->be_ctx->domain, + state->hosts, NULL, state->rules, + &state->session_ctx->last_update); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to save Desktop Profile rules\n"); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t +ipa_fetch_deskprofile_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_pam_session_handler_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct ipa_session_ctx *session_ctx; + struct pam_data *pd; + + /* Those attributes are used for: + * - saving the deskprofile rules to the disk; + * - deleting the deskprofile rules from the disk; + * - contacting the deskprofile client that everything is ready; + */ + char *shortname; + char *domain; + char *user_dir; + uid_t uid; + gid_t gid; +}; + +static errno_t +ipa_pam_session_handler_get_deskprofile_user_info( + TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *username, + char **_shortname, + char **_domain, + char **_user_dir, + uid_t *uid, + gid_t *gid); +static void ipa_pam_session_handler_done(struct tevent_req *subreq); +static errno_t +ipa_pam_session_handler_save_deskprofile_rules( + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + const char *username, /* fully-qualified */ + const char *user_dir, + const char *hostname, + uid_t uid, + gid_t gid); +static errno_t +ipa_pam_session_handler_notify_deskprofile_client(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uid_t uid, + const char *user_dir, + uint16_t prio); + + +struct tevent_req * +ipa_pam_session_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_session_ctx *session_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct ipa_pam_session_handler_state *state; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Retrieving Desktop Profile rules\n"); + req = tevent_req_create(mem_ctx, &state, + struct ipa_pam_session_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + state->ev = params->ev; + state->be_ctx = params->be_ctx; + state->session_ctx = session_ctx; + + /* Get all the user info that will be needed in order the delete the + * user's deskprofile directory from the disk, create the user's directory, + * save the fetched rules to the disk and notify the deskprofile client + * that this operation is done. */ + ret = ipa_pam_session_handler_get_deskprofile_user_info( + state, + params->domain, + pd->user, + &state->shortname, + &state->domain, + &state->user_dir, + &state->uid, + &state->gid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_deskprofile_get_user_info() failed [%d]: %s\n", + ret, sss_strerror(ret)); + state->pd->pam_status = PAM_SESSION_ERR; + goto done; + } + + /* As no proper merging mechanism has been implemented yet ... + * let's just remove the user directory stored in the disk as it's + * going to be created again in case there's any rule fetched. */ + ret = ipa_deskprofile_rules_remove_user_dir(state->user_dir, + state->uid, + state->gid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_deskprofile_rules_remove_user_dir() failed.\n"); + state->pd->pam_status = PAM_SESSION_ERR; + goto done; + } + + subreq = ipa_fetch_deskprofile_send(state, state->ev, state->be_ctx, + state->session_ctx, pd->user); + if (subreq == NULL) { + state->pd->pam_status = PAM_SESSION_ERR; + goto done; + } + + tevent_req_set_callback(subreq, ipa_pam_session_handler_done, req); + return req; + +done: + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void +ipa_pam_session_handler_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct ipa_pam_session_handler_state *state; + const char *hostname; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_pam_session_handler_state); + + ret = ipa_fetch_deskprofile_recv(subreq); + talloc_free(subreq); + + if (ret == ENOENT) { + DEBUG(SSSDBG_FUNC_DATA, "No Desktop Profile rules found\n"); + if (!state->session_ctx->no_rules_found) { + state->session_ctx->no_rules_found = true; + state->session_ctx->last_request = time(NULL); + } + state->pd->pam_status = PAM_SUCCESS; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to fetch Desktop Profile rules [%d]: %s\n", + ret, sss_strerror(ret)); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + state->session_ctx->last_request = time(NULL); + + hostname = dp_opt_get_string(state->session_ctx->ipa_options, IPA_HOSTNAME); + ret = ipa_pam_session_handler_save_deskprofile_rules(state->be_ctx, + state->be_ctx->domain, + state->pd->user, + state->user_dir, + hostname, + state->uid, + state->gid); + + if (ret == EOK || ret == ENOENT) { + state->pd->pam_status = PAM_SUCCESS; + } else { + state->pd->pam_status = PAM_SESSION_ERR; + } + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +ipa_pam_session_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct ipa_pam_session_handler_state *state = NULL; + + state = tevent_req_data(req, struct ipa_pam_session_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} + +static errno_t +ipa_pam_session_handler_get_deskprofile_user_info(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *username, + char **_shortname, + char **_domain, + char **_user_dir, + uid_t *_uid, + gid_t *_gid) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_result *res = NULL; + char *shortname; + char *domain_name; + char *user_dir; + uid_t uid; + gid_t gid; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmp_ctx, username, + &shortname, &domain_name); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Failed to parse \"%s\" [%d]: %s\n", + username, ret, sss_strerror(ret)); + goto done; + } + + user_dir = talloc_asprintf(tmp_ctx, IPA_DESKPROFILE_RULES_USER_DIR"/%s/%s", + domain_name, shortname); + if (user_dir == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed!\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_getpwnam(tmp_ctx, domain, username, &res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_getpwnam() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (res->count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_getpwnam() returned unexpected amount of users. " + "Expected [%d], got [%d]\n", 1, res->count); + ret = EINVAL; + goto done; + } + + uid = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_UIDNUM, 0); + gid = ldb_msg_find_attr_as_uint64(res->msgs[0], SYSDB_GIDNUM, 0); + if (uid == 0 || gid == 0) { + /* As IPA doesn't handle root users ou groups, we know for sure that's + * something wrong in case we get uid = 0 or gid = 0. + */ + ret = EINVAL; + goto done; + } + + ret = EOK; + + *_shortname = talloc_steal(mem_ctx, shortname); + *_domain = talloc_steal(mem_ctx, domain_name); + *_user_dir = talloc_steal(mem_ctx, user_dir); + *_uid = uid; + *_gid = gid; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ipa_pam_session_handler_save_deskprofile_rules( + struct be_ctx *be_ctx, + struct sss_domain_info *domain, + const char *username, /* fully-qualified */ + const char *user_dir, + const char *hostname, + uid_t uid, + gid_t gid) +{ + TALLOC_CTX *tmp_ctx; + const char **attrs_get_cached_rules; + size_t rule_count; + struct sysdb_attrs **rules; + uint16_t priority; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Get Desktop Profile priority from sysdb */ + ret = deskprofile_get_cached_priority(be_ctx->domain, &priority); + if (ret == ENOENT) { + DEBUG(SSSDBG_FUNC_DATA, "No Desktop Profile priority found in sysdb\n"); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "deskprofile_get_cached_priority() failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + + /* Get Desktop Profile rules from sysdb */ + attrs_get_cached_rules = deskprofile_get_attrs_to_get_cached_rules(tmp_ctx); + if (attrs_get_cached_rules == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "deskprofile_get_attrs_get_cached_rules() failed\n"); + ret = ENOMEM; + goto done; + } + ret = ipa_common_get_cached_rules(tmp_ctx, be_ctx->domain, + IPA_DESKPROFILE_RULE, + DESKPROFILE_RULES_SUBDIR, + attrs_get_cached_rules, + &rule_count, + &rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not retrieve Desktop Profile rules from the cache\n"); + goto done; + } + + /* nothing to do for FC */ + if (!rule_count) { + DEBUG(SSSDBG_FUNC_DATA, "No Desktop Profile rules found in sysdb\n"); + ret = ENOENT; + goto done; + } + + /* Create the user directory where the rules are going to be stored */ + ret = ipa_deskprofile_rules_create_user_dir(username, uid, gid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot create the user directory [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Save the rules to the disk */ + for (size_t i = 0; i < rule_count; i++) { + ret = ipa_deskprofile_rules_save_rule_to_disk(tmp_ctx, + priority, + rules[i], + domain, + hostname, + username, + uid, + gid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to save a Desktop Profile Rule to disk [%d]: %s\n", + ret, sss_strerror(ret)); + continue; + } + } + + /* Notify FleetCommander that our side is done */ + ret = ipa_pam_session_handler_notify_deskprofile_client(be_ctx, + be_ctx->ev, + uid, + user_dir, + priority); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "ipa_pam_session_handler_notify_deskprofile_client() " + "failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static void +ipa_pam_session_handler_notify_deskprofile_client_done(struct tevent_req *subreq); + +static errno_t +ipa_pam_session_handler_notify_deskprofile_client(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uid_t uid, + const char *user_dir, + uint16_t prio) +{ + struct sbus_connection *conn; + struct tevent_req *subreq; + + conn = sbus_connect_system(mem_ctx, ev, NULL, NULL); + if (conn == NULL) { + return ENOMEM; + } + + subreq = sbus_call_fleet_ProcessSSSDFiles_send(mem_ctx, conn, + SSS_FLEETCOMMANDERCLIENT_BUS, SSS_FLEETCOMMANDERCLIENT_PATH, + uid, user_dir, prio); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + talloc_free(conn); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_pam_session_handler_notify_deskprofile_client_done, + conn); + + return EOK; +} + +static void ipa_pam_session_handler_notify_deskprofile_client_done(struct tevent_req *subreq) +{ + struct sbus_connection *conn; + errno_t ret; + + conn = tevent_req_callback_data(subreq, struct sbus_connection); + + ret = sbus_call_fleet_ProcessSSSDFiles_recv(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Error sending sbus message [%d]: %s\n", + ret, sss_strerror(ret)); + } + + talloc_free(conn); +} diff --git a/src/providers/ipa/ipa_session.h b/src/providers/ipa/ipa_session.h new file mode 100644 index 0000000..0c4d54f --- /dev/null +++ b/src/providers/ipa/ipa_session.h @@ -0,0 +1,54 @@ +/* + SSSD + + IPA Backend Module -- Session Management + + Authors: + Fabiano Fidêncio <fidencio@redhat.com> + + Copyright (C) 2017 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef IPA_SESSION_H_ +#define IPA_SESSION_H_ + +#include "providers/ldap/ldap_common.h" + +struct ipa_session_ctx { + struct sdap_id_ctx *sdap_ctx; + struct dp_option *ipa_options; + time_t last_update; + time_t last_request; + bool no_rules_found; + + struct sdap_attr_map *host_map; + struct sdap_attr_map *hostgroup_map; + struct sdap_search_base **deskprofile_search_bases; + struct sdap_search_base **host_search_bases; +}; + +struct tevent_req * +ipa_pam_session_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_session_ctx *session_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +ipa_pam_session_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +#endif /* IPA_SESSION_H_ */ diff --git a/src/providers/ipa/ipa_srv.c b/src/providers/ipa/ipa_srv.c new file mode 100644 index 0000000..7477711 --- /dev/null +++ b/src/providers/ipa/ipa_srv.c @@ -0,0 +1,224 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2013 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <string.h> +#include <talloc.h> +#include <tevent.h> + +#include "util/util.h" +#include "resolv/async_resolv.h" +#include "providers/fail_over_srv.h" +#include "providers/ipa/ipa_srv.h" + +#define IPA_DNS_LOCATION "_location" + +struct ipa_srv_plugin_ctx { + struct resolv_ctx *resolv_ctx; + const char *hostname; + const char *ipa_domain; +}; + +struct ipa_srv_plugin_ctx * +ipa_srv_plugin_ctx_init(TALLOC_CTX *mem_ctx, + struct resolv_ctx *resolv_ctx, + const char *hostname, + const char *ipa_domain) +{ + struct ipa_srv_plugin_ctx *ctx = NULL; + + ctx = talloc_zero(mem_ctx, struct ipa_srv_plugin_ctx); + if (ctx == NULL) { + return NULL; + } + + ctx->resolv_ctx = resolv_ctx; + + ctx->hostname = talloc_strdup(ctx, hostname); + if (ctx->hostname == NULL) { + goto fail; + } + + ctx->ipa_domain = talloc_strdup(ctx, ipa_domain); + if (ctx->ipa_domain == NULL) { + goto fail; + } + + return ctx; + +fail: + talloc_free(ctx); + return NULL; +} + +struct ipa_srv_plugin_state { + char *dns_domain; + uint32_t ttl; + struct fo_server_info *primary_servers; + size_t num_primary_servers; + struct fo_server_info *backup_servers; + size_t num_backup_servers; +}; + +static void ipa_srv_plugin_done(struct tevent_req *subreq); + +/* If IPA server supports sites, we will use + * _locations.hostname.discovery_domain for primary servers and + * discovery_domain for backup servers. If the server does not support sites or + * client's SRV record is not found, we will use the latter for primary + * servers, setting backup servers to NULL */ +struct tevent_req *ipa_srv_plugin_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *service, + const char *protocol, + const char *discovery_domain, + void *pvt) +{ + struct ipa_srv_plugin_state *state = NULL; + struct ipa_srv_plugin_ctx *ctx = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + const char *primary_domain = NULL; + const char *backup_domain = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_srv_plugin_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + ctx = talloc_get_type(pvt, struct ipa_srv_plugin_ctx); + if (ctx == NULL) { + ret = EINVAL; + goto immediately; + } + + if (discovery_domain != NULL) { + backup_domain = talloc_strdup(state, discovery_domain); + } else { + backup_domain = talloc_strdup(state, ctx->ipa_domain); + } + if (backup_domain == NULL) { + ret = ENOMEM; + goto immediately; + } + + if (strchr(ctx->hostname, '.') == NULL) { + /* not FQDN, append domain name */ + primary_domain = talloc_asprintf(state, IPA_DNS_LOCATION ".%s.%s", + ctx->hostname, backup_domain); + } else { + primary_domain = talloc_asprintf(state, IPA_DNS_LOCATION ".%s", + ctx->hostname); + } + if (primary_domain == NULL) { + ret = ENOMEM; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_FUNC, "About to discover primary and " + "backup servers\n"); + + subreq = fo_discover_servers_send(state, ev, ctx->resolv_ctx, service, + protocol, primary_domain, backup_domain); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_srv_plugin_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void ipa_srv_plugin_done(struct tevent_req *subreq) +{ + struct ipa_srv_plugin_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_srv_plugin_state); + + ret = fo_discover_servers_recv(state, subreq, &state->dns_domain, + &state->ttl, + &state->primary_servers, + &state->num_primary_servers, + &state->backup_servers, + &state->num_backup_servers); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Got %zu primary and %zu backup servers\n", + state->num_primary_servers, state->num_backup_servers); + + tevent_req_done(req); +} + +errno_t ipa_srv_plugin_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_dns_domain, + uint32_t *_ttl, + struct fo_server_info **_primary_servers, + size_t *_num_primary_servers, + struct fo_server_info **_backup_servers, + size_t *_num_backup_servers) +{ + struct ipa_srv_plugin_state *state = NULL; + state = tevent_req_data(req, struct ipa_srv_plugin_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_primary_servers) { + *_primary_servers = talloc_steal(mem_ctx, state->primary_servers); + } + + if (_num_primary_servers) { + *_num_primary_servers = state->num_primary_servers; + } + + if (_backup_servers) { + *_backup_servers = talloc_steal(mem_ctx, state->backup_servers); + } + + if (_num_backup_servers) { + *_num_backup_servers = state->num_backup_servers; + } + + if (_dns_domain) { + *_dns_domain = talloc_steal(mem_ctx, state->dns_domain); + } + + if (_ttl) { + *_ttl = state->ttl; + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_srv.h b/src/providers/ipa/ipa_srv.h new file mode 100644 index 0000000..d089c9f --- /dev/null +++ b/src/providers/ipa/ipa_srv.h @@ -0,0 +1,48 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2013 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef __IPA_SRV_H__ +#define __IPA_SRV_H__ + +struct ipa_srv_plugin_ctx; + +struct ipa_srv_plugin_ctx * +ipa_srv_plugin_ctx_init(TALLOC_CTX *mem_ctx, + struct resolv_ctx *resolv_ctx, + const char *hostname, + const char *ipa_domain); + +struct tevent_req *ipa_srv_plugin_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *service, + const char *protocol, + const char *discovery_domain, + void *pvt); + +errno_t ipa_srv_plugin_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_dns_domain, + uint32_t *_ttl, + struct fo_server_info **_primary_servers, + size_t *_num_primary_servers, + struct fo_server_info **_backup_servers, + size_t *_num_backup_servers); + +#endif /* __IPA_SRV_H__ */ diff --git a/src/providers/ipa/ipa_subdomains.c b/src/providers/ipa/ipa_subdomains.c new file mode 100644 index 0000000..075f6f4 --- /dev/null +++ b/src/providers/ipa/ipa_subdomains.c @@ -0,0 +1,3180 @@ +/* + SSSD + + IPA Subdomains Module + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2011 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/sdap_ops.h" +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_id.h" +#include "providers/ipa/ipa_opts.h" +#include "providers/ipa/ipa_config.h" +#ifdef BUILD_PASSKEY +#include "providers/ipa/ipa_subdomains_passkey.h" +#endif /* BUILD_PASSKEY */ + +#include <ctype.h> + +#define SUBDOMAINS_FILTER "objectclass=ipaNTTrustedDomain" +#define MASTER_DOMAIN_FILTER "objectclass=ipaNTDomainAttrs" +#define RANGE_FILTER "objectclass=ipaIDRange" + +#define IPA_FLATNAME "ipaNTFlatName" +#define IPA_SID "ipaNTSecurityIdentifier" +#define IPA_ADDITIONAL_SUFFIXES "ipaNTAdditionalSuffixes" +#define IPA_SID_BLACKLIST_INCOMING "ipaNTSIDBlacklistIncoming" + +#define OBJECTCLASS "objectClass" + +#define IPA_ASSIGNED_ID_VIEW "ipaAssignedIDView" + +#define IPA_DOMAIN_RESOLUTION_ORDER "ipaDomainResolutionOrder" + +/* do not refresh more often than every 5 seconds for now */ +#define IPA_SUBDOMAIN_REFRESH_LIMIT 5 + +#define IPA_SUBDOMAIN_DISABLED_PERIOD 3600 + +#define IPA_OC_CERTMAP_CONFIG_OBJECT "ipaCertMapConfigObject" +#define IPA_CERTMAP_PROMPT_USERNAME "ipaCertMapPromptUserName" + +#define IPA_OC_CERTMAP_RULE "ipaCertMapRule" +#define IPA_CERTMAP_MAPRULE "ipaCertMapMapRule" +#define IPA_CERTMAP_MATCHRULE "ipaCertMapMatchRule" +#define IPA_CERTMAP_PRIORITY "ipaCertMapPriority" +#define IPA_ENABLED_FLAG "ipaEnabledFlag" +#define IPA_TRUE_VALUE "TRUE" +#define IPA_ASSOCIATED_DOMAIN "associatedDomain" +#define IPA_PASSKEY_VERIFICATION "ipaRequireUserVerification" +#define IPA_PASSKEY_CONFIG_FILTER "cn=passkeyconfig" + +#define OBJECTCLASS "objectClass" + +#define CERTMAP_FILTER "(|(&("OBJECTCLASS"="IPA_OC_CERTMAP_RULE")" \ + "("IPA_ENABLED_FLAG"="IPA_TRUE_VALUE"))" \ + "("OBJECTCLASS"="IPA_OC_CERTMAP_CONFIG_OBJECT"))" + +/* It doesn't make sense to resolve more servers than this from the SRV + * lookup because kinit would time out before we are able to cycle + * through the whole list + */ +#define MAX_SERVERS_FROM_SRV 5 + +struct ipa_sd_k5_svc_list { + struct krb5_service *k5svc; + + struct ipa_sd_k5_svc_list *next; + struct ipa_sd_k5_svc_list *prev; +}; + +static errno_t +ipa_subdom_reinit(struct ipa_subdomains_ctx *ctx) +{ + errno_t ret; + bool canonicalize = false; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Re-initializing domain %s\n", ctx->be_ctx->domain->name); + + if (ctx->ipa_id_ctx->ipa_options->auth_ctx != NULL + && ctx->ipa_id_ctx->ipa_options->auth_ctx->krb5_auth_ctx != NULL + && ctx->ipa_id_ctx->ipa_options->auth_ctx->krb5_auth_ctx->opts != NULL + ) { + canonicalize = dp_opt_get_bool( + ctx->ipa_id_ctx->ipa_options->auth_ctx->krb5_auth_ctx->opts, + KRB5_CANONICALIZE); + } else { + DEBUG(SSSDBG_CONF_SETTINGS, "Auth provider data is not available, " + "most probably because the auth provider " + "is not 'ipa'. Kerberos configuration " + "snippet to set the 'canonicalize' option " + "will not be created.\n"); + } + + ret = sss_write_krb5_conf_snippet( + dp_opt_get_string(ctx->ipa_id_ctx->ipa_options->basic, + IPA_KRB5_CONFD_PATH), + canonicalize, false); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "sss_write_krb5_conf_snippet failed.\n"); + /* Just continue */ + } + + ret = sysdb_master_domain_update(ctx->be_ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_master_domain_update failed.\n"); + return ret; + } + + ret = sysdb_update_subdomains(ctx->be_ctx->domain, ctx->be_ctx->cdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_update_subdomains failed.\n"); + return ret; + } + + ret = sss_write_domain_mappings(ctx->be_ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sss_krb5_write_mappings failed.\n"); + /* Just continue */ + } + + return EOK; +} + +struct priv_sss_debug { + int level; +}; + +static errno_t ipa_certmap_parse_results(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct sdap_options *sdap_opts, + size_t count, + struct sysdb_attrs **reply, + struct certmap_info ***_certmap_list) +{ + struct certmap_info **certmap_list = NULL; + struct certmap_info *m; + const char *value; + const char **values; + size_t c; + size_t lc = 0; + int ret; + const char **ocs = NULL; + bool user_name_hint = false; + + certmap_list = talloc_zero_array(mem_ctx, struct certmap_info *, count + 1); + if (certmap_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + return ENOMEM; + } + + for (c = 0; c < count; c++) { + ret = sysdb_attrs_get_string_array(reply[c], SYSDB_OBJECTCLASS, mem_ctx, + &ocs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing objectclasses for config objects.\n"); + ret = EINVAL; + goto done; + } + + if (string_in_list(IPA_OC_CERTMAP_CONFIG_OBJECT, discard_const(ocs), + false)) { + ret = sysdb_attrs_get_bool(reply[c], IPA_CERTMAP_PROMPT_USERNAME, + &user_name_hint); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read user name hint option, skipping.\n"); + } + continue; + } + + m = talloc_zero(certmap_list, struct certmap_info); + if (m == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(reply[c], IPA_CN, &value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + m->name = talloc_strdup(m, value); + if (m->name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(reply[c], IPA_CERTMAP_MATCHRULE, &value); + if (ret == EOK) { + m->match_rule = talloc_strdup(m, value); + if (m->match_rule == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } else if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_string(reply[c], IPA_CERTMAP_MAPRULE, &value); + if (ret == EOK) { + m->map_rule = talloc_strdup(m, value); + if (m->map_rule == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } else if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_string_array(reply[c], IPA_ASSOCIATED_DOMAIN, m, + &values); + if (ret == EOK) { + m->domains = values; + } else if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_uint32_t(reply[c], IPA_CERTMAP_PRIORITY, + &m->priority); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } else if (ret == ENOENT) { + m->priority = SSS_CERTMAP_MIN_PRIO; + } + + certmap_list[lc++] = m; + } + + certmap_list[lc] = NULL; + + ret = sdap_setup_certmap(sdap_opts->sdap_certmap_ctx, certmap_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_setup_certmap failed.\n"); + goto done; + } + + ret = sysdb_update_certmap(domain->sysdb, certmap_list, user_name_hint); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_update_certmap failed.\n"); + goto done; + } + + if (_certmap_list != NULL) { + *_certmap_list = certmap_list; + } else { + talloc_free(certmap_list); + } + + ret = EOK; + +done: + talloc_free(ocs); + if (ret != EOK) { + talloc_free(certmap_list); + } + + return ret; +} + +static errno_t ipa_subdom_enumerates(struct sss_domain_info *parent, + struct sysdb_attrs *attrs, + bool *_enumerates) +{ + errno_t ret; + const char *name; + + ret = sysdb_attrs_get_string(attrs, IPA_CN, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + return ret; + } + + *_enumerates = subdomain_enumerates(parent, name); + return EOK; +} + +static errno_t ipa_subdom_get_forest(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb_ctx, + struct sysdb_attrs *attrs, + char **_forest) +{ + int ret; + struct ldb_dn *dn = NULL; + const char *name; + const struct ldb_val *val; + char *forest = NULL; + + dn = ipa_subdom_ldb_dn(mem_ctx, ldb_ctx, attrs); + if (dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_subdom_ldb_dn failed.\n"); + ret = EIO; + goto done; + } + + if (ipa_subdom_is_member_dom(dn) == false) { + ret = sysdb_attrs_get_string(attrs, IPA_CN, &name); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + forest = talloc_strdup(mem_ctx, name); + if (forest == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "The forest name is %s\n", forest); + ret = EOK; + goto done; + } + + val = ldb_dn_get_component_val(dn, 1); + forest = talloc_strndup(mem_ctx, (const char *) val->data, val->length); + if (forest == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = EOK; +done: + talloc_free(dn); + + if (ret == EOK) { + *_forest = forest; + } + + return ret; +} + +static errno_t ipa_get_sd_trust_direction(struct sysdb_attrs *sd, + struct ipa_id_ctx *id_ctx, + struct ldb_context *ldb_ctx, + uint32_t *_direction) +{ + if (id_ctx->server_mode != NULL) { + return ipa_server_get_trust_direction(sd, ldb_ctx, _direction); + } else { + /* Clients do not have access to the trust objects's trust direction + * and don't generally care + */ + *_direction = 0; + return EOK; + } +} + +static errno_t ipa_subdom_store(struct sss_domain_info *parent, + struct ipa_id_ctx *id_ctx, + struct sdap_idmap_ctx *sdap_idmap_ctx, + struct sysdb_attrs *attrs) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + char *realm; + const char *flat; + const char *dns; + const char *id; + char *forest = NULL; + int ret; + bool use_id_mapping; + enum sss_domain_mpg_mode mpg_mode; + bool enumerate; + uint32_t direction; + struct ldb_message_element *alternative_domain_suffixes = NULL; + struct range_info *range; + const char *forest_id; + + tmp_ctx = talloc_new(parent); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_attrs_get_string(attrs, IPA_CN, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + realm = get_uppercase_realm(tmp_ctx, name); + if (!realm) { + ret = ENOMEM; + goto done; + } + + dns = talloc_strdup(tmp_ctx, name); + if (dns == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(attrs, IPA_FLATNAME, &flat); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_string(attrs, IPA_TRUSTED_DOMAIN_SID, &id); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_el_ext(attrs, IPA_ADDITIONAL_SUFFIXES, false, + &alternative_domain_suffixes); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + ret = ipa_subdom_get_forest(tmp_ctx, sysdb_ctx_get_ldb(parent->sysdb), + attrs, &forest); + if (ret != EOK) { + goto done; + } + + ret = ipa_subdom_enumerates(parent, attrs, &enumerate); + if (ret != EOK) { + goto done; + } + + ret = ipa_get_sd_trust_direction(attrs, id_ctx, + sysdb_ctx_get_ldb(parent->sysdb), + &direction); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_get_sd_trust_direction failed: %d\n", ret); + goto done; + } + + if (id_ctx->server_mode != NULL) { + DEBUG(SSSDBG_FUNC_DATA, + "Trust type of [%s]: %s\n", name, ipa_trust_dir2str(direction)); + } + + /* First see if there is an ID range for the domain. */ + ret = sysdb_get_range(tmp_ctx, parent->sysdb, id, &range); + if (ret == ENOENT) { + /* Check if there is ID range for the forest root. We need to find the + * domain in sysdb since the sss_domain_info object might not be yet + * created. */ + ret = sysdb_subdomain_get_id_by_name(tmp_ctx, parent->sysdb, forest, + &forest_id); + if (ret == EOK) { + ret = sysdb_get_range(tmp_ctx, parent->sysdb, forest_id, &range); + } + } + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, "Unable to find ID range for [%s] [%d]: %s\n", + name, ret, sss_strerror(ret)); + } + mpg_mode = ret == EOK ? range->mpg_mode : MPG_DEFAULT; + + DEBUG(SSSDBG_TRACE_FUNC, "Range mpg mode for %s: %s\n", + name, str_domain_mpg_mode(mpg_mode)); + + if (mpg_mode == MPG_DEFAULT) { + use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping( + sdap_idmap_ctx, name, id); + if (use_id_mapping == true) { + mpg_mode = MPG_ENABLED; + } else { + /* Domains that use the POSIX attributes set by the admin must + * inherit the MPG setting from the parent domain so that the + * auto_private_groups options works for trusted domains as well + */ + mpg_mode = get_domain_mpg_mode(parent); + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "Domain mpg mode for %s: %s\n", + name, str_domain_mpg_mode(mpg_mode)); + + ret = sysdb_subdomain_store(parent->sysdb, name, realm, flat, dns, + id, mpg_mode, enumerate, forest, + direction, alternative_domain_suffixes); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_subdomain_store failed.\n"); + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static struct krb5_service * +ipa_subdom_get_k5_svc(struct ipa_subdomains_ctx *ctx, + struct sss_domain_info *dom, + bool use_kdcinfo) +{ + struct ipa_sd_k5_svc_list *k5svc_ent; + + /* get the service by realm */ + DLIST_FOR_EACH(k5svc_ent, ctx->k5svc_list) { + if (strcasecmp(dom->realm, k5svc_ent->k5svc->realm) == 0) { + break; + } + } + + if (k5svc_ent != NULL) { + /* Already exists */ + return k5svc_ent->k5svc; + } + + /* Create a new service */ + k5svc_ent = talloc_zero(ctx, struct ipa_sd_k5_svc_list); + if (k5svc_ent == NULL) { + return NULL; + } + + k5svc_ent->k5svc = krb5_service_new(k5svc_ent, + ctx->be_ctx, + "IPA", + dom->realm, + use_kdcinfo, + (size_t) -1, + (size_t) -1); + if (k5svc_ent->k5svc == NULL) { + talloc_free(k5svc_ent); + return NULL; + } + DLIST_ADD(ctx->k5svc_list, k5svc_ent); + + return k5svc_ent->k5svc; +} + +static void ipa_subdom_remove_k5_svc(struct ipa_subdomains_ctx *ctx) +{ + /* Domain going away is such a rare operation that it makes + * more sense to just throw away the whole k5svc_list and let + * the write_kdcinfo request recreate them all again instead + * of coding up complex logic.. + */ + talloc_zfree(ctx->k5svc_list); +} + +static void ipa_subdom_remove_step(struct ipa_subdomains_ctx *ctx, + struct sss_domain_info *dom) +{ + if (dp_opt_get_bool(ctx->ipa_id_ctx->ipa_options->basic, + IPA_SERVER_MODE) == false) { + /* IPA clients keep track of krb5_service wrappers */ + return ipa_subdom_remove_k5_svc(ctx); + } else { + /* IPA servers keeps track of AD contexts */ + return ipa_ad_subdom_remove(ctx->be_ctx, ctx->ipa_id_ctx, dom); + } + +} + +static void ipa_subdom_store_step(struct sss_domain_info *parent, + struct ipa_id_ctx *id_ctx, + struct sdap_idmap_ctx *sdap_idmap_ctx, + struct sysdb_attrs *attrs) +{ + int ret; + + ret = ipa_subdom_store(parent, id_ctx, sdap_idmap_ctx, attrs); + if (ret == ERR_TRUST_NOT_SUPPORTED) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unsupported trust type, skipping\n"); + } else if (ret) { + /* Nothing we can do about the error. */ + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to parse subdom data, " + "will try to use cached subdomain\n"); + } +} + +static errno_t add_dom_sids_to_list(TALLOC_CTX *mem_ctx, const char **sids, + char ***list) +{ + size_t c; + errno_t ret; + + for (c = 0; sids != NULL && sids[c] != NULL; c++) { + if (is_domain_sid(sids[c])) { + ret = add_string_to_list(mem_ctx, sids[c], list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_string_to_list failed.\n"); + return ret; + } + } + } + + return EOK; +} + +static errno_t ipa_get_disabled_domain_sids(TALLOC_CTX *mem_ctx, size_t count, + struct sysdb_attrs **reply, + char ***disabled_domain_sids) +{ + size_t c; + char **dom_sid_list = NULL; + const char **tmp_list; + int ret; + + for (c = 0; c < count; c++) { + ret = sysdb_attrs_get_string_array(reply[c], IPA_SID_BLACKLIST_INCOMING, + mem_ctx, &tmp_list); + if (ret != EOK) { + if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_string_array failed, list of disabled " + "domains might be incomplete.\n"); + } + continue; + } + + ret = add_dom_sids_to_list(mem_ctx, tmp_list, &dom_sid_list); + talloc_free(tmp_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_dom_sids_to_list failed.\n"); + talloc_free(dom_sid_list); + return ret; + } + } + + *disabled_domain_sids = dom_sid_list; + + return EOK; +} + +static errno_t ipa_subdomains_check_domain_state(struct sss_domain_info *dom, + char **disabled_domain_sids) +{ + int ret; + + if (dom->domain_id == NULL) { + return EINVAL; + } + + if (disabled_domain_sids != NULL + && string_in_list(dom->domain_id, disabled_domain_sids, true)) { + DEBUG(SSSDBG_TRACE_ALL, "Domain [%s] is disabled on the server.\n", + dom->name); + /* disable domain if not already disabled */ + if (sss_domain_get_state(dom) != DOM_DISABLED) { + sss_domain_set_state(dom, DOM_DISABLED); + ret = sysdb_domain_set_enabled(dom->sysdb, dom->name, false); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_domain_set_enabled failed.\n"); + return ret; + } + + ret = sysdb_subdomain_content_delete(dom->sysdb, dom->name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_subdomain_content_delete failed.\n"); + return ret; + } + } + } else { + /* enabled domain if it was disabled */ + DEBUG(SSSDBG_TRACE_ALL, "Domain [%s] is enabled on the server.\n", + dom->name); + if (sss_domain_get_state(dom) == DOM_DISABLED) { + sss_domain_set_state(dom, DOM_ACTIVE); + ret = sysdb_domain_set_enabled(dom->sysdb, dom->name, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_domain_set_enabled failed.\n"); + return ret; + } + } + } + + return EOK; +} + + +static void ipa_subdomains_update_dom_state(struct sss_domain_info *parent, + int count, + struct sysdb_attrs **reply) +{ + int ret; + struct sss_domain_info *dom; + char **disabled_domain_sids = NULL; + + ret = ipa_get_disabled_domain_sids(reply, count, reply, + &disabled_domain_sids); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_disabled_domain_sids failed, " + "assuming no domain is disabled.\n"); + disabled_domain_sids = NULL; + } + + for (dom = get_next_domain(parent, SSS_GND_DESCEND|SSS_GND_INCLUDE_DISABLED); + dom && IS_SUBDOMAIN(dom); /* if we get back to a parent, stop */ + dom = get_next_domain(dom, SSS_GND_INCLUDE_DISABLED)) { + + /* check if domain should be disabled/enabled */ + ret = ipa_subdomains_check_domain_state(dom, disabled_domain_sids); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to check domain state, " + "state of domain [%s] might be wrong.\n", dom->name); + } + } +} + +static errno_t ipa_subdomains_refresh(struct ipa_subdomains_ctx *ctx, + int count, struct sysdb_attrs **reply, + bool *changes) +{ + struct sss_domain_info *parent, *dom; + bool handled[count]; + const char *value; + int c, h; + int ret; + + parent = ctx->be_ctx->domain; + memset(handled, 0, sizeof(bool) * count); + h = 0; + + if (changes == NULL) { + return EINVAL; + } + *changes = false; + + /* check existing subdomains */ + for (dom = get_next_domain(parent, SSS_GND_DESCEND); + dom && IS_SUBDOMAIN(dom); /* if we get back to a parent, stop */ + dom = get_next_domain(dom, 0)) { + for (c = 0; c < count; c++) { + if (handled[c]) { + continue; + } + ret = sysdb_attrs_get_string(reply[c], IPA_CN, &value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + if (strcmp(value, dom->name) == 0) { + break; + } + } + + if (c >= count) { + /* ok this subdomain does not exist anymore, let's clean up */ + sss_domain_set_state(dom, DOM_DISABLED); + ret = sysdb_subdomain_delete(dom->sysdb, dom->name); + if (ret != EOK) { + goto done; + } + + ipa_subdom_remove_step(ctx, dom); + } else { + /* ok let's try to update it */ + ipa_subdom_store_step(parent, ctx->ipa_id_ctx, + ctx->sdap_id_ctx->opts->idmap_ctx, + reply[c]); + handled[c] = true; + h++; + } + } + + if (count == h) { + /* all domains were already accounted for and have been updated */ + ret = EOK; + goto done; + } + + /* if we get here it means we have changes to the subdomains list */ + *changes = true; + + for (c = 0; c < count; c++) { + if (handled[c]) { + continue; + } + + ipa_subdom_store_step(parent, ctx->ipa_id_ctx, + ctx->sdap_id_ctx->opts->idmap_ctx, + reply[c]); + } + + ret = EOK; +done: + if (ret != EOK) { + ctx->last_refreshed = 0; + } else { + ctx->last_refreshed = time(NULL); + } + + return ret; +} + +static void clean_view_name(struct sss_domain_info *domain) +{ + struct sss_domain_info *dom = domain; + + while (dom) { + dom->has_views = false; + talloc_free(discard_const(dom->view_name)); + dom->view_name = NULL; + dom = get_next_domain(dom, SSS_GND_DESCEND); + } +} + +static errno_t ipa_apply_view(struct sss_domain_info *domain, + struct ipa_id_ctx *ipa_id_ctx, + const char *view_name, + bool read_at_init, + struct confdb_ctx *confdb) +{ + const char *current = ipa_id_ctx->view_name; + struct sysdb_ctx *sysdb = domain->sysdb; + bool in_transaction = false; + errno_t sret; + errno_t ret; + + DEBUG(SSSDBG_TRACE_ALL, "read_at_init [%s] current view [%s]\n", + read_at_init ? "true" : "false", ipa_id_ctx->view_name); + + if (current != NULL && strcmp(current, view_name) != 0 && read_at_init) { + DEBUG(SSSDBG_CRIT_FAILURE, "View name changed, this is not supported " + "at runtime. Please restart SSSD to get the new view applied.\n"); + return EOK; + } + + if (current != NULL && strcmp(current, view_name) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "View name did not change.\n"); + return EOK; + } + + DEBUG(SSSDBG_TRACE_FUNC, "View name changed to [%s].\n", view_name); + + /* View name changed. If there was a non-default non-local view + * was used the tree in cache containing the override values is + * removed. In all cases sysdb_invalidate_overrides() is called to + * remove the override attribute from the cached user objects. + * + * Typically ctx->sd_ctx->id_ctx->view_name == NULL means that the + * cache was empty but there was a bug in with caused that the + * view name was not written to the cache at all. In this case the + * cache must be invalidated if the new view is not the + * default-view as well. */ + + if (current != NULL || !is_default_view(view_name)) { + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to start transaction " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + in_transaction = true; + + if (!is_default_view(current) && !is_local_view(current)) { + /* Old view was not the default view, delete view tree */ + ret = sysdb_delete_view_tree(sysdb, current); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to delete old view tree " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + } + + ret = sysdb_invalidate_overrides(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, " Unable to invalidate overrides " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to commint transaction " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + in_transaction = false; + } + + ret = sysdb_update_view_name(sysdb, view_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot update view name " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + talloc_free(ipa_id_ctx->view_name); + ipa_id_ctx->view_name = talloc_strdup(ipa_id_ctx, view_name); + if (ipa_id_ctx->view_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot copy view name.\n"); + ret = ENOMEM; + goto done; + } + + if (!read_at_init) { + /* refresh view data of all domains at startup, since + * sysdb_master_domain_update and sysdb_update_subdomains might have + * been called earlier without the proper view name the name is + * cleaned here before the calls. This is acceptable because this is + * the initial setup (!read_at_init). */ + clean_view_name(domain); + ret = sysdb_master_domain_update(domain); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_master_domain_update failed " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_update_subdomains(domain, confdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_update_subdomains failed " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + } + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not cancel transaction\n"); + } + } + + return ret; +} + +struct ipa_subdomains_ranges_state { + struct sss_domain_info *domain; +}; + +static void ipa_subdomains_ranges_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_ranges_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_subdomains_ranges_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + const char *attrs[] = { OBJECTCLASS, IPA_CN, + IPA_BASE_ID, IPA_BASE_RID, IPA_SECONDARY_BASE_RID, + IPA_ID_RANGE_SIZE, IPA_TRUSTED_DOMAIN_SID, + IPA_RANGE_TYPE, IPA_ID_RANGE_MPG, NULL }; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_ranges_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (sd_ctx->ranges_search_bases == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "No search base is set\n"); + ret = EOK; + goto immediately; + } + + state->domain = sd_ctx->be_ctx->domain; + + subreq = sdap_search_bases_send(state, ev, sd_ctx->sdap_id_ctx->opts, sh, + sd_ctx->ranges_search_bases, NULL, false, + 0, RANGE_FILTER, attrs, NULL); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_ranges_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void ipa_subdomains_ranges_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_ranges_state *state; + struct tevent_req *req; + struct range_info **range_list; + struct sysdb_attrs **reply; + size_t reply_count; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_ranges_state); + + ret = sdap_search_bases_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get data from LDAP [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ipa_ranges_parse_results(state, state->domain->name, + reply_count, reply, &range_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to parse range resulg [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_update_ranges(state->domain->sysdb, range_list); + talloc_free(range_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to update ranges [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_subdomains_ranges_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +#define IPA_CERTMAP_SEARCH_BASE_TEMPLATE "cn=certmap,%s" + +struct ipa_subdomains_certmap_state { + struct sss_domain_info *domain; + struct sdap_options *sdap_opts; +}; + +static void ipa_subdomains_certmap_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_certmap_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_subdomains_certmap_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + char *ldap_basedn; + char *search_base; + const char *attrs[] = { OBJECTCLASS, IPA_CN, + IPA_CERTMAP_MAPRULE, IPA_CERTMAP_MATCHRULE, + IPA_CERTMAP_PRIORITY, IPA_ASSOCIATED_DOMAIN, + IPA_CERTMAP_PROMPT_USERNAME, + NULL }; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_certmap_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->domain = sd_ctx->be_ctx->domain; + state->sdap_opts = sd_ctx->sdap_id_ctx->opts; + + ret = domain_to_basedn(state, state->domain->name, &ldap_basedn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "domain_to_basedn failed.\n"); + goto immediately; + } + + search_base = talloc_asprintf(state, IPA_CERTMAP_SEARCH_BASE_TEMPLATE, + ldap_basedn); + if (search_base == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto immediately; + } + + subreq = sdap_get_generic_send(state, ev, sd_ctx->sdap_id_ctx->opts, sh, + search_base, LDAP_SCOPE_SUBTREE, + CERTMAP_FILTER, + attrs, NULL, 0, 0, false); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_certmap_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void ipa_subdomains_certmap_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_certmap_state *state; + struct tevent_req *req; + struct sysdb_attrs **reply; + size_t reply_count; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_certmap_state); + + ret = sdap_get_generic_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get data from LDAP [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ipa_certmap_parse_results(state, state->domain, + state->sdap_opts, + reply_count, reply, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to parse certmap results [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_subdomains_certmap_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_subdomains_master_state { + struct sss_domain_info *domain; + struct ipa_options *ipa_options; +}; + +static void ipa_subdomains_master_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_master_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_subdomains_master_state *state; + struct sss_domain_info *domain; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + const char *attrs[] = { IPA_CN, IPA_FLATNAME, IPA_SID, + IPA_ADDITIONAL_SUFFIXES, NULL }; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_master_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (sd_ctx->master_search_bases == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "No search base is set\n"); + ret = EOK; + goto immediately; + } + + state->domain = domain = sd_ctx->be_ctx->domain; + state->ipa_options = sd_ctx->ipa_id_ctx->ipa_options; + + ret = sysdb_master_domain_update(domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to update master domain [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediately; + } + + if (domain->flat_name != NULL && domain->domain_id != NULL + && domain->dns_name != NULL + && domain->realm != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Master record is up to date.\n"); + ret = EOK; + goto immediately; + } + + subreq = sdap_search_bases_return_first_send(state, ev, + sd_ctx->sdap_id_ctx->opts, sh, + sd_ctx->master_search_bases, NULL, false, + 0, MASTER_DOMAIN_FILTER, attrs, NULL); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_master_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void ipa_subdomains_master_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_master_state *state; + struct tevent_req *req; + struct sysdb_attrs **reply; + size_t reply_count; + const char *flat = NULL; + const char *dns = NULL; + const char *id = NULL; + const char *realm = NULL; + struct ldb_message_element *alternative_domain_suffixes = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_master_state); + + ret = sdap_search_bases_return_first_recv(subreq, state, + &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get data from LDAP [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (reply_count > 0) { + ret = sysdb_attrs_get_string(reply[0], IPA_FLATNAME, &flat); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_get_string(reply[0], IPA_SID, &id); + if (ret != EOK) { + goto done; + } + + ret = sysdb_attrs_get_el_ext(reply[0], IPA_ADDITIONAL_SUFFIXES, false, + &alternative_domain_suffixes); + if (ret != EOK && ret != ENOENT) { + goto done; + } + } else { + /* All search paths are searched and no master domain record was + * found. + * + * A default IPA installation will not have a master domain record, + * this is only created by ipa-adtrust-install. Nevertheless we should + * continue to read other data like the idview on IPA clients. */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Master domain record not found!\n"); + } + + realm = dp_opt_get_string(state->ipa_options->basic, IPA_KRB5_REALM); + if (realm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No Kerberos realm for IPA?\n"); + ret = EINVAL; + goto done; + } + + dns = dp_opt_get_string(state->ipa_options->basic, IPA_DOMAIN); + if (dns == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No domain name for IPA?\n"); + ret = EINVAL; + goto done; + } + + ret = sysdb_master_domain_add_info(state->domain, realm, flat, dns, id, NULL, + alternative_domain_suffixes); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add master domain info " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_subdomains_master_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_subdomains_slave_state { + struct ipa_subdomains_ctx *sd_ctx; + struct be_ctx *be_ctx; + struct ipa_id_ctx *ipa_id_ctx; +}; + +static void ipa_subdomains_slave_search_done(struct tevent_req *subreq); +static void ipa_subdomains_slave_trusts_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_slave_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_subdomains_slave_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + const char *attrs[] = { IPA_CN, IPA_FLATNAME, IPA_TRUSTED_DOMAIN_SID, + IPA_TRUST_DIRECTION, IPA_ADDITIONAL_SUFFIXES, + IPA_SID_BLACKLIST_INCOMING, NULL }; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_slave_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (sd_ctx->search_bases == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "No search base is set\n"); + ret = EOK; + goto immediately; + } + + state->sd_ctx = sd_ctx; + state->be_ctx = sd_ctx->be_ctx; + state->ipa_id_ctx = sd_ctx->ipa_id_ctx; + + subreq = sdap_search_bases_send(state, ev, sd_ctx->sdap_id_ctx->opts, sh, + sd_ctx->search_bases, NULL, false, + 0, SUBDOMAINS_FILTER, attrs, NULL); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_slave_search_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t ipa_enable_enterprise_principals(struct be_ctx *be_ctx) +{ + int ret; + struct sss_domain_info *d; + TALLOC_CTX *tmp_ctx; + char **vals = NULL; + struct dp_module *auth; + struct krb5_ctx *krb5_auth_ctx; + + d = get_domains_head(be_ctx->domain); + + while (d != NULL) { + DEBUG(SSSDBG_TRACE_ALL, "checking [%s].\n", d->name); + if (d->upn_suffixes != NULL) { + break; + } + d = get_next_domain(d, SSS_GND_DESCEND); + } + + if (d == NULL) { + DEBUG(SSSDBG_TRACE_ALL, + "No UPN suffixes found, " + "no need to enable enterprise principals.\n"); + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + ret = confdb_get_param(be_ctx->cdb, tmp_ctx, be_ctx->conf_path, + ipa_def_krb5_opts[KRB5_USE_ENTERPRISE_PRINCIPAL].opt_name, + &vals); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_param failed.\n"); + goto done; + } + + if (vals[0]) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Parameter [%s] set in config file and will not be changed.\n", + ipa_def_krb5_opts[KRB5_USE_ENTERPRISE_PRINCIPAL].opt_name); + return EOK; + } + + auth = dp_target_module(be_ctx->provider, DPT_AUTH); + if (auth == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to find auth proivder.\n"); + ret = EINVAL; + goto done; + } + + krb5_auth_ctx = ipa_init_get_krb5_auth_ctx(dp_get_module_data(auth)); + if (krb5_auth_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to find auth proivder data.\n"); + ret = EINVAL; + goto done; + } + + ret = dp_opt_set_bool(krb5_auth_ctx->opts, + KRB5_USE_ENTERPRISE_PRINCIPAL, true); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "dp_opt_set_bool failed.\n"); + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Enterprise principals enabled.\n"); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static void ipa_subdomains_slave_search_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_slave_state *state; + struct tevent_req *req; + struct sysdb_attrs **reply; + size_t reply_count; + bool has_changes = false; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_slave_state); + + ret = sdap_search_bases_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get data from LDAP [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ipa_subdomains_refresh(state->sd_ctx, reply_count, reply, + &has_changes); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to refresh subdomains.\n"); + goto done; + } + + ret = ipa_enable_enterprise_principals(state->sd_ctx->be_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_enable_enterprise_principals failed. " + "Enterprise principals might not work as " + "expected.\n"); + } + + /* If there are no changes this step can be skipped, but + * ipa_subdomains_update_dom_state() must be called after that in all case + * to cover existing an newly added domains. Since the domain state is not + * handled by a domain flag but by the blacklist has_changes does not + * cover the state. */ + if (has_changes) { + ret = ipa_subdom_reinit(state->sd_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not reinitialize subdomains\n"); + goto done; + } + } + + ipa_subdomains_update_dom_state(state->sd_ctx->be_ctx->domain, + reply_count, reply); + + if (!has_changes || state->sd_ctx->ipa_id_ctx->server_mode == NULL) { + ret = EOK; + goto done; + } + + subreq = ipa_server_create_trusts_send(state, state->be_ctx->ev, + state->be_ctx, state->ipa_id_ctx, + state->be_ctx->domain); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ipa_subdomains_slave_trusts_done, req); + return; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static void ipa_subdomains_slave_trusts_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = ipa_server_create_trusts_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create trusts [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_subdomains_slave_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_subdomains_view_name_state { + struct ipa_subdomains_ctx *sd_ctx; +}; + +static void ipa_subdomains_view_name_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_view_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_subdomains_view_name_state *state; + struct sdap_attr_map_info *maps; + struct tevent_req *subreq; + struct tevent_req *req; + struct ipa_options *ipa_options; + const char *filter; + const char *attrs[] = {IPA_CN, OBJECTCLASS, NULL}; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_view_name_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (sd_ctx->ipa_id_ctx->server_mode != NULL) { + /* Only get view on clients, on servers it is always 'default'. */ + ret = EOK; + goto immediately; + } + + state->sd_ctx = sd_ctx; + + ipa_options = sd_ctx->ipa_id_ctx->ipa_options; + + maps = talloc_zero_array(state, struct sdap_attr_map_info, 2); + if (maps == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero() failed\n"); + ret = ENOMEM; + goto immediately; + } + maps[0].map = ipa_options->view_map; + maps->num_attrs = IPA_OPTS_VIEW; + + filter = talloc_asprintf(state, "(&(objectClass=%s)(%s=%s))", + ipa_options->id->host_map[SDAP_OC_HOST].name, + ipa_options->id->host_map[SDAP_AT_HOST_FQDN].name, + dp_opt_get_string(ipa_options->basic, IPA_HOSTNAME)); + if (filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* We add SDAP_DEREF_FLG_SILENT because old IPA servers don't have + * the attribute we dereference, causing the deref call to fail. */ + subreq = sdap_deref_bases_return_first_send(state, ev, + sd_ctx->sdap_id_ctx->opts, sh, sd_ctx->host_search_bases, + maps, filter, attrs, IPA_ASSIGNED_ID_VIEW, + SDAP_DEREF_FLG_SILENT, 0); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_view_name_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void ipa_subdomains_view_name_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_view_name_state *state; + struct tevent_req *req; + size_t reply_count; + struct sdap_deref_attrs **reply; + const char *view_name; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_view_name_state); + + ret = sdap_deref_bases_return_first_recv(subreq, state, + &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + /* Depending on the version 389ds return a different error code if the + * search for the view name failed because our dereference attribute + * ipaAssignedIDView is not known. Newer version return + * LDAP_UNAVAILABLE_CRITICAL_EXTENSION(12) which is translated to + * EOPNOTSUPP and older versions return LDAP_PROTOCOL_ERROR(2) which + * is returned as EIO. In both cases we have to assume that the server + * is not view aware and keep the view name unset. */ + if (ret == EOPNOTSUPP || ret == EIO) { + DEBUG(SSSDBG_TRACE_FUNC, "Unable to get view name, looks " \ + "like server does not support views.\n"); + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_OP_FAILURE, "Unable to get view name [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (reply_count == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "No view found, using default.\n"); + view_name = SYSDB_DEFAULT_VIEW_NAME; + } else if (reply_count == 1) { + ret = sysdb_attrs_get_string(reply[0]->attrs, SYSDB_VIEW_NAME, + &view_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto done; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "More than one object returned.\n"); + ret = EINVAL; + goto done; + } + + ret = ipa_apply_view(state->sd_ctx->be_ctx->domain, + state->sd_ctx->ipa_id_ctx, view_name, + state->sd_ctx->view_read_at_init, + state->sd_ctx->be_ctx->cdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set view [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + state->sd_ctx->view_read_at_init = true; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_subdomains_view_name_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_subdomains_view_domain_resolution_order_state { + struct sss_domain_info *domain; + const char *view_name; +}; + +static void +ipa_subdomains_view_domain_resolution_order_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_view_domain_resolution_order_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_subdomains_view_domain_resolution_order_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + const char *attrs[] = { IPA_DOMAIN_RESOLUTION_ORDER, NULL }; + char *ldap_basedn; + char *base; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_view_domain_resolution_order_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->domain = sd_ctx->be_ctx->domain; + state->view_name = sd_ctx->ipa_id_ctx->view_name; + + ret = domain_to_basedn(state, sd_ctx->be_ctx->domain->name, &ldap_basedn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "domain_to_basedn failed.\n"); + goto immediately; + } + + base = talloc_asprintf(state, "cn=%s,cn=views,cn=accounts,%s", + sd_ctx->ipa_id_ctx->view_name, ldap_basedn); + if (base == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto immediately; + } + + subreq = sdap_get_generic_send( + state, ev, sd_ctx->sdap_id_ctx->opts, sh, + base, LDAP_SCOPE_BASE, NULL, attrs, NULL, 0, + dp_opt_get_int(sd_ctx->sdap_id_ctx->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_view_domain_resolution_order_done, + req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void +ipa_subdomains_view_domain_resolution_order_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_view_domain_resolution_order_state *state; + struct tevent_req *req; + size_t reply_count; + struct sysdb_attrs **reply; + const char *domain_resolution_order; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, + struct ipa_subdomains_view_domain_resolution_order_state); + + ret = sdap_get_generic_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to get view name [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (reply_count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "More than one object returned.\n"); + ret = EINVAL; + goto done; + } else if (reply_count == 0) { + domain_resolution_order = NULL; + } else { + /* reply_count == 1 */ + ret = sysdb_attrs_get_string(reply[0], IPA_DOMAIN_RESOLUTION_ORDER, + &domain_resolution_order); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get the view domains' resolution order " + "configuration value for view [%s] [%d]: %s\n", + state->view_name, ret, sss_strerror(ret)); + goto done; + } else if (ret == ENOENT) { + domain_resolution_order = NULL; + } + } + + ret = sysdb_update_view_domain_resolution_order(state->domain->sysdb, + domain_resolution_order); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_update_view_domain_resolution_order() [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t +ipa_subdomains_view_domain_resolution_order_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_domain_resolution_order_state { + struct sss_domain_info *domain; +}; + +static void ipa_domain_resolution_order_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_domain_resolution_order_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_domain_resolution_order_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + const char *attrs[] = {IPA_DOMAIN_RESOLUTION_ORDER, NULL}; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_domain_resolution_order_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->domain = sd_ctx->be_ctx->domain; + + subreq = ipa_get_config_send(state, ev, sh, sd_ctx->sdap_id_ctx->opts, + state->domain->name, attrs, NULL, NULL); + if (subreq == NULL) { + ret = ENOMEM; + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } else { + tevent_req_set_callback(subreq, ipa_domain_resolution_order_done, req); + } + + return req; +} + +static void ipa_domain_resolution_order_done(struct tevent_req *subreq) +{ + struct ipa_domain_resolution_order_state *state; + struct tevent_req *req; + struct sysdb_attrs *config = NULL; + const char *domain_resolution_order = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_domain_resolution_order_state); + + ret = ipa_get_config_recv(subreq, state, &config); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Failed to get the domains' resolution order configuration " + "from the server [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (config != NULL) { + ret = sysdb_attrs_get_string(config, IPA_DOMAIN_RESOLUTION_ORDER, + &domain_resolution_order); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Failed to get the domains' resolution order configuration " + "value [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } else if (ret == ENOENT) { + domain_resolution_order = NULL; + } + } + + ret = sysdb_domain_update_domain_resolution_order( + state->domain->sysdb, state->domain->name, + domain_resolution_order); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_domain_update_resolution_order() [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t ipa_domain_resolution_order_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct kdcinfo_from_server_list_state { + struct resolv_hostport *hostport_list; + enum host_database db[2]; + + struct resolv_hostport_addr **rhp_addrs; + size_t rhp_len; +}; + +static void kdcinfo_from_server_list_done(struct tevent_req *subreq); + +static struct tevent_req * +kdcinfo_from_server_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_resolv_ctx *be_res, + const char *servers) +{ + struct kdcinfo_from_server_list_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + int server_list_len; + char **server_list; + + req = tevent_req_create(mem_ctx, &state, + struct kdcinfo_from_server_list_state); + if (req == NULL) { + return NULL; + } + state->db[0] = DB_DNS; + state->db[1] = DB_SENTINEL; + + if (servers == NULL) { + ret = EOK; + goto immediately; + } + + ret = split_on_separator(state, servers, ',', true, true, + &server_list, + &server_list_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to parse server list!\n"); + goto immediately; + } + + state->hostport_list = talloc_array(state, + struct resolv_hostport, + server_list_len); + if (state->hostport_list == NULL) { + ret = ENOMEM; + goto immediately; + } + + for (int i = 0; i < server_list_len; i++) { + state->hostport_list[i].host = server_list[i]; + state->hostport_list[i].port = 0; + } + + subreq = resolv_hostport_list_send(state, + ev, + be_res->resolv, + state->hostport_list, + server_list_len, + 0, + be_res->family_order, + state->db); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, kdcinfo_from_server_list_done, req); + return req; + +immediately: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, ev); + return req; +} + +static void kdcinfo_from_server_list_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct kdcinfo_from_server_list_state *state = tevent_req_data(req, + struct kdcinfo_from_server_list_state); + + ret = resolv_hostport_list_recv(subreq, + state, + &state->rhp_len, + &state->rhp_addrs); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to resolve address list [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t kdcinfo_from_server_list_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct resolv_hostport_addr ***_rhp_addrs, + size_t *_rhp_len) +{ + struct kdcinfo_from_server_list_state *state = tevent_req_data(req, + struct kdcinfo_from_server_list_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_rhp_addrs != NULL) { + *_rhp_addrs = talloc_steal(mem_ctx, state->rhp_addrs); + } + + if (_rhp_len != NULL) { + *_rhp_len = state->rhp_len; + } + + return EOK; +} + +struct kdcinfo_from_site_state { + struct tevent_context *ev; + struct be_resolv_ctx *be_res; + + const char *discovery_domains[2]; + struct resolv_hostport *hostport_list; + enum host_database db[2]; + + struct resolv_hostport_addr **rhp_addrs; + size_t rhp_len; +}; + +static void kdcinfo_from_site_srv_done(struct tevent_req *subreq); +static void kdcinfo_from_site_server_list_done(struct tevent_req *subreq); + +static struct tevent_req * +kdcinfo_from_site_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_resolv_ctx *be_res, + const char *site, + const char *domain) +{ + struct kdcinfo_from_site_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct kdcinfo_from_site_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->be_res = be_res; + state->db[0] = DB_DNS; + state->db[1] = DB_SENTINEL; + + state->discovery_domains[0] = ad_site_dns_discovery_domain(state, + site, + domain); + if (state->discovery_domains[0] == NULL) { + ret = ENOMEM; + goto immediately; + } + state->discovery_domains[1] = NULL; + + subreq = fo_discover_srv_send(state, + state->ev, + state->be_res->resolv, + "kerberos", "tcp", + state->discovery_domains); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, kdcinfo_from_site_srv_done, req); + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kdcinfo_from_site_srv_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct kdcinfo_from_site_state *state = tevent_req_data(req, + struct kdcinfo_from_site_state); + struct fo_server_info *servers; + size_t num_servers; + + ret = fo_discover_srv_recv(state, subreq, + NULL, NULL, /* not interested in TTL etc */ + &servers, &num_servers); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not resolve the site [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->hostport_list = talloc_array(state, + struct resolv_hostport, + num_servers); + if (state->hostport_list == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + for (size_t i = 0; i < num_servers; i++) { + state->hostport_list[i].host = servers[i].host; + state->hostport_list[i].port = servers[i].port; + } + + subreq = resolv_hostport_list_send(state, + state->ev, + state->be_res->resolv, + state->hostport_list, + num_servers, + MAX_SERVERS_FROM_SRV, + state->be_res->family_order, + state->db); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kdcinfo_from_site_server_list_done, req); +} + +static void kdcinfo_from_site_server_list_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct kdcinfo_from_site_state *state = tevent_req_data(req, + struct kdcinfo_from_site_state); + + ret = resolv_hostport_list_recv(subreq, + state, + &state->rhp_len, + &state->rhp_addrs); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to resolve address list [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + + +static errno_t kdcinfo_from_site_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct resolv_hostport_addr ***_rhp_addrs, + size_t *_rhp_len) +{ + struct kdcinfo_from_site_state *state = tevent_req_data(req, + struct kdcinfo_from_site_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_rhp_addrs != NULL) { + *_rhp_addrs = talloc_steal(mem_ctx, state->rhp_addrs); + } + + if (_rhp_len != NULL) { + *_rhp_len = state->rhp_len; + } + + return EOK; +} + +/* Anything per-domain in this request goes here so that we + * can just free the whole struct without mixing data from + * different domains or the overhead of another request + */ +struct ipa_sd_per_dom_kdcinfo_ctx { + struct sss_domain_info *dom; + + const char *servers; + const char *site; + + const char *discovery_domains[2]; + struct krb5_service *krb5_service; +}; + +struct ipa_subdomains_write_kdcinfo_state { + struct tevent_context *ev; + struct ipa_subdomains_ctx *ipa_sd_ctx; + struct be_ctx *be_ctx; + + bool use_kdcinfo; + struct ipa_sd_per_dom_kdcinfo_ctx *pdctx; +}; + +static errno_t ipa_subdomains_write_kdcinfo_domain_step(struct sss_domain_info *start_dom, + struct tevent_req *req); +static void ipa_subdomains_write_kdcinfo_domain_done(struct tevent_req *subreq); +static errno_t ipa_subdomains_write_kdcinfo_write_step(struct sss_domain_info *dom, + struct krb5_service *krb5_service, + struct resolv_hostport_addr **rhp_addrs, + size_t rhp_len); + +static struct tevent_req * +ipa_subdomains_write_kdcinfo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *ipa_sd_ctx, + struct be_ctx *be_ctx) +{ + struct ipa_subdomains_write_kdcinfo_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_write_kdcinfo_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->ipa_sd_ctx = ipa_sd_ctx; + state->be_ctx = be_ctx; + + if (ipa_sd_ctx->ipa_id_ctx->server_mode != NULL) { + /* This request is valid for clients only */ + ret = EOK; + goto immediately; + } + + state->use_kdcinfo = dp_opt_get_bool(ipa_sd_ctx->ipa_id_ctx->ipa_options->auth, + KRB5_USE_KDCINFO); + if (state->use_kdcinfo == false) { + DEBUG(SSSDBG_CONF_SETTINGS, "kdcinfo creation disabled\n"); + ret = EOK; + goto immediately; + } + + if (be_ctx->domain->subdomains == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No subdomains, done\n"); + ret = EOK; + goto immediately; + } + + ret = ipa_subdomains_write_kdcinfo_domain_step(be_ctx->domain->subdomains, + req); + if (ret != EAGAIN) { + goto immediately; + } + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ipa_subdomains_write_kdcinfo_domain_step(struct sss_domain_info *start_dom, + struct tevent_req *req) +{ + struct ipa_subdomains_write_kdcinfo_state *state = \ + tevent_req_data(req, + struct ipa_subdomains_write_kdcinfo_state); + struct dp_option *ipa_ad_subdom_opts; + struct tevent_req *subreq = NULL; + char *subdom_conf_path; + errno_t ret; + const char *servers; + const char *site; + + for (struct sss_domain_info *dom = start_dom; + dom != NULL; + dom = get_next_domain(dom, 0)) { + + talloc_zfree(state->pdctx); + + subdom_conf_path = subdomain_create_conf_path(state, dom); + if (subdom_conf_path == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "subdom_conf_path failed for %s\n", dom->name); + /* Not fatal */ + continue; + } + + ret = dp_get_options(state, state->be_ctx->cdb, + subdom_conf_path, + ipa_cli_ad_subdom_opts, + IPA_OPTS_CLI_AD_SUBDOM, + &ipa_ad_subdom_opts); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot get options for %s: [%d]: %s\n", + dom->name, ret, sss_strerror(ret)); + /* Not fatal */ + continue; + } + + servers = dp_opt_get_string(ipa_ad_subdom_opts, IPA_CLI_AD_SERVER); + site = dp_opt_get_string(ipa_ad_subdom_opts, IPA_CLI_AD_SITE); + + if (servers == NULL && site == NULL) { + /* If neither is set, just go to the next domain */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "No site or server defined for %s, skipping\n", + dom->name); + continue; + } + + /* We will resolve this domain, create a per-domain context */ + state->pdctx = talloc_zero(state, struct ipa_sd_per_dom_kdcinfo_ctx); + if (state->pdctx == NULL) { + return ENOMEM; + } + state->pdctx->dom = dom; + state->pdctx->servers = servers; + state->pdctx->site = site; + state->pdctx->krb5_service = ipa_subdom_get_k5_svc(state->ipa_sd_ctx, + dom, + state->use_kdcinfo); + if (state->pdctx->krb5_service == NULL) { + continue; + } + + if (state->pdctx->servers != NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Resolving servers [%s] for domain %s\n", + state->pdctx->servers, dom->name); + + subreq = kdcinfo_from_server_list_send(state, + state->ev, + state->be_ctx->be_res, + state->pdctx->servers); + } else if (state->pdctx->site != NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Resolving site %s for domain %s\n", + state->pdctx->site, dom->name); + + subreq = kdcinfo_from_site_send(state, + state->ev, + state->be_ctx->be_res, + state->pdctx->site, + state->pdctx->dom->name); + } else { + /* We should never get here */ + return EINVAL; + } + + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_subdomains_write_kdcinfo_domain_done, req); + return EAGAIN; + } + + return EOK; +} + +static void ipa_subdomains_write_kdcinfo_domain_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_subdomains_write_kdcinfo_state *state = \ + tevent_req_data(req, + struct ipa_subdomains_write_kdcinfo_state); + struct sss_domain_info *next_domain; + struct resolv_hostport_addr **rhp_addrs = NULL; + size_t rhp_len = 0; + + if (state->pdctx->servers != NULL) { + ret = kdcinfo_from_server_list_recv(state->pdctx, subreq, + &rhp_addrs, &rhp_len); + } else if (state->pdctx->site != NULL) { + ret = kdcinfo_from_site_recv(state->pdctx, subreq, + &rhp_addrs, &rhp_len); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Neither site nor servers set\n"); + ret = EINVAL; + } + + if (ret == EOK) { + ret = ipa_subdomains_write_kdcinfo_write_step(state->pdctx->dom, + state->pdctx->krb5_service, + rhp_addrs, rhp_len); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not write kdcinfo file for %s\n", state->pdctx->dom->name); + /* Not fatal, loop to the next domain below */ + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not get address list for %s\n", state->pdctx->dom->name); + /* Not fatal, loop to the next domain below */ + } + + next_domain = get_next_domain(state->pdctx->dom, 0); + ret = ipa_subdomains_write_kdcinfo_domain_step(next_domain, req); + if (ret == EOK) { + tevent_req_done(req); + return; + } else if (ret != EAGAIN) { + /* the loop in ipa_subdomains_write_kdcinfo_domain_step already + * tries to be quite permissive, so any error is fatal + */ + tevent_req_error(req, ret); + return; + } + + /* Continue to the next domain */ +} + +static errno_t ipa_subdomains_write_kdcinfo_write_step(struct sss_domain_info *dom, + struct krb5_service *krb5_service, + struct resolv_hostport_addr **rhp_addrs, + size_t rhp_len) +{ + errno_t ret; + char *address = NULL; + char *safe_address = NULL; + const char **safe_addr_list; + int addr_index = 0; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + safe_addr_list = talloc_zero_array(tmp_ctx, const char *, rhp_len+1); + if (safe_addr_list == NULL) { + ret = ENOMEM; + goto done; + } + + for (size_t i = 0; i < rhp_len; i++) { + address = resolv_get_string_address(tmp_ctx, rhp_addrs[i]->reply); + if (address == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "resolv_get_string_address failed.\n"); + continue; + } + + if (rhp_addrs[i]->origin.port != 0) { + address = talloc_asprintf_append(address, + ":%d", + rhp_addrs[i]->origin.port); + } + + safe_address = sss_escape_ip_address(tmp_ctx, + rhp_addrs[i]->reply->family, + address); + talloc_zfree(address); + if (safe_address == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_escape_ip_address failed.\n"); + continue; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Will write [%s] for %s\n", + safe_address, dom->name); + + safe_addr_list[addr_index] = talloc_steal(safe_addr_list, + safe_address); + addr_index++; + } + + ret = write_krb5info_file(krb5_service, + safe_addr_list, + SSS_KRB5KDC_FO_SRV); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "write to %s/kdcinfo.%s failed, authentication might fail.\n", + PUBCONF_PATH, krb5_service->realm); + goto done; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t ipa_subdomains_write_kdcinfo_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct ipa_subdomains_refresh_state { + struct tevent_context *ev; + struct ipa_subdomains_ctx *sd_ctx; + struct sdap_id_op *sdap_op; +}; + +static errno_t ipa_subdomains_refresh_retry(struct tevent_req *req); +static void ipa_subdomains_refresh_connect_done(struct tevent_req *subreq); +static void ipa_subdomains_refresh_ranges_done(struct tevent_req *subreq); +static void ipa_subdomains_refresh_certmap_done(struct tevent_req *subreq); +#ifdef BUILD_PASSKEY +static void ipa_subdomains_refresh_passkey_done(struct tevent_req *subreq); +#endif /* BUILD_PASSKEY */ +static void ipa_subdomains_refresh_master_done(struct tevent_req *subreq); +static void ipa_subdomains_refresh_slave_done(struct tevent_req *subreq); +static void ipa_subdomains_refresh_view_name_done(struct tevent_req *subreq); +static void ipa_subdomains_refresh_view_domain_resolution_order_done( + struct tevent_req *subreq); +static void ipa_domain_refresh_resolution_order_done(struct tevent_req *subreq); +static void ipa_domain_refresh_kdcinfo_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->sd_ctx = sd_ctx; + + state->sdap_op = sdap_id_op_create(state, + sd_ctx->sdap_id_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto immediately; + } + + ret = ipa_subdomains_refresh_retry(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t ipa_subdomains_refresh_retry(struct tevent_req *req) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, ipa_subdomains_refresh_connect_done, req); + + return EAGAIN; +} + +static void ipa_subdomains_refresh_connect_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to connect to LDAP " + "[%d]: %s\n", ret, sss_strerror(ret)); + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, "No IPA server is available, " + "cannot get the subdomain list while offline\n"); + ret = ERR_OFFLINE; + } + tevent_req_error(req, ret); + return; + } + + subreq = ipa_subdomains_ranges_send(state, state->ev, state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_subdomains_refresh_ranges_done, req); + return; +} + +static void ipa_subdomains_refresh_ranges_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_subdomains_ranges_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get IPA ranges " + "[%d]: %s\n", ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + } + + subreq = ipa_subdomains_certmap_send(state, state->ev, state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_subdomains_refresh_certmap_done, req); + return; +} + +static void ipa_subdomains_refresh_certmap_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_subdomains_certmap_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to read certificate mapping rules " + "[%d]: %s\n", ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + } + +#ifdef BUILD_PASSKEY + subreq = ipa_subdomains_passkey_send(state, state->ev, state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_subdomains_refresh_passkey_done, req); + return; +} + +static void ipa_subdomains_refresh_passkey_done(struct tevent_req *subreq) +{ + + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_subdomains_passkey_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to get passkey configuration " + "[%d]: %s\n", ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + DEBUG(SSSDBG_IMPORTANT_INFO, "Passkey feature is not configured " + "on IPA server\n"); + } +#endif /* BUILD_PASSKEY */ + + subreq = ipa_subdomains_master_send(state, state->ev, state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_subdomains_refresh_master_done, req); + return; +} + +static void ipa_subdomains_refresh_master_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_subdomains_master_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get master domain " + "[%d]: %s\n", ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + } + + subreq = ipa_subdomains_slave_send(state, state->ev, state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_subdomains_refresh_slave_done, req); + return; +} + +static void ipa_subdomains_refresh_slave_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_subdomains_slave_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get subdomains " + "[%d]: %s\n", ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + } + + subreq = ipa_subdomains_view_name_send(state, state->ev, state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_subdomains_refresh_view_name_done, + req); + return; +} + +static void ipa_subdomains_refresh_view_name_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_subdomains_view_name_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to get view name [%d]: %s\n", + ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + } + + subreq = ipa_subdomains_view_domain_resolution_order_send( + state, + state->ev, + state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, + ipa_subdomains_refresh_view_domain_resolution_order_done, + req); +} + +static void +ipa_subdomains_refresh_view_domain_resolution_order_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_subdomains_view_domain_resolution_order_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to get view domain_resolution order [%d]: %s\n", + ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + } + + subreq = ipa_domain_resolution_order_send(state, state->ev, state->sd_ctx, + sdap_id_op_handle(state->sdap_op)); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, + ipa_domain_refresh_resolution_order_done, + req); +} + +static void +ipa_domain_refresh_resolution_order_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_refresh_state *state; + struct tevent_req *req; + int dp_error; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_refresh_state); + + ret = ipa_domain_resolution_order_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to get the domains order resolution [%d]: %s\n", + ret, sss_strerror(ret)); + /* Not good, but let's try to continue with other server side options */ + } + + ret = sdap_id_op_done(state->sdap_op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = ipa_subdomains_refresh_retry(req); + } else if (dp_error == DP_ERR_OFFLINE) { + ret = ERR_OFFLINE; + } + + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Unable to refresh subdomains [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + subreq = ipa_subdomains_write_kdcinfo_send(state, + state->ev, + state->sd_ctx, + state->sd_ctx->be_ctx); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ipa_domain_refresh_kdcinfo_done, req); +} + +static void +ipa_domain_refresh_kdcinfo_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = ipa_subdomains_write_kdcinfo_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to write the kdc info files, authentication might " + "fail or time out [%d]: %s\n", + ret, sss_strerror(ret)); + /* Not fatal, let's hope DNS is set correctly */ + } + + tevent_req_done(req); +} + +static errno_t ipa_subdomains_refresh_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_subdomains_handler_state { + struct dp_reply_std reply; +}; + +static void ipa_subdomains_handler_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_subdomains_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_subdomains_ctx *sd_ctx, + struct dp_subdomains_data *data, + struct dp_req_params *params) +{ + struct ipa_subdomains_handler_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + + if (sd_ctx->last_refreshed > time(NULL) - IPA_SUBDOMAIN_REFRESH_LIMIT) { + DEBUG(SSSDBG_TRACE_FUNC, "Subdomains were recently refreshed, " + "nothing to do\n"); + ret = EOK; + goto immediately; + } + + subreq = ipa_subdomains_refresh_send(state, params->ev, sd_ctx); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ipa_subdomains_handler_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_handler_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_handler_state); + + ret = ipa_subdomains_refresh_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to refresh subdomains [%d]: %s\n", + ret, sss_strerror(ret)); + } + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + tevent_req_done(req); +} + +static errno_t ipa_subdomains_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct ipa_subdomains_handler_state *state; + + state = tevent_req_data(req, struct ipa_subdomains_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} + +static struct tevent_req * +ipa_subdomains_ptask_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct ipa_subdomains_ctx *sd_ctx; + sd_ctx = talloc_get_type(pvt, struct ipa_subdomains_ctx); + + return ipa_subdomains_refresh_send(mem_ctx, ev, sd_ctx); +} + +static errno_t +ipa_subdomains_ptask_recv(struct tevent_req *req) +{ + return ipa_subdomains_refresh_recv(req); +} + +errno_t ipa_subdomains_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *ipa_id_ctx, + struct dp_method *dp_methods) +{ + struct ipa_subdomains_ctx *sd_ctx; + struct ipa_options *ipa_options; + time_t period; + time_t offset; + errno_t ret; + /* Delay the first ptask that refreshes the trusted domains so that a race between + * the first responder-induced request and the ptask doesn't cause issues, see + * also upstream ticket #3601 + */ + const time_t ptask_first_delay = 600; + + ipa_options = ipa_id_ctx->ipa_options; + + sd_ctx = talloc_zero(mem_ctx, struct ipa_subdomains_ctx); + if (sd_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + sd_ctx->be_ctx = be_ctx; + sd_ctx->ipa_id_ctx = ipa_id_ctx; + sd_ctx->sdap_id_ctx = ipa_id_ctx->sdap_id_ctx; + sd_ctx->search_bases = ipa_options->subdomains_search_bases; + sd_ctx->master_search_bases = ipa_options->master_domain_search_bases; + sd_ctx->ranges_search_bases = ipa_options->ranges_search_bases; + sd_ctx->host_search_bases = ipa_options->id->sdom->host_search_bases; + + dp_set_method(dp_methods, DPM_DOMAINS_HANDLER, + ipa_subdomains_handler_send, ipa_subdomains_handler_recv, sd_ctx, + struct ipa_subdomains_ctx, struct dp_subdomains_data, struct dp_reply_std); + + period = be_ctx->domain->subdomain_refresh_interval; + offset = be_ctx->domain->subdomain_refresh_interval_offset; + ret = be_ptask_create(sd_ctx, be_ctx, period, ptask_first_delay, 0, offset, + period, 0, + ipa_subdomains_ptask_send, ipa_subdomains_ptask_recv, sd_ctx, + "Subdomains Refresh", + BE_PTASK_OFFLINE_DISABLE | + BE_PTASK_SCHEDULE_FROM_LAST, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup ptask " + "[%d]: %s\n", ret, sss_strerror(ret)); + /* Ignore, responders will trigger refresh from time to time. */ + } + + ret = ipa_subdom_reinit(sd_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Could not reinitialize subdomains. " + "Users from trusted domains might not be resolved correctly\n"); + /* Ignore this error and try to discover the subdomains later */ + } + + ret = ipa_ad_subdom_init(be_ctx, ipa_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "ipa_ad_subdom_init() failed.\n"); + return ret; + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_subdomains.h b/src/providers/ipa/ipa_subdomains.h new file mode 100644 index 0000000..1411d0c --- /dev/null +++ b/src/providers/ipa/ipa_subdomains.h @@ -0,0 +1,177 @@ +/* + SSSD + + IPA Subdomains Module + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2011 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef _IPA_SUBDOMAINS_H_ +#define _IPA_SUBDOMAINS_H_ + +#include "providers/backend.h" +#include "providers/ipa/ipa_common.h" +#include "config.h" + +#ifndef IPA_TRUST_KEYTAB_DIR +#define IPA_TRUST_KEYTAB_DIR SSS_STATEDIR"/keytabs" +#endif /* IPA_TRUST_KEYTAB_DIR */ + +/* ==Sid2Name Extended Operation============================================= */ +#define EXOP_SID2NAME_OID "2.16.840.1.113730.3.8.10.4" +#define EXOP_SID2NAME_V1_OID "2.16.840.1.113730.3.8.10.4.1" +#define EXOP_SID2NAME_V2_OID "2.16.840.1.113730.3.8.10.4.2" + +enum extdom_protocol { + EXTDOM_INVALID_VERSION = -1, + EXTDOM_V0, + EXTDOM_V1, + EXTDOM_V2 +}; + +struct ipa_subdomains_ctx { + struct be_ctx *be_ctx; + struct ipa_id_ctx *ipa_id_ctx; + struct sdap_id_ctx *sdap_id_ctx; + struct sdap_search_base **search_bases; + struct sdap_search_base **master_search_bases; + struct sdap_search_base **ranges_search_bases; + struct sdap_search_base **host_search_bases; + + time_t last_refreshed; + bool view_read_at_init; + /* List of krb5_service structures for each subdomain + * in order to write the kdcinfo files. For use on + * the client only + */ + struct ipa_sd_k5_svc_list *k5svc_list; +}; + +errno_t ipa_subdomains_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *ipa_id_ctx, + struct dp_method *dp_methods); + +/* The following are used in server mode only */ +struct ipa_ad_server_ctx { + struct sss_domain_info *dom; + struct ad_id_ctx *ad_id_ctx; + + struct ipa_ad_server_ctx *next, *prev; +}; + +/* Can be used to set up trusted subdomain, for example fetch + * keytab in server mode + */ +struct tevent_req * +ipa_server_trusted_dom_setup_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom); +errno_t ipa_server_trusted_dom_setup_recv(struct tevent_req *req); + +/* To be used by ipa_subdomains.c only */ +struct tevent_req * +ipa_server_create_trusts_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *parent); + +errno_t ipa_server_create_trusts_recv(struct tevent_req *req); + +void ipa_ad_subdom_remove(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom); + +int ipa_ad_subdom_init(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx); + +errno_t ipa_server_get_trust_direction(struct sysdb_attrs *sd, + struct ldb_context *ldb_ctx, + uint32_t *_direction); + +const char *ipa_trust_dir2str(uint32_t direction); + +/* Utilities */ +#define IPA_TRUST_DIRECTION "ipaNTTrustDirection" + +struct ldb_dn *ipa_subdom_ldb_dn(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb_ctx, + struct sysdb_attrs *attrs); + +bool ipa_subdom_is_member_dom(struct ldb_dn *dn); + +/* struct for external group memberships, defined in + * ipa_subdomains_ext_groups.c */ +struct ipa_ext_groups; + +struct ipa_server_mode_ctx { + const char *realm; + const char *hostname; + + struct ipa_ad_server_ctx *trusts; + struct ipa_ext_groups *ext_groups; + + uid_t kt_owner_uid; + uid_t kt_owner_gid; +}; + +int ipa_ad_subdom_init(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx); + +enum req_input_type { + REQ_INP_NAME, + REQ_INP_ID, + REQ_INP_SECID, + REQ_INP_CERT +}; + +struct req_input { + enum req_input_type type; + union { + const char *name; + uint32_t id; + const char *secid; + const char *cert; + } inp; +}; + +struct tevent_req *ipa_get_ad_memberships_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dp_id_data *ar, + struct ipa_server_mode_ctx *server_mode, + struct sss_domain_info *user_dom, + struct sdap_id_ctx *sdap_id_ctx, + const char *domain); + +errno_t ipa_get_ad_memberships_recv(struct tevent_req *req, int *dp_error_out); + +struct tevent_req *ipa_ext_group_member_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *ext_member, + void *pvt); +errno_t ipa_ext_group_member_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + enum sysdb_member_type *_member_type, + struct sss_domain_info **_dom, + struct sysdb_attrs **_member); + +#endif /* _IPA_SUBDOMAINS_H_ */ diff --git a/src/providers/ipa/ipa_subdomains_ext_groups.c b/src/providers/ipa/ipa_subdomains_ext_groups.c new file mode 100644 index 0000000..f4f8474 --- /dev/null +++ b/src/providers/ipa/ipa_subdomains_ext_groups.c @@ -0,0 +1,1213 @@ +/* + SSSD + + IPA Identity Backend Module for sub-domains - evaluate external group + memberships + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2013 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_ops.h" +#include "providers/ipa/ipa_id.h" +#include "providers/ad/ad_id.h" +#include "providers/ipa/ipa_subdomains.h" + +#define IPA_EXT_GROUPS_FILTER "objectClass=ipaexternalgroup" + +struct ipa_ext_groups { + time_t next_update; + hash_table_t *ext_groups; +}; + +static errno_t process_ext_groups(TALLOC_CTX *mem_ctx, size_t reply_count, + struct sysdb_attrs **reply, + hash_table_t **_ext_group_hash) +{ + int ret; + hash_table_t *ext_group_hash = NULL; + hash_key_t key; + hash_value_t value; + hash_table_t *m_hash = NULL; + hash_key_t m_key; + hash_value_t m_value; + size_t g; + size_t s; + size_t m; + TALLOC_CTX *tmp_ctx = NULL; + const char **ext_sids; + const char **mof; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sss_hash_create(mem_ctx, reply_count, &ext_group_hash); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "sss_hash_create failed.\n"); + goto done; + } + + key.type = HASH_KEY_STRING; + m_key.type = HASH_KEY_STRING; + m_value.type = HASH_VALUE_PTR; + m_value.ptr = NULL; + + for (g = 0; g < reply_count; g++) { + ret = sysdb_attrs_get_string_array(reply[g], "ipaExternalMember", + tmp_ctx, &ext_sids); + if (ret == ENOENT) { + /* no external members, try next external group. */ + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_string_array failed.\n"); + goto done; + } + + ret = sysdb_attrs_get_string_array(reply[g], "memberOf", + tmp_ctx, &mof); + if (ret == ENOENT) { + /* no IPA groups, try next external group. */ + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_get_string_array failed.\n"); + goto done; + } + + for (s = 0; ext_sids[s] != NULL; s++) { + /* hash_lookup does not modify key.str. */ + key.str = discard_const(ext_sids[s]); + ret = hash_lookup(ext_group_hash, &key, &value); + if (ret == HASH_SUCCESS) { + if (value.type != HASH_VALUE_PTR) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected value type.\n"); + ret = EINVAL; + goto done; + } + + for (m = 0; mof[m] != NULL; m++) { + /* hash_enter does not modify m_key.str. */ + m_key.str = discard_const(mof[m]); + DEBUG(SSSDBG_TRACE_ALL, "Adding group [%s] to SID [%s].\n", + m_key.str, key.str); + ret = hash_enter(value.ptr, &m_key, &m_value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "hash_enter failed.\n"); + goto done; + } + } + } else if (ret == HASH_ERROR_KEY_NOT_FOUND) { + ret = sss_hash_create(ext_group_hash, 0, &m_hash); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "sss_hash_create failed.\n"); + goto done; + } + + value.type = HASH_VALUE_PTR; + value.ptr = m_hash; + + DEBUG(SSSDBG_TRACE_ALL, + "Adding SID [%s] to external group hash.\n", key.str); + ret = hash_enter(ext_group_hash, &key, &value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "hash_enter failed.\n"); + goto done; + } + + for (m = 0; mof[m] != NULL; m++) { + /* hash_enter does not modify m_key.str. */ + m_key.str = discard_const(mof[m]); + DEBUG(SSSDBG_TRACE_ALL, "Adding group [%s] to SID [%s].\n", + m_key.str, key.str); + ret = hash_enter(m_hash, &m_key, &m_value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "hash_enter failed.\n"); + goto done; + } + } + } else { + DEBUG(SSSDBG_OP_FAILURE, "hash_lookup failed.\n"); + goto done; + } + } + } + + ret = EOK; +done: + if (ret != EOK) { + talloc_free(ext_group_hash); + } else { + *_ext_group_hash = ext_group_hash; + } + + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t find_ipa_ext_memberships(TALLOC_CTX *mem_ctx, + const char *user_name, + struct sss_domain_info *user_dom, + hash_table_t *ext_group_hash, + struct ldb_dn **_user_dn, + char ***_groups) +{ + int ret; + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_result *result; + char **groups = NULL; + size_t c; + const char *sid; + hash_key_t key; + hash_value_t value; + hash_entry_t *entry; + struct hash_iter_context_t *iter; + hash_table_t *group_hash; + size_t g_count; + struct ldb_dn *user_dn = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_initgroups(tmp_ctx, user_dom, user_name, &result); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_initgroups failed.\n"); + goto done; + } + + if (result->count == 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "User [%s] not found in cache.\n", + user_name); + ret = EOK; + goto done; + } + + ret = sss_hash_create(tmp_ctx, 0, &group_hash); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "sss_hash_create failed.\n"); + goto done; + } + + key.type = HASH_KEY_STRING; + + /* The IPA external domains can have references to group and user SIDs. + * This means that we not only want to look up the group SIDs but the SID + * of the user (first element of result) as well. */ + for (c = 0; c < result->count; c++) { + sid = ldb_msg_find_attr_as_string(result->msgs[c], SYSDB_SID_STR, + NULL); + if (sid == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Group [%s] does not have a SID.\n", + ldb_dn_get_linearized(result->msgs[c]->dn)); + continue; + } + + key.str = discard_const(sid); + ret = hash_lookup(ext_group_hash, &key, &value); + if (ret == HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_TRACE_ALL, "SID [%s] not found in ext group hash.\n", + sid); + } else if (ret == HASH_SUCCESS) { + iter = new_hash_iter_context(value.ptr); + if (iter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "new_hash_iter_context failed.\n"); + ret = EINVAL; + goto done; + } + + while ((entry = iter->next(iter)) != NULL) { + ret = hash_enter(group_hash, &entry->key, &entry->value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add group [%s].\n", + entry->key.str); + } + } + + talloc_free(iter); + } else { + DEBUG(SSSDBG_OP_FAILURE, "hash_lookup failed for SID [%s].\n", + sid); + } + } + + g_count = hash_count(group_hash); + if (g_count == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "No external groupmemberships found.\n"); + ret = EOK; + goto done; + } + + groups = talloc_zero_array(mem_ctx, char *, g_count + 1); + if (groups == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + ret = ENOMEM; + goto done; + } + + iter = new_hash_iter_context(group_hash); + if (iter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "new_hash_iter_context failed.\n"); + ret = EINVAL; + goto done; + } + + c = 0; + while ((entry = iter->next(iter)) != NULL) { + groups[c] = talloc_strdup(groups, entry->key.str); + if (groups[c] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + c++; + } + + user_dn = ldb_dn_copy(mem_ctx, result->msgs[0]->dn); + if (user_dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_copy failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = EOK; +done: + *_user_dn = user_dn; + *_groups = groups; + + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t add_ad_user_to_cached_groups(struct ldb_dn *user_dn, + struct sss_domain_info *user_dom, + struct sss_domain_info *group_dom, + char **groups, + bool *missing_groups) +{ + size_t c; + struct sysdb_attrs *user_attrs; + size_t msgs_count; + struct ldb_message **msgs; + TALLOC_CTX *tmp_ctx; + int ret; + + *missing_groups = false; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + for (c = 0; groups[c] != NULL; c++) { + if (groups[c][0] == '\0') { + continue; + } + + ret = sysdb_search_groups_by_orig_dn(tmp_ctx, group_dom, groups[c], + NULL, &msgs_count, &msgs); + if (ret != EOK) { + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "Group [%s] not in the cache.\n", + groups[c]); + *missing_groups = true; + continue; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_entry failed.\n"); + goto done; + } + } + +/* TODO? Do we have to remove members as well? I think not because the AD + * query before removes all memberships. */ + + ret = sysdb_mod_group_member(group_dom, user_dn, msgs[0]->dn, + LDB_FLAG_MOD_ADD); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_mod_group_member failed.\n"); + goto done; + } + + user_attrs = sysdb_new_attrs(tmp_ctx); + if (user_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(user_attrs, SYSDB_ORIG_MEMBEROF, + groups[c]); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_string failed.\n"); + goto done; + } + + ret = sysdb_set_entry_attr(user_dom->sysdb, user_dn, user_attrs, + LDB_FLAG_MOD_ADD); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_entry_attr failed.\n"); + goto done; + } + + /* mark group as already processed */ + groups[c][0] = '\0'; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + +static struct tevent_req *ipa_add_ad_memberships_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *sdap_id_ctx, + struct ldb_dn *user_dn, + struct sss_domain_info *user_dom, + char **groups, + struct sss_domain_info *group_dom); +static void ipa_add_ad_memberships_done(struct tevent_req *subreq); + +struct get_ad_membership_state { + struct tevent_context *ev; + struct ipa_server_mode_ctx *server_mode; + struct sdap_id_op *sdap_op; + struct sdap_id_ctx *sdap_id_ctx; + struct fo_server *srv; + char *user_name; + struct sss_domain_info *user_dom; + + int dp_error; + const char *domain; + size_t reply_count; + struct sysdb_attrs **reply; +}; + +static void ipa_get_ad_memberships_connect_done(struct tevent_req *subreq); +static void ipa_get_ext_groups_done(struct tevent_req *subreq); +static errno_t ipa_add_ext_groups_step(struct tevent_req *req); +static errno_t ipa_add_ad_memberships_recv(struct tevent_req *req, + int *dp_error_out); + +struct tevent_req *ipa_get_ad_memberships_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dp_id_data *ar, + struct ipa_server_mode_ctx *server_mode, + struct sss_domain_info *user_dom, + struct sdap_id_ctx *sdap_id_ctx, + const char *domain) +{ + int ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct get_ad_membership_state *state; + + req = tevent_req_create(mem_ctx, &state, struct get_ad_membership_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->user_dom = user_dom; + state->sdap_id_ctx = sdap_id_ctx; + state->srv = NULL; + state->domain = domain; + state->dp_error = -1; + + if (((ar->entry_type & BE_REQ_TYPE_MASK) != BE_REQ_INITGROUPS + && (ar->entry_type & BE_REQ_TYPE_MASK) != BE_REQ_USER) + || ar->filter_type != BE_FILTER_NAME) { + DEBUG(SSSDBG_OP_FAILURE, "Unsupported request type.\n"); + ret = EINVAL; + goto done; + } + + state->user_name = talloc_strdup(state, ar->filter_value); + if (state->user_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_Strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + state->sdap_op = sdap_id_op_create(state, + state->sdap_id_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + state->server_mode = server_mode; + if (server_mode->ext_groups == NULL) { + server_mode->ext_groups = talloc_zero(server_mode, + struct ipa_ext_groups); + if (server_mode->ext_groups == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + } + + if (server_mode->ext_groups->next_update > time(NULL)) { + DEBUG(SSSDBG_TRACE_FUNC, "External group information still valid.\n"); + ret = ipa_add_ext_groups_step(req); + if (ret == EOK) { + goto done; + } else if (ret == EAGAIN) { + return req; + } else { + DEBUG(SSSDBG_OP_FAILURE, "ipa_add_ext_groups_step failed.\n"); + goto done; + } + + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: %d(%s).\n", + ret, strerror(ret)); + goto done; + } + + tevent_req_set_callback(subreq, ipa_get_ad_memberships_connect_done, req); + + return req; + +done: + if (ret != EOK) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + } else { + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + } + tevent_req_post(req, state->ev); + + return req; +} + +static void ipa_get_ad_memberships_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_ad_membership_state *state = tevent_req_data(req, + struct get_ad_membership_state); + int ret; + + ret = sdap_id_op_connect_recv(subreq, &state->dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + if (state->dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No IPA server is available, going offline\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect to IPA server: [%d](%s)\n", + ret, strerror(ret)); + } + + goto fail; + } + + subreq = sdap_search_bases_send(state, state->ev, state->sdap_id_ctx->opts, + sdap_id_op_handle(state->sdap_op), + state->sdap_id_ctx->opts->sdom->group_search_bases, + NULL, true, + dp_opt_get_int(state->sdap_id_ctx->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT), + IPA_EXT_GROUPS_FILTER, + NULL, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send failed.\n"); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, ipa_get_ext_groups_done, req); + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static void ipa_get_ext_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_ad_membership_state *state = tevent_req_data(req, + struct get_ad_membership_state); + int ret; + hash_table_t *ext_group_hash; + + ret = sdap_search_bases_recv(subreq, + state, + &state->reply_count, + &state->reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ext_groups request failed.\n"); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "[%zu] external groups found.\n", + state->reply_count); + + ret = process_ext_groups(state, + state->reply_count, + state->reply, + &ext_group_hash); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "process_ext_groups failed.\n"); + goto fail; + } + + talloc_free(state->server_mode->ext_groups->ext_groups); + state->server_mode->ext_groups->ext_groups = talloc_steal( + state->server_mode->ext_groups, + ext_group_hash); + /* Do we have to make the update timeout configurable? */ + state->server_mode->ext_groups->next_update = time(NULL) + 10; + + ret = ipa_add_ext_groups_step(req); + if (ret == EOK) { + tevent_req_done(req); + return; + } else if (ret == EAGAIN) { + return; + } else { + DEBUG(SSSDBG_OP_FAILURE, "ipa_add_ext_groups_step failed.\n"); + goto fail; + } + +fail: + tevent_req_error(req, ret); + return; +} + +static errno_t ipa_add_ext_groups_step(struct tevent_req *req) +{ + struct get_ad_membership_state *state = tevent_req_data(req, + struct get_ad_membership_state); + struct ldb_dn *user_dn; + int ret; + char **groups = NULL; + struct tevent_req *subreq; + + ret = find_ipa_ext_memberships(state, state->user_name, state->user_dom, + state->server_mode->ext_groups->ext_groups, + &user_dn, &groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "find_ipa_ext_memberships failed.\n"); + goto fail; + } + + if (groups == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No external groups memberships found.\n"); + state->dp_error = DP_ERR_OK; + return EOK; + } + + subreq = ipa_add_ad_memberships_send(state, state->ev, state->sdap_id_ctx, + user_dn, state->user_dom, groups, + state->sdap_id_ctx->be->domain); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_add_ad_memberships_send failed.\n"); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, ipa_add_ad_memberships_done, req); + return EAGAIN; + +fail: + tevent_req_error(req, ret); + return ret; +} + +static void ipa_add_ad_memberships_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct get_ad_membership_state *state = tevent_req_data(req, + struct get_ad_membership_state); + int ret; + + ret = ipa_add_ad_memberships_recv(subreq, &state->dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_add_ad_memberships request failed.\n"); + tevent_req_error(req, ret); + return; + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; +} + +errno_t ipa_get_ad_memberships_recv(struct tevent_req *req, int *dp_error_out) +{ + struct get_ad_membership_state *state = tevent_req_data(req, + struct get_ad_membership_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + return EOK; +} + +struct add_ad_membership_state { + struct tevent_context *ev; + struct sdap_id_ctx *sdap_id_ctx; + struct sdap_id_op *sdap_op; + struct ldb_dn *user_dn; + struct sss_domain_info *user_dom; + struct sss_domain_info *group_dom; + char **groups; + int dp_error; + size_t iter; + struct sdap_domain *group_sdom; +}; + +static void ipa_add_ad_memberships_connect_done(struct tevent_req *subreq); +static void ipa_add_ad_memberships_get_next(struct tevent_req *req); +static void ipa_add_ad_memberships_get_group_done(struct tevent_req *subreq); +static struct tevent_req *ipa_add_ad_memberships_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *sdap_id_ctx, + struct ldb_dn *user_dn, + struct sss_domain_info *user_dom, + char **groups, + struct sss_domain_info *group_dom) +{ + int ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct add_ad_membership_state *state; + bool missing_groups = false; + + req = tevent_req_create(mem_ctx, &state, struct add_ad_membership_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->user_dom = user_dom; + state->sdap_id_ctx = sdap_id_ctx; + state->user_dn = user_dn; + state->group_dom = group_dom; + state->groups = groups; + state->dp_error = -1; + state->iter = 0; + state->group_sdom = sdap_domain_get(sdap_id_ctx->opts, group_dom); + if (state->group_sdom == NULL) { + ret = EIO; + goto done; + } + + ret = add_ad_user_to_cached_groups(user_dn, user_dom, group_dom, groups, + &missing_groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_ad_user_to_cached_groups failed.\n"); + goto done; + } + + if (!missing_groups) { + DEBUG(SSSDBG_TRACE_ALL, "All groups found in cache.\n"); + ret = EOK; + goto done; + } + + state->sdap_op = sdap_id_op_create(state, + state->sdap_id_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: %d(%s).\n", + ret, strerror(ret)); + goto done; + } + + tevent_req_set_callback(subreq, ipa_add_ad_memberships_connect_done, req); + + return req; + +done: + if (ret != EOK) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + } else { + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + } + tevent_req_post(req, state->ev); + + return req; +} + +static void ipa_add_ad_memberships_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct add_ad_membership_state *state = tevent_req_data(req, + struct add_ad_membership_state); + int ret; + + ret = sdap_id_op_connect_recv(subreq, &state->dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + if (state->dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No IPA server is available, going offline\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect to IPA server: [%d](%s)\n", + ret, strerror(ret)); + } + + tevent_req_error(req, ret); + return; + } + + state->iter = 0; + ipa_add_ad_memberships_get_next(req); +} + +static void ipa_add_ad_memberships_get_next(struct tevent_req *req) +{ + struct add_ad_membership_state *state = tevent_req_data(req, + struct add_ad_membership_state); + struct tevent_req *subreq; + struct ldb_dn *group_dn; + int ret; + const struct ldb_val *val; + bool missing_groups; + const char *fq_name; + char *tmp_str; + + while (state->groups[state->iter] != NULL + && state->groups[state->iter][0] == '\0') { + state->iter++; + } + + if (state->groups[state->iter] == NULL) { + ret = add_ad_user_to_cached_groups(state->user_dn, state->user_dom, + state->group_dom, state->groups, + &missing_groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_ad_user_to_cached_groups failed.\n"); + goto fail; + } + + if (missing_groups) { + /* this might be HBAC or sudo rule */ + DEBUG(SSSDBG_FUNC_DATA, "There are unresolved external group " + "memberships even after all groups " + "have been looked up on the LDAP " + "server.\n"); + } + tevent_req_done(req); + return; + } + + group_dn = ldb_dn_new(state, sysdb_ctx_get_ldb(state->group_dom->sysdb), + state->groups[state->iter]); + if (group_dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_new failed.\n"); + ret = ENOMEM; + goto fail; + } + + val = ldb_dn_get_rdn_val(group_dn); + if (val == NULL || val->data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Invalid group DN [%s].\n", state->groups[state->iter]); + ret = EINVAL; + goto fail; + } + + fq_name = (const char *) val->data; + if (strchr(fq_name, '@') == NULL) { + tmp_str = sss_create_internal_fqname(state, fq_name, + state->group_dom->name); + /* keep using val->data if sss_create_internal_fqname() fails */ + if (tmp_str != NULL) { + fq_name = tmp_str; + } + } + +/* TODO: here is would be useful for have a filter type like BE_FILTER_DN to + * directly fetch the group with the corresponding DN. */ + subreq = groups_get_send(state, state->ev, + state->sdap_id_ctx, state->group_sdom, + state->sdap_id_ctx->conn, + fq_name, + BE_FILTER_NAME, + false, false, false); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "groups_get_send failed.\n"); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, ipa_add_ad_memberships_get_group_done, req); + return; + +fail: + tevent_req_error(req, ret); +} + +static void ipa_add_ad_memberships_get_group_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct add_ad_membership_state *state = tevent_req_data(req, + struct add_ad_membership_state); + int ret; + + ret = groups_get_recv(subreq, &state->dp_error, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to read group [%s] from LDAP [%d](%s)\n", + state->groups[state->iter], ret, strerror(ret)); + + tevent_req_error(req, ret); + return; + } + + state->iter++; + ipa_add_ad_memberships_get_next(req); +} + +static errno_t ipa_add_ad_memberships_recv(struct tevent_req *req, + int *dp_error_out) +{ + struct add_ad_membership_state *state = tevent_req_data(req, + struct add_ad_membership_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + return EOK; +} + +static errno_t +search_user_or_group_by_sid_str(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *sid_str, + enum sysdb_member_type *_member_type, + struct ldb_message **_msg) +{ + errno_t ret; + struct ldb_message *msg = NULL; + const char *attrs[] = { SYSDB_NAME, + SYSDB_SID_STR, + SYSDB_ORIG_DN, + SYSDB_OBJECTCATEGORY, + SYSDB_CACHE_EXPIRE, + NULL }; + TALLOC_CTX *tmp_ctx = NULL; + char *sanitized_sid = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + /* In theory SID shouldn't contain any special LDAP characters, but let's + * be paranoid + */ + ret = sss_filter_sanitize(tmp_ctx, sid_str, &sanitized_sid); + if (ret != EOK) { + goto done; + } + + ret = sysdb_search_user_by_sid_str(tmp_ctx, domain, + sid_str, attrs, &msg); + if (ret == EOK) { + *_member_type = SYSDB_MEMBER_USER; + } else if (ret == ENOENT) { + ret = sysdb_search_group_by_sid_str(tmp_ctx, domain, + sid_str, attrs, &msg); + if (ret == EOK) { + *_member_type = SYSDB_MEMBER_GROUP; + } + } + + switch (ret) { + case EOK: + DEBUG(SSSDBG_TRACE_FUNC, "Found %s in sysdb\n", sid_str); + *_msg = talloc_steal(mem_ctx, msg); + break; + case ENOENT: + DEBUG(SSSDBG_TRACE_FUNC, + "Could not find %s in sysdb\n", sid_str); + break; + default: + DEBUG(SSSDBG_OP_FAILURE, + "Error looking for %s in sysdb [%d]: %s\n", + sid_str, ret, sss_strerror(ret)); + break; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ipa_ext_group_member_check(TALLOC_CTX *mem_ctx, + struct sss_domain_info *member_dom, + const char *ext_member, + enum sysdb_member_type *_member_type, + struct sysdb_attrs **_member) +{ + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + uint64_t expire; + time_t now = time(NULL); + struct ldb_message *msg; + struct sysdb_attrs **members; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = search_user_or_group_by_sid_str(tmp_ctx, member_dom, ext_member, + _member_type, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Error looking up sid %s: [%d]: %s\n", + ext_member, ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_msg2attrs(tmp_ctx, 1, &msg, &members); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not convert result to sysdb_attrs [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Return the member both expired and valid */ + *_member = talloc_steal(mem_ctx, members[0]); + + expire = ldb_msg_find_attr_as_uint64(msg, SYSDB_CACHE_EXPIRE, 0); + if (expire != 0 && expire <= now) { + DEBUG(SSSDBG_TRACE_FUNC, "%s is expired\n", ext_member); + ret = EAGAIN; + goto done; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +/* For the IPA external member resolution, we expect a SID as the input. + * The _recv() function output is the member and a type (user/group) + * since nothing else can be a group member. + */ +struct ipa_ext_member_state { + const char *ext_member; + struct sss_domain_info *dom; + + enum sysdb_member_type member_type; + struct sysdb_attrs *member; +}; + +static void ipa_ext_group_member_done(struct tevent_req *subreq); + +struct tevent_req *ipa_ext_group_member_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *ext_member, + void *pvt) +{ + struct ipa_id_ctx *ipa_ctx; + struct ipa_ext_member_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + struct dp_id_data *ar; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ipa_ext_member_state); + if (req == NULL) { + return NULL; + } + state->ext_member = ext_member; + + ipa_ctx = talloc_get_type(pvt, struct ipa_id_ctx); + if (ipa_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Wrong private context!\n"); + ret = EINVAL; + goto immediate; + } + + state->dom = find_domain_by_sid(ipa_ctx->sdap_id_ctx->be->domain, + ext_member); + if (state->dom == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot find domain of SID [%s]\n", ext_member); + ret = ENOENT; + goto immediate; + } + + ret = ipa_ext_group_member_check(state, state->dom, ext_member, + &state->member_type, &state->member); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "external member %s already cached\n", ext_member); + goto immediate; + } + + ret = get_dp_id_data_for_sid(state, ext_member, state->dom->name, &ar); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot create the account request for [%s]\n", ext_member); + goto immediate; + } + + subreq = dp_req_send(state, ipa_ctx->sdap_id_ctx->be->provider, + ar->domain, "External Member", 0, NULL, + DPT_ID, DPM_ACCOUNT_HANDLER, 0, ar, NULL); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, ipa_ext_group_member_done, req); + + return req; + +immediate: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, ev); + return req; +} + +static void ipa_ext_group_member_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_ext_member_state *state = tevent_req_data(req, + struct ipa_ext_member_state); + errno_t ret; + struct ldb_message *msg; + struct sysdb_attrs **members; + struct dp_reply_std *reply; + + + ret = dp_req_recv_ptr(state, subreq, struct dp_reply_std, &reply); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "dp_req_recv failed\n"); + tevent_req_error(req, ret); + return; + } else if (reply->dp_error != DP_ERR_OK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot refresh data from DP: %u,%u: %s\n", + reply->dp_error, reply->error, reply->message); + tevent_req_error(req, EIO); + return; + } + + ret = search_user_or_group_by_sid_str(state, + state->dom, + state->ext_member, + &state->member_type, + &msg); + if (ret != EOK) { + DEBUG(ret == ENOENT ? SSSDBG_TRACE_FUNC : SSSDBG_OP_FAILURE, + "Could not find %s in sysdb [%d]: %s\n", + state->ext_member, ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = sysdb_msg2attrs(state, 1, &msg, &members); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not convert result to sysdb_attrs [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->member = members[0]; + tevent_req_done(req); +} + +errno_t ipa_ext_group_member_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + enum sysdb_member_type *_member_type, + struct sss_domain_info **_dom, + struct sysdb_attrs **_member) +{ + struct ipa_ext_member_state *state = tevent_req_data(req, + struct ipa_ext_member_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_member_type != NULL) { + *_member_type = state->member_type; + } + + if (_dom) { + *_dom = state->dom; + } + + if (_member != NULL) { + *_member = talloc_steal(mem_ctx, state->member); + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_subdomains_id.c b/src/providers/ipa/ipa_subdomains_id.c new file mode 100644 index 0000000..1b63f9e --- /dev/null +++ b/src/providers/ipa/ipa_subdomains_id.c @@ -0,0 +1,1827 @@ +/* + SSSD + + IPA Identity Backend Module for sub-domains + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2012 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <errno.h> + +#include "util/util.h" +#include "util/sss_nss.h" +#include "util/strtonum.h" +#include "db/sysdb.h" +#include "providers/ldap/ldap_common.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_async_ad.h" +#include "providers/ipa/ipa_id.h" +#include "providers/ad/ad_id.h" +#include "providers/ad/ad_pac.h" +#include "providers/ipa/ipa_subdomains.h" + +static struct tevent_req * +ipa_srv_ad_acct_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sysdb_attrs *override_attrs, + struct dp_id_data *ar); +static errno_t +ipa_srv_ad_acct_recv(struct tevent_req *req, int *dp_error_out); + +struct ipa_subdomain_account_state { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct sdap_id_ctx *ctx; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct dp_id_data *ar; + + bool ipa_server_mode; + bool server_retry; + int entry_type; + const char *filter; + int filter_type; + struct sysdb_attrs *override_attrs; + struct sysdb_attrs *mapped_attrs; + char *object_sid; + + int dp_error; +}; + +static void ipa_subdomain_account_connected(struct tevent_req *subreq); +static void ipa_subdomain_account_got_override(struct tevent_req *subreq); +static void ipa_subdomain_account_done(struct tevent_req *subreq); +static errno_t ipa_subdomain_account_get_original_step(struct tevent_req *req, + struct dp_id_data *ar); + +struct tevent_req *ipa_subdomain_account_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct dp_id_data *ar) +{ + struct tevent_req *req; + struct ipa_subdomain_account_state *state; + struct tevent_req *subreq; + int ret; + + req = tevent_req_create(memctx, &state, struct ipa_subdomain_account_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->ctx = ipa_ctx->sdap_id_ctx; + state->dp_error = DP_ERR_FATAL; + + state->op = sdap_id_op_create(state, state->ctx->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + state->domain = find_domain_by_name(state->ctx->be->domain, + ar->domain, true); + if (state->domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n"); + ret = ENOMEM; + goto fail; + } + state->sysdb = state->domain->sysdb; + state->ar = ar; + state->ipa_server_mode = dp_opt_get_bool(state->ipa_ctx->ipa_options->basic, + IPA_SERVER_MODE); + state->override_attrs = NULL; + state->mapped_attrs = NULL; + + /* With views we cannot got directly to the look up the AD objects but + * have to check first if the request matches an override in the given + * view. But there are cases where this can be skipped and the AD object + * can be searched directly: + * - if no view is defined, i.e. the server does not supprt views yet + * - searches by SID: because we do not override the SID + * - if the responder does not send the EXTRA_INPUT_MAYBE_WITH_VIEW flags, + * because in this case the entry was found in the cache and the + * original value is used for the search (e.g. during cache updates) */ + if (state->ipa_ctx->view_name == NULL + || state->ar->filter_type == BE_FILTER_SECID + || (!state->ipa_server_mode + && state->ar->extra_value != NULL + && strcmp(state->ar->extra_value, + EXTRA_INPUT_MAYBE_WITH_VIEW) != 0 )) { + ret = ipa_subdomain_account_get_original_step(req, state->ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_subdomain_account_get_original_step failed.\n"); + goto fail; + } + + return req; + } + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + goto fail; + } + tevent_req_set_callback(subreq, ipa_subdomain_account_connected, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ipa_subdomain_account_connected(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_subdomain_account_state *state = tevent_req_data(req, + struct ipa_subdomain_account_state); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect request failed.\n"); + goto fail; + } + + subreq = ipa_get_ad_override_send(state, state->ev, state->ctx, + state->ipa_ctx->ipa_options, + dp_opt_get_string(state->ipa_ctx->ipa_options->basic, + IPA_KRB5_REALM), + state->ipa_ctx->view_name, state->ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, ipa_subdomain_account_got_override, req); + + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +#define OVERRIDE_ANCHOR_SID_PREFIX ":SID:" +#define OVERRIDE_ANCHOR_SID_PREFIX_LEN (sizeof(OVERRIDE_ANCHOR_SID_PREFIX) -1 ) + +static void ipa_subdomain_account_got_override(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_subdomain_account_state *state = tevent_req_data(req, + struct ipa_subdomain_account_state); + int dp_error = DP_ERR_FATAL; + int ret; + const char *anchor = NULL; + struct dp_id_data *ar; + + ret = ipa_get_ad_override_recv(subreq, &dp_error, state, + &state->override_attrs); + talloc_zfree(subreq); + if (ret != EOK) { + ret = sdap_id_op_done(state->op, ret, &dp_error); + + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed.\n"); + goto fail; + } + tevent_req_set_callback(subreq, ipa_subdomain_account_connected, + req); + return; + } + + DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); + goto fail; + } + + if (state->ar->filter_type == BE_FILTER_CERT + && is_default_view(state->ipa_ctx->view_name)) { + /* The override data was found with a lookup by certificate. for the + * default view the certificate will be added to + * SYSDB_USER_MAPPED_CERT so that cache lookups will find the same + * user. If no override data was found the mapping (if any) should be + * removed. For other view this is not needed because the override + * certificate is store in the cached override object in this case. */ + state->mapped_attrs = sysdb_new_attrs(state); + if (state->mapped_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_new_attrs failed, ignored.\n"); + } else { + ret = sysdb_attrs_add_base64_blob(state->mapped_attrs, + SYSDB_USER_MAPPED_CERT, + state->ar->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_attrs_add_base64_blob failed, ignored.\n"); + talloc_free(state->mapped_attrs); + state->mapped_attrs = NULL; + } + } + } + + if (state->override_attrs != NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Processing override.\n"); + + ret = sysdb_attrs_get_string(state->override_attrs, + SYSDB_OVERRIDE_ANCHOR_UUID, + &anchor); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed.\n"); + goto fail; + } + if (anchor != NULL && strncmp(OVERRIDE_ANCHOR_SID_PREFIX, anchor, + OVERRIDE_ANCHOR_SID_PREFIX_LEN) == 0) { + + ret = get_dp_id_data_for_sid(state, + anchor + OVERRIDE_ANCHOR_SID_PREFIX_LEN, + state->ar->domain, + &ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + goto fail; + } + + if (state->mapped_attrs != NULL) { + /* save the SID so that SYSDB_USER_MAPPED_CERT can be added + * later to the object */ + state->object_sid = talloc_strdup(state, ar->filter_value); + if (state->object_sid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "talloc_strdup failed, ignored.\n"); + talloc_free(state->mapped_attrs); + state->mapped_attrs = NULL; + } + } + + if (state->ipa_server_mode + && (state->ar->entry_type & BE_REQ_TYPE_MASK) + == BE_REQ_INITGROUPS) { + DEBUG(SSSDBG_TRACE_ALL, + "Switching back to BE_REQ_INITGROUPS.\n"); + ar->entry_type = BE_REQ_INITGROUPS; + ar->filter_type = BE_FILTER_SECID; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupported override anchor type [%s].\n", anchor); + ret = EINVAL; + goto fail; + } + } else { + if (state->mapped_attrs != NULL) { + /* remove certificate (if any) if no matching override was found */ + ret = sysdb_remove_mapped_data(state->domain, state->mapped_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_remove_mapped_data failed, " + "some cached entries might contain " + "invalid mapping data.\n"); + } + talloc_free(state->mapped_attrs); + state->mapped_attrs = NULL; + } + ar = state->ar; + } + + ret = ipa_subdomain_account_get_original_step(req, ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_subdomain_account_get_original_step failed.\n"); + goto fail; + } + + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; +} + +static errno_t ipa_subdomain_account_get_original_step(struct tevent_req *req, + struct dp_id_data *ar) +{ + struct ipa_subdomain_account_state *state = tevent_req_data(req, + struct ipa_subdomain_account_state); + struct tevent_req *subreq; + + if (state->ipa_server_mode) { + subreq = ipa_srv_ad_acct_send(state, state->ev, state->ipa_ctx, + state->override_attrs, ar); + } else { + subreq = ipa_get_subdom_acct_send(state, state->ev, state->ipa_ctx, + state->override_attrs, ar); + } + + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_*_acct_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_subdomain_account_done, req); + + return EOK; +} + + +static void ipa_subdomain_account_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_subdomain_account_state *state = tevent_req_data(req, + struct ipa_subdomain_account_state); + int dp_error = DP_ERR_FATAL; + int ret; + struct ldb_result *res; + struct sss_domain_info *object_dom; + + if (state->ipa_server_mode) { + ret = ipa_srv_ad_acct_recv(subreq, &dp_error); + } else { + ret = ipa_get_subdom_acct_recv(subreq, &dp_error); + } + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_*_acct request failed: [%d]: %s.\n", + ret, sss_strerror(ret)); + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (state->mapped_attrs != NULL) { + object_dom = sss_get_domain_by_sid_ldap_fallback(state->domain, + state->object_sid); + ret = sysdb_search_object_by_sid(state, + object_dom != NULL ? object_dom + : state->domain, + state->object_sid, NULL, &res); + if (ret == EOK) { + ret = sysdb_set_entry_attr(state->domain->sysdb, res->msgs[0]->dn, + state->mapped_attrs, SYSDB_MOD_ADD); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_set_entry_attr failed, ignoring.\n"); + } + } else if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "No cached object found, cannot add " + "mapped attribute, ignoring.\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_search_object_by_sid failed, cannot add mapped " + "attribute, ignoring.\n"); + } + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; +} + +errno_t ipa_subdomain_account_recv(struct tevent_req *req, int *dp_error_out) +{ + struct ipa_subdomain_account_state *state = tevent_req_data(req, + struct ipa_subdomain_account_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_get_subdom_acct { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct sdap_id_ctx *ctx; + struct sdap_id_op *op; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct sysdb_attrs *override_attrs; + + int entry_type; + const char *filter; + int filter_type; + const char *extra_value; + bool use_pac; + struct ldb_message *user_msg; + + int dp_error; +}; + +static void ipa_get_subdom_acct_connected(struct tevent_req *subreq); +static void ipa_get_subdom_acct_done(struct tevent_req *subreq); + +struct tevent_req *ipa_get_subdom_acct_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sysdb_attrs *override_attrs, + struct dp_id_data *ar) +{ + struct tevent_req *req; + struct ipa_get_subdom_acct *state; + struct tevent_req *subreq; + int ret; + + req = tevent_req_create(memctx, &state, struct ipa_get_subdom_acct); + if (!req) return NULL; + + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->ctx = ipa_ctx->sdap_id_ctx; + state->dp_error = DP_ERR_FATAL; + state->override_attrs = override_attrs; + state->use_pac = false; + + state->op = sdap_id_op_create(state, state->ctx->conn->conn_cache); + if (!state->op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto fail; + } + + state->domain = find_domain_by_name(state->ctx->be->domain, + ar->domain, true); + if (state->domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n"); + ret = ENOMEM; + goto fail; + } + state->sysdb = state->domain->sysdb; + + state->entry_type = (ar->entry_type & BE_REQ_TYPE_MASK); + state->filter = ar->filter_value; + state->filter_type = ar->filter_type; + state->extra_value = ar->extra_value; + + switch (state->entry_type) { + case BE_REQ_USER: + case BE_REQ_GROUP: + case BE_REQ_BY_SECID: + case BE_REQ_BY_CERT: + case BE_REQ_USER_AND_GROUP: + ret = EOK; + break; + case BE_REQ_INITGROUPS: + ret = check_if_pac_is_available(state, state->domain, ar, + &state->user_msg); + if (ret == EOK) { + state->use_pac = true; + } + + ret = EOK; + break; + default: + ret = EINVAL; + if (state->entry_type > BE_REQ__LAST) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid sub-domain request type %d.\n", + state->entry_type); + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Unhandled sub-domain request type %d.\n", + state->entry_type); + } + } + if (ret != EOK) goto fail; + + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + goto fail; + } + tevent_req_set_callback(subreq, ipa_get_subdom_acct_connected, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void ipa_get_subdom_acct_connected(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_subdom_acct *state = tevent_req_data(req, + struct ipa_get_subdom_acct); + int dp_error = DP_ERR_FATAL; + int ret; + char *endptr; + struct req_input *req_input; + char *shortname; + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + if (state->entry_type == BE_REQ_INITGROUPS) { + /* With V1/V2 of the extdom plugin a user lookup will resolve the full + * group membership of the user. */ + if (sdap_is_extension_supported(sdap_id_op_handle(state->op), + EXOP_SID2NAME_V1_OID) || + sdap_is_extension_supported(sdap_id_op_handle(state->op), + EXOP_SID2NAME_V2_OID)) { + state->entry_type = BE_REQ_USER; + } else { + if (state->use_pac && state->user_msg != NULL) { + /* This means the user entry is already in the cache and has + * the pac attached, we only have look up the missing groups + * and add the user to all groups. */ + + subreq = ipa_get_subdom_acct_process_pac_send(state, state->ev, + sdap_id_op_handle(state->op), + state->ipa_ctx, + state->domain, + state->user_msg); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_get_subdom_acct_process_pac failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ipa_get_subdom_acct_done, req); + + return; + } + + /* Fall through if there is no PAC */ + + DEBUG(SSSDBG_TRACE_FUNC, "Initgroups requests are not handled " \ + "by the IPA provider but are resolved " \ + "by the responder directly from the " \ + "cache.\n"); + tevent_req_error(req, ENOTSUP); + return; + } + } + + req_input = talloc(state, struct req_input); + if (req_input == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + + switch (state->filter_type) { + case BE_FILTER_NAME: + req_input->type = REQ_INP_NAME; + /* The extdom plugin expects the shortname and domain separately, + * but for UPN/email lookup we need to send the raw name */ + if (state->extra_value != NULL + && strcmp(state->extra_value, EXTRA_NAME_IS_UPN) == 0) { + req_input->inp.name = talloc_strdup(req_input, state->filter); + } else { + ret = sss_parse_internal_fqname(req_input, state->filter, + &shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot parse internal name [%s]: %d\n", + state->filter, ret); + tevent_req_error(req, ret); + return; + } + + req_input->inp.name = talloc_steal(req_input, shortname); + } + if (req_input->inp.name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + break; + case BE_FILTER_IDNUM: + req_input->type = REQ_INP_ID; + req_input->inp.id = strtouint32(state->filter, &endptr, 10); + if (errno || *endptr || (state->filter == endptr)) { + tevent_req_error(req, errno ? errno : EINVAL); + return; + } + break; + case BE_FILTER_SECID: + req_input->type = REQ_INP_SECID; + req_input->inp.secid = talloc_strdup(req_input, state->filter); + if (req_input->inp.secid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + break; + case BE_FILTER_CERT: + if (sdap_is_extension_supported(sdap_id_op_handle(state->op), + EXOP_SID2NAME_V1_OID) || + sdap_is_extension_supported(sdap_id_op_handle(state->op), + EXOP_SID2NAME_V2_OID)) { + req_input->type = REQ_INP_CERT; + req_input->inp.cert = talloc_strdup(req_input, state->filter); + if (req_input->inp.cert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Lookup by certificate not supported by the server.\n"); + state->dp_error = DP_ERR_OK; + tevent_req_error(req, EINVAL); + return; + } + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Invalid sub-domain filter type.\n"); + state->dp_error = dp_error; + tevent_req_error(req, EINVAL); + return; + } + + subreq = ipa_s2n_get_acct_info_send(state, + state->ev, + state->ipa_ctx, + state->ctx->opts, + state->domain, + state->override_attrs, + sdap_id_op_handle(state->op), + state->entry_type, + req_input); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, ipa_get_subdom_acct_done, req); + + return; +} + +static void ipa_get_subdom_acct_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_subdom_acct *state = tevent_req_data(req, + struct ipa_get_subdom_acct); + int dp_error = DP_ERR_FATAL; + int ret; + + ret = ipa_s2n_get_acct_info_recv(subreq); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->op, ret, &dp_error); + if (dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + subreq = sdap_id_op_connect_send(state->op, state, &ret); + if (!subreq) { + tevent_req_error(req, ret); + return; + } + tevent_req_set_callback(subreq, ipa_get_subdom_acct_connected, req); + return; + } + + if (ret && ret != ENOENT) { + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + /* FIXME: do we need some special handling of ENOENT */ + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); +} + +int ipa_get_subdom_acct_recv(struct tevent_req *req, int *dp_error_out) +{ + struct ipa_get_subdom_acct *state = tevent_req_data(req, + struct ipa_get_subdom_acct); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static struct ad_id_ctx *ipa_get_ad_id_ctx(struct ipa_id_ctx *ipa_ctx, + struct sss_domain_info *dom); + +static struct sdap_id_conn_ctx ** +ipa_ad_gc_conn_list(TALLOC_CTX *mem_ctx, struct ipa_id_ctx *ipa_ctx, + struct ad_id_ctx *ad_ctx, struct sss_domain_info *dom) +{ + struct ad_id_ctx *forest_root_ad_id_ctx; + struct sdap_id_conn_ctx **clist; + int cindex = 0; + + /* While creating the domains and sub-domains each domain gets a global + * catalog services assigned but only one should be used because the + * global catalog is by definition responsible for the whole forest so it + * does not make sense to use a global catalog service for each domain and + * in the worst case connect to the same GC multiple times. + * + * In the AD provider this is simple because the GC service of the + * configured domain AD_GC_SERVICE_NAME ("AD_GC") can be used. In the IPA + * case all domains from the trusted forest are on the level of + * sub-domains so we have to pick one. Since the forest root is linked + * from all domain of the same forest it will be the most straight forward + * choice. */ + forest_root_ad_id_ctx = ipa_get_ad_id_ctx(ipa_ctx, dom->forest_root); + if (forest_root_ad_id_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing ad_id_ctx for forest root.\n"); + return NULL; + } + + clist = talloc_zero_array(mem_ctx, struct sdap_id_conn_ctx *, 3); + if (clist == NULL) return NULL; + + /* Always try GC first */ + if (dp_opt_get_bool(forest_root_ad_id_ctx->ad_options->basic, + AD_ENABLE_GC)) { + clist[cindex] = forest_root_ad_id_ctx->gc_ctx; + clist[cindex]->ignore_mark_offline = true; + clist[cindex]->no_mpg_user_fallback = true; + cindex++; + } + + clist[cindex] = ad_get_dom_ldap_conn(ad_ctx, dom); + + return clist; +} + +/* IPA lookup for server mode. Directly to AD. */ +struct ipa_get_ad_acct_state { + int dp_error; + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct dp_id_data *ar; + struct sss_domain_info *obj_dom; + char *object_sid; + struct sysdb_attrs *override_attrs; + struct ldb_message *obj_msg; +}; + +static void ipa_get_ad_acct_ad_part_done(struct tevent_req *subreq); +static void ipa_get_ad_override_done(struct tevent_req *subreq); +static errno_t ipa_get_ad_apply_override_step(struct tevent_req *req); +static errno_t ipa_get_ad_ipa_membership_step(struct tevent_req *req); +static void ipa_id_get_groups_overrides_done(struct tevent_req *subreq); +static void ipa_get_ad_acct_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_get_ad_acct_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sysdb_attrs *override_attrs, + struct dp_id_data *ar) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct ipa_get_ad_acct_state *state; + struct sdap_domain *sdom; + struct sdap_id_conn_ctx **clist; + struct sdap_id_ctx *sdap_id_ctx; + struct ad_id_ctx *ad_id_ctx; + + req = tevent_req_create(mem_ctx, &state, struct ipa_get_ad_acct_state); + if (req == NULL) return NULL; + + state->dp_error = -1; + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->ar = ar; + state->obj_msg = NULL; + state->override_attrs = override_attrs; + + /* This can only be a subdomain request, verify subdomain */ + state->obj_dom = find_domain_by_name(ipa_ctx->sdap_id_ctx->be->domain, + ar->domain, true); + if (state->obj_dom == NULL) { + ret = EINVAL; + goto fail; + } + + /* Let's see if this subdomain has a ad_id_ctx */ + ad_id_ctx = ipa_get_ad_id_ctx(ipa_ctx, state->obj_dom); + if (ad_id_ctx == NULL) { + ret = EINVAL; + goto fail; + } + sdap_id_ctx = ad_id_ctx->sdap_id_ctx; + + /* We read users and groups from GC. From groups, we may switch to + * using LDAP connection in the group request itself, but in order + * to resolve Universal group memberships, we also need the GC + * connection + */ + switch (state->ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_INITGROUPS: + case BE_REQ_BY_SECID: + case BE_REQ_GROUP: + clist = ipa_ad_gc_conn_list(req, ipa_ctx, ad_id_ctx, state->obj_dom); + break; + default: + clist = ad_ldap_conn_list(req, ad_id_ctx, state->obj_dom); + break; + } + + if (clist == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot generate AD connection list!\n"); + ret = ENOMEM; + goto fail; + } + + /* Now we already need ad_id_ctx in particular sdap_id_conn_ctx */ + sdom = sdap_domain_get(sdap_id_ctx->opts, state->obj_dom); + if (sdom == NULL) { + ret = EIO; + goto fail; + } + + subreq = ad_handle_acct_info_send(req, ar, sdap_id_ctx, + ad_id_ctx->ad_options, sdom, clist); + if (subreq == NULL) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, ipa_get_ad_acct_ad_part_done, req); + return req; + +fail: + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static struct ad_id_ctx * +ipa_get_ad_id_ctx(struct ipa_id_ctx *ipa_ctx, + struct sss_domain_info *dom) +{ + struct ipa_ad_server_ctx *iter; + + DLIST_FOR_EACH(iter, ipa_ctx->server_mode->trusts) { + if (iter->dom == dom) break; + } + + return (iter) ? iter->ad_id_ctx : NULL; +} + +static errno_t +get_subdomain_homedir_of_user(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, + const char *fqname, uint32_t uid, + const char *original, const char **_homedir) +{ + errno_t ret; + const char *homedir; + TALLOC_CTX *tmp_ctx; + struct sss_nss_homedir_ctx homedir_ctx; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + if (strstr(dom->subdomain_homedir, "%o") != NULL && original == NULL) { + DEBUG(SSSDBG_TRACE_ALL, + "Original home directory for user: %s is empty.\n", fqname); + ret = ERR_HOMEDIR_IS_NULL; + goto done; + } + + memset(&homedir_ctx, 0, sizeof(homedir_ctx)); + + homedir_ctx.uid = uid; + homedir_ctx.username = fqname; + homedir_ctx.domain = dom->name; + homedir_ctx.flatname = dom->flat_name; + homedir_ctx.config_homedir_substr = dom->homedir_substr; + homedir_ctx.original = original; + + /* To be compatible with the old winbind based user lookups and IPA + * clients the user name in the home directory path will be lower-case. */ + homedir = expand_homedir_template(tmp_ctx, dom->subdomain_homedir, + false, &homedir_ctx); + if (homedir == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "expand_homedir_template failed\n"); + ret = ENOMEM; + goto done; + } + + if (_homedir == NULL) { + ret = EINVAL; + goto done; + } + *_homedir = talloc_steal(mem_ctx, homedir); + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +store_homedir_of_user(struct sss_domain_info *domain, + const char *fqname, const char *homedir) +{ + errno_t ret; + errno_t sret; + TALLOC_CTX *tmp_ctx; + bool in_transaction = false; + struct sysdb_attrs *attrs; + struct sysdb_ctx *sysdb = domain->sysdb; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(attrs, SYSDB_HOMEDIR, homedir); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Error setting homedir: [%s]\n", + strerror(ret)); + goto done; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + + in_transaction = true; + + ret = sysdb_set_user_attr(domain, fqname, attrs, SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to update homedir information!\n"); + goto done; + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot commit sysdb transaction [%d]: %s.\n", + ret, strerror(ret)); + goto done; + } + + in_transaction = false; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction.\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +apply_subdomain_homedir(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, + struct ldb_message *msg) +{ + errno_t ret; + uint32_t uid; + const char *fqname; + const char *original; + const char *homedir = NULL; + struct ldb_message_element *msg_el = NULL; + size_t c; + const char *category = NULL; + size_t length = 0; + bool user_class = true; + + msg_el = ldb_msg_find_element(msg, SYSDB_OBJECTCATEGORY); + if (msg_el == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_msg_find_element failed.\n"); + ret = ENOENT; + goto done; + } + + /* The object is a user if SYSDB_OBJECTCATEGORY is SYSDB_USER_CLASS or in + * case of a MPG group lookup if SYSDB_OBJECTCATEGORY is SYSDB_GROUP_CLASS. + */ + for (c = 0; c < msg_el->num_values; c++) { + category = (const char *)msg_el->values[c].data; + length = msg_el->values[c].length; + if (strncmp(SYSDB_USER_CLASS, category, length) == 0) { + user_class = true; + break; + } + if (sss_domain_is_mpg(dom) + && strncmp(SYSDB_GROUP_CLASS, category, length) == 0) { + user_class = false; + break; + } + } + if (c == msg_el->num_values) { + DEBUG(SSSDBG_TRACE_ALL, + "User objectclass not found, object is not a user.\n"); + ret = ENOENT; + goto done; + } + + fqname = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (fqname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); + ret = EINVAL; + goto done; + } + + uid = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0); + if (uid == 0) { + if (user_class) { + DEBUG(SSSDBG_OP_FAILURE, "UID for user [%s] is unknown\n", fqname); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "No UID for object [%s], perhaps mpg\n", fqname); + } + ret = ENOENT; + goto done; + } + + original = ldb_msg_find_attr_as_string(msg, SYSDB_HOMEDIR, NULL); + if (original == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Missing homedir of %s.\n", fqname); + } + + ret = get_subdomain_homedir_of_user(mem_ctx, dom, fqname, uid, original, + &homedir); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "get_subdomain_homedir_of_user failed: [%d]: [%s]\n", + ret, sss_strerror(ret)); + if (ret == ERR_HOMEDIR_IS_NULL) { + /* This is not fatal, fallback_homedir will be used. */ + ret = EOK; + } + goto done; + } + + ret = store_homedir_of_user(dom, fqname, homedir); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "store_homedir_of_user failed: [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + return ret; +} + +errno_t get_object_from_cache(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + struct dp_id_data *ar, + struct ldb_message **_msg) +{ + errno_t ret; + uint32_t id; + struct ldb_message *msg = NULL; + struct ldb_result *res = NULL; + char *endptr; + const char *attrs[] = { SYSDB_NAME, + SYSDB_UIDNUM, + SYSDB_SID_STR, + SYSDB_OBJECTCATEGORY, + SYSDB_UUID, + SYSDB_GHOST, + SYSDB_HOMEDIR, + NULL }; + + if (ar->filter_type == BE_FILTER_SECID) { + ret = sysdb_search_object_by_sid(mem_ctx, dom, ar->filter_value, attrs, + &res); + if (ret == EOK) { + *_msg = res->msgs[0]; + } + goto done; + } else if (ar->filter_type == BE_FILTER_UUID) { + ret = sysdb_search_object_by_uuid(mem_ctx, dom, ar->filter_value, attrs, + &res); + if (ret == EOK) { + *_msg = res->msgs[0]; + } + goto done; + } else if (ar->filter_type == BE_FILTER_CERT) { + ret = sysdb_search_object_by_cert(mem_ctx, dom, ar->filter_value, attrs, + &res); + if (ret == EOK) { + if (res->count != 1) { + DEBUG(SSSDBG_OP_FAILURE, + "More than one result found in our cache\n"); + ret = EINVAL; + } else { + *_msg = res->msgs[0]; + } + } + goto done; + } else if (ar->filter_type == BE_FILTER_IDNUM) { + id = strtouint32(ar->filter_value, &endptr, 10); + if ((errno != 0) || *endptr || (ar->filter_value == endptr)) { + ret = errno ? errno : EINVAL; + DEBUG(SSSDBG_OP_FAILURE, "strtouint32 failed.\n"); + goto done; + } + + switch (ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_GROUP: + ret = sysdb_getgrgid_attrs(mem_ctx, dom, id, attrs, &res); + if (ret == EOK) { + if (res->count == 0) { + ret = ENOENT; + } else { + msg = res->msgs[0]; + } + } + break; + case BE_REQ_INITGROUPS: + case BE_REQ_USER: + case BE_REQ_USER_AND_GROUP: + ret = sysdb_search_user_by_uid(mem_ctx, dom, id, attrs, &msg); + if (ret == ENOENT && (ar->entry_type & BE_REQ_TYPE_MASK) + == BE_REQ_USER_AND_GROUP) { + ret = sysdb_getgrgid_attrs(mem_ctx, dom, id, attrs, &res); + if (ret == EOK) { + if (res->count == 0) { + ret = ENOENT; + } else { + msg = res->msgs[0]; + } + } + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected entry type [%d].\n", + (ar->entry_type & BE_REQ_TYPE_MASK)); + ret = EINVAL; + goto done; + } + } else if (ar->filter_type == BE_FILTER_NAME) { + switch (ar->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_GROUP: + ret = sysdb_search_group_by_name(mem_ctx, dom, ar->filter_value, + attrs, &msg); + break; + case BE_REQ_INITGROUPS: + case BE_REQ_USER: + case BE_REQ_USER_AND_GROUP: + if (ar->extra_value + && strcmp(ar->extra_value, EXTRA_NAME_IS_UPN) == 0) { + ret = sysdb_search_user_by_upn(mem_ctx, dom, false, ar->filter_value, + attrs, &msg); + } else { + ret = sysdb_search_user_by_name(mem_ctx, dom, ar->filter_value, + attrs, &msg); + if (ret == ENOENT && (ar->entry_type & BE_REQ_TYPE_MASK) + == BE_REQ_USER_AND_GROUP) { + ret = sysdb_search_group_by_name(mem_ctx, dom, + ar->filter_value, attrs, + &msg); + } + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected entry type [%d].\n", + (ar->entry_type & BE_REQ_TYPE_MASK)); + ret = EINVAL; + goto done; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected filter type.\n"); + ret = EINVAL; + goto done; + } + + if (ret == EOK) { + *_msg = msg; + } + +done: + if (ret != EOK) { + if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to make request to our cache: [%d]: [%s]\n", + ret, sss_strerror(ret)); + } else { + DEBUG(SSSDBG_FUNC_DATA, "Object wasn't found in cache\n"); + } + } + + return ret; +} + +static void +ipa_get_ad_acct_ad_part_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_ad_acct_state *state = tevent_req_data(req, + struct ipa_get_ad_acct_state); + errno_t ret; + const char *sid; + struct dp_id_data *ar; + + ret = ad_handle_acct_info_recv(subreq, &state->dp_error, NULL); + talloc_zfree(subreq); + if (ret == ERR_SUBDOM_INACTIVE) { + tevent_req_error(req, ret); + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "AD lookup failed: %d\n", ret); + tevent_req_error(req, ret); + return; + } + + ret = get_object_from_cache(state, state->obj_dom, state->ar, + &state->obj_msg); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, "Object not found, ending request\n"); + tevent_req_done(req); + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_object_from_cache failed.\n"); + goto fail; + } + + ret = apply_subdomain_homedir(state, state->obj_dom, + state->obj_msg); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "apply_subdomain_homedir failed: [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto fail; + } + + if (state->override_attrs == NULL) { + sid = ldb_msg_find_attr_as_string(state->obj_msg, SYSDB_SID_STR, NULL); + if (sid == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot find a SID.\n"); + ret = EINVAL; + goto fail; + } + + state->object_sid = talloc_strdup(state, sid); + if (state->object_sid == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto fail; + } + + ret = get_dp_id_data_for_sid(state, state->object_sid, + state->obj_dom->name, &ar); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_dp_id_data_for_sid failed.\n"); + goto fail; + } + + subreq = ipa_get_ad_override_send(state, state->ev, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->ipa_options, + state->ipa_ctx->server_mode->realm, + state->ipa_ctx->view_name, + ar); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override_send failed.\n"); + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, ipa_get_ad_override_done, req); + } else { + ret = ipa_get_ad_apply_override_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa_get_ad_apply_override_step failed.\n"); + goto fail; + } + } + + return; + +fail: + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + return; +} + + +static void +ipa_get_ad_override_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_ad_acct_state *state = tevent_req_data(req, + struct ipa_get_ad_acct_state); + errno_t ret; + + ret = ipa_get_ad_override_recv(subreq, &state->dp_error, state, + &state->override_attrs); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA override lookup failed: %d\n", ret); + tevent_req_error(req, ret); + return; + + } + + ret = ipa_get_ad_apply_override_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_apply_override_step failed.\n"); + goto fail; + } + + return; + +fail: + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + return; +} + +static void ipa_check_ghost_members_done(struct tevent_req *subreq); +static errno_t ipa_check_ghost_members(struct tevent_req *req) +{ + struct ipa_get_ad_acct_state *state = tevent_req_data(req, + struct ipa_get_ad_acct_state); + errno_t ret; + struct tevent_req *subreq; + struct ldb_message_element *ghosts = NULL; + + + if (state->obj_msg == NULL) { + ret = get_object_from_cache(state, state->obj_dom, state->ar, + &state->obj_msg); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Object not found, ending request\n"); + return EOK; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_object_from_cache failed.\n"); + return ret; + } + } + + ghosts = ldb_msg_find_element(state->obj_msg, SYSDB_GHOST); + + if (ghosts != NULL) { + /* Resolve ghost members */ + subreq = ipa_resolve_user_list_send(state, state->ev, + state->ipa_ctx, + state->obj_dom->name, + ghosts); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_resolve_user_list_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_check_ghost_members_done, req); + return EAGAIN; + } + + return EOK; +} + +static void ipa_check_ghost_members_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + int ret; + + ret = ipa_resolve_user_list_recv(subreq, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_resolve_user_list request failed [%d]\n", + ret); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +static errno_t ipa_get_ad_apply_override_step(struct tevent_req *req) +{ + struct ipa_get_ad_acct_state *state = tevent_req_data(req, + struct ipa_get_ad_acct_state); + errno_t ret; + struct tevent_req *subreq; + const char *obj_name; + int entry_type; + size_t groups_count = 0; + struct ldb_message **groups = NULL; + const char *attrs[] = SYSDB_INITGR_ATTRS; + + if (state->override_attrs != NULL) { + /* We are in ipa-server-mode, so the view is the default view by + * definition. */ + ret = sysdb_apply_default_override(state->obj_dom, + state->override_attrs, + state->obj_msg->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_apply_default_override failed.\n"); + return ret; + } + } + + entry_type = (state->ar->entry_type & BE_REQ_TYPE_MASK); + if (entry_type != BE_REQ_INITGROUPS + && entry_type != BE_REQ_USER + && entry_type != BE_REQ_BY_SECID + && entry_type != BE_REQ_GROUP) { + tevent_req_done(req); + return EOK; + } + + /* expand ghost members, if any, to get group members with overrides + * right. */ + if (entry_type == BE_REQ_GROUP) { + ret = ipa_check_ghost_members(req); + if (ret == EOK) { + tevent_req_done(req); + return EOK; + } else if (ret == EAGAIN) { + return EOK; + } else { + DEBUG(SSSDBG_OP_FAILURE, "ipa_check_ghost_members failed.\n"); + return ret; + } + } + + /* Replace ID with name in search filter */ + if ((entry_type == BE_REQ_USER && state->ar->filter_type == BE_FILTER_IDNUM) + || (entry_type == BE_REQ_INITGROUPS + && state->ar->filter_type == BE_FILTER_SECID) + || entry_type == BE_REQ_BY_SECID) { + if (state->obj_msg == NULL) { + ret = get_object_from_cache(state, state->obj_dom, state->ar, + &state->obj_msg); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Object not found, ending request\n"); + tevent_req_done(req); + return EOK; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_object_from_cache failed.\n"); + return ret; + } + } + + obj_name = ldb_msg_find_attr_as_string(state->obj_msg, SYSDB_NAME, + NULL); + if (obj_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cached object has no name.\n"); + return EINVAL; + } + + state->ar->filter_value = talloc_strdup(state->ar, obj_name); + if (state->ar->filter_value == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + return ENOMEM; + } + state->ar->filter_type = BE_FILTER_NAME; + state->ar->entry_type = BE_REQ_USER; + } + + /* Lookup all groups the user is a member of which do not have ORIGINALAD + * attributes set, i.e. where overrides might not have been applied. */ + ret = sysdb_asq_search(state, state->obj_dom, state->obj_msg->dn, + "(&("SYSDB_GC")("SYSDB_GIDNUM"=*)" \ + "("SYSDB_POSIX"=TRUE)" \ + "(!("ORIGINALAD_PREFIX SYSDB_GIDNUM"=*))" \ + "(!("ORIGINALAD_PREFIX SYSDB_NAME"=*)))", + SYSDB_INITGR_ATTR, + attrs, &groups_count, &groups); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_groups_without_orig failed.\n"); + return ret; + } + + if (groups != NULL) { + subreq = ipa_initgr_get_overrides_send(state, state->ev, state->ipa_ctx, + state->obj_dom, groups_count, + groups, SYSDB_SID_STR); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_initgr_get_overrides_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_id_get_groups_overrides_done, req); + return EOK; + } + + ret = ipa_get_ad_ipa_membership_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_ipa_membership_step failed.\n"); + return ret; + } + + return EOK; +} + +static void ipa_id_get_groups_overrides_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + errno_t ret; + + ret = ipa_initgr_get_overrides_recv(subreq, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "IPA resolve user groups overrides failed [%d].\n", ret); + tevent_req_error(req, ret); + return; + } + + ret = ipa_get_ad_ipa_membership_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_ipa_membership_step failed.\n"); + tevent_req_error(req, ret); + return; + } + + return; +} + +static errno_t ipa_get_ad_ipa_membership_step(struct tevent_req *req) +{ + struct ipa_get_ad_acct_state *state = tevent_req_data(req, + struct ipa_get_ad_acct_state); + struct tevent_req *subreq; + + /* For initgroups request we have to check IPA group memberships of AD + * users. This has to be done for other user-request as well to make sure + * IPA related attributes are not overwritten. */ + subreq = ipa_get_ad_memberships_send(state, state->ev, state->ar, + state->ipa_ctx->server_mode, + state->obj_dom, + state->ipa_ctx->sdap_id_ctx, + state->ipa_ctx->server_mode->realm); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_memberships_send failed.\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_get_ad_acct_done, req); + + return EOK; +} + +static void +ipa_get_ad_acct_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_ad_acct_state *state = tevent_req_data(req, + struct ipa_get_ad_acct_state); + errno_t ret; + + ret = ipa_get_ad_memberships_recv(subreq, &state->dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "IPA external groups lookup failed: %d\n", + ret); + tevent_req_error(req, ret); + return; + + } + + tevent_req_done(req); +} + +static errno_t +ipa_get_ad_acct_recv(struct tevent_req *req, int *dp_error_out) +{ + struct ipa_get_ad_acct_state *state = tevent_req_data(req, + struct ipa_get_ad_acct_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ipa_srv_ad_acct_state { + struct tevent_context *ev; + struct ipa_id_ctx *ipa_ctx; + struct sysdb_attrs *override_attrs; + struct dp_id_data *ar; + + struct sss_domain_info *obj_dom; + struct be_ctx *be_ctx; + bool retry; + + int dp_error; +}; + +static int ipa_srv_ad_acct_lookup_step(struct tevent_req *req); +static void ipa_srv_ad_acct_lookup_done(struct tevent_req *subreq); +static void ipa_srv_ad_acct_retried(struct tevent_req *subreq); + +static struct tevent_req * +ipa_srv_ad_acct_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_id_ctx *ipa_ctx, + struct sysdb_attrs *override_attrs, + struct dp_id_data *ar) +{ + errno_t ret; + struct tevent_req *req; + struct ipa_srv_ad_acct_state *state; + + req = tevent_req_create(mem_ctx, &state, struct ipa_srv_ad_acct_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->ipa_ctx = ipa_ctx; + state->override_attrs = override_attrs; + state->ar = ar; + state->retry = true; + state->dp_error = DP_ERR_FATAL; + state->be_ctx = ipa_ctx->sdap_id_ctx->be; + + state->obj_dom = find_domain_by_name( + state->ipa_ctx->sdap_id_ctx->be->domain, + state->ar->domain, true); + if (state->obj_dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Domain not found\n"); + ret = ERR_DOMAIN_NOT_FOUND; + goto fail; + } + + ret = ipa_srv_ad_acct_lookup_step(req); + if (ret != EOK) { + goto fail; + } + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static int ipa_srv_ad_acct_lookup_step(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct ipa_srv_ad_acct_state *state = tevent_req_data(req, + struct ipa_srv_ad_acct_state); + + DEBUG(SSSDBG_TRACE_FUNC, "Looking up AD account\n"); + subreq = ipa_get_ad_acct_send(state, state->ev, state->ipa_ctx, + state->override_attrs, + state->ar); + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_srv_ad_acct_lookup_done, req); + + return EOK; +} + +static void ipa_srv_ad_acct_lookup_done(struct tevent_req *subreq) +{ + errno_t ret; + int dp_error = DP_ERR_FATAL; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_srv_ad_acct_state *state = tevent_req_data(req, + struct ipa_srv_ad_acct_state); + + ret = ipa_get_ad_acct_recv(subreq, &dp_error); + talloc_free(subreq); + if (ret == ERR_SUBDOM_INACTIVE && state->retry == true) { + + state->retry = false; + + DEBUG(SSSDBG_MINOR_FAILURE, + "Subdomain lookup failed, will try to reset subdomain.\n"); + subreq = ipa_server_trusted_dom_setup_send(state, state->ev, + state->be_ctx, + state->ipa_ctx, + state->obj_dom); + if (subreq == NULL) { + goto fail; + } + tevent_req_set_callback(subreq, ipa_srv_ad_acct_retried, req); + return; + } else if (ret != EOK) { + be_mark_dom_offline(state->obj_dom, state->be_ctx); + + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_*_acct request failed: [%d]: %s.\n", + ret, sss_strerror(ret)); + goto fail; + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + +fail: + state->dp_error = dp_error; + tevent_req_error(req, ret); +} + +static void ipa_srv_ad_acct_retried(struct tevent_req *subreq) +{ + errno_t ret; + struct ad_id_ctx *ad_id_ctx; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_srv_ad_acct_state *state = tevent_req_data(req, + struct ipa_srv_ad_acct_state); + + ret = ipa_server_trusted_dom_setup_recv(subreq); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to re-set subdomain [%d]: %s\n", ret, sss_strerror(ret)); + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Subdomain re-set, will retry lookup\n"); + ad_id_ctx = ipa_get_ad_id_ctx(state->ipa_ctx, state->obj_dom); + if (ad_id_ctx == NULL || ad_id_ctx->ad_options == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No AD ID ctx or no ID CTX options?\n"); + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, EINVAL); + return; + } + + ad_failover_reset(state->be_ctx, ad_id_ctx->ad_options->service); + + ret = ipa_srv_ad_acct_lookup_step(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to look up AD acct [%d]: %s\n", ret, sss_strerror(ret)); + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + return; + } +} + +static errno_t +ipa_srv_ad_acct_recv(struct tevent_req *req, int *dp_error_out) +{ + struct ipa_srv_ad_acct_state *state = tevent_req_data(req, + struct ipa_srv_ad_acct_state); + + if (dp_error_out) { + *dp_error_out = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} diff --git a/src/providers/ipa/ipa_subdomains_passkey.c b/src/providers/ipa/ipa_subdomains_passkey.c new file mode 100644 index 0000000..d5dd275 --- /dev/null +++ b/src/providers/ipa/ipa_subdomains_passkey.c @@ -0,0 +1,146 @@ +/* + SSSD + + IPA Subdomains Passkey Module + + Authors: + Justin Stephenson <jstephen@redhat.com> + + Copyright (C) 2022 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ldap/sdap_ops.h" +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_id.h" +#include "providers/ipa/ipa_opts.h" +#include "providers/ipa/ipa_config.h" +#include "providers/ipa/ipa_subdomains_passkey.h" +#include "db/sysdb_passkey_user_verification.h" + +#include <ctype.h> +#define IPA_PASSKEY_VERIFICATION "ipaRequireUserVerification" +#define IPA_PASSKEY_CONFIG_FILTER "cn=passkeyconfig" + +void ipa_subdomains_passkey_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_subdomains_passkey_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh) +{ + struct ipa_subdomains_passkey_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + const char *attrs[] = { IPA_PASSKEY_VERIFICATION, NULL }; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_subdomains_passkey_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->domain = sd_ctx->be_ctx->domain; + state->sdap_opts = sd_ctx->sdap_id_ctx->opts; + + subreq = ipa_get_config_send(state, ev, sh, sd_ctx->sdap_id_ctx->opts, + state->domain->name, attrs, IPA_PASSKEY_CONFIG_FILTER, NULL); + + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_subdomains_passkey_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +void ipa_subdomains_passkey_done(struct tevent_req *subreq) +{ + struct ipa_subdomains_passkey_state *state; + struct tevent_req *req; + struct sysdb_attrs *config; + const char *user_verification = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_subdomains_passkey_state); + + ret = ipa_get_config_recv(subreq, state, &config); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to get data from LDAP [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (config != NULL) { + ret = sysdb_attrs_get_string(config, IPA_PASSKEY_VERIFICATION, + &user_verification); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, "Retrieved [%s] from [%s] attribute.\n", + user_verification, IPA_PASSKEY_VERIFICATION); + } + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get passkey user verification " + "value [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } else if (ret == ENOENT) { + user_verification = NULL; + } + } + + ret = sysdb_domain_update_passkey_user_verification( + state->domain->sysdb, state->domain->name, + user_verification); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_domain_passkey_user_verification() [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t ipa_subdomains_passkey_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/providers/ipa/ipa_subdomains_passkey.h b/src/providers/ipa/ipa_subdomains_passkey.h new file mode 100644 index 0000000..ecbf239 --- /dev/null +++ b/src/providers/ipa/ipa_subdomains_passkey.h @@ -0,0 +1,45 @@ +/* + SSSD + + IPA Subdomains Passkey Module + + Authors: + Justin Stephenson + + Copyright (C) 2022 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef _IPA_SUBDOMAINS_PASSKEY_H_ +#define _IPA_SUBDOMAINS_PASSKEY_H_ + +#include "providers/backend.h" +#include "providers/ipa/ipa_common.h" +#include "config.h" + +struct ipa_subdomains_passkey_state { + struct sss_domain_info *domain; + struct sdap_options *sdap_opts; +}; + +struct tevent_req * +ipa_subdomains_passkey_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_subdomains_ctx *sd_ctx, + struct sdap_handle *sh); + +errno_t ipa_subdomains_passkey_recv(struct tevent_req *req); + +#endif /* _IPA_SUBDOMAINS_PASSKEY_H_ */ diff --git a/src/providers/ipa/ipa_subdomains_server.c b/src/providers/ipa/ipa_subdomains_server.c new file mode 100644 index 0000000..aaedf62 --- /dev/null +++ b/src/providers/ipa/ipa_subdomains_server.c @@ -0,0 +1,1215 @@ +/* + SSSD + + IPA Subdomains Module - server mode + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2015 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "providers/ldap/sdap_async.h" +#include "providers/ldap/sdap_idmap.h" +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_id.h" + +/* These constants are defined in MS-ADTS 6.1.6.7.1 + * https://msdn.microsoft.com/en-us/library/cc223768.aspx + */ +#define LSA_TRUST_DIRECTION_INBOUND 0x00000001 +#define LSA_TRUST_DIRECTION_OUTBOUND 0x00000002 +#define LSA_TRUST_DIRECTION_MASK (LSA_TRUST_DIRECTION_INBOUND | LSA_TRUST_DIRECTION_OUTBOUND) + +static char *forest_keytab(TALLOC_CTX *mem_ctx, const char *forest) +{ + return talloc_asprintf(mem_ctx, + "%s/%s.keytab", IPA_TRUST_KEYTAB_DIR, forest); +} + +static char *subdomain_trust_princ(TALLOC_CTX *mem_ctx, + const char *forest_realm, + struct sss_domain_info *sd) +{ + if (sd->parent->flat_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown flat name for parent %s\n", sd->parent->name); + return NULL; + } + + return talloc_asprintf(mem_ctx, "%s$@%s", + sd->parent->flat_name, forest_realm); +} + +static uint32_t default_direction(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb_ctx, + struct sysdb_attrs *attrs) +{ + struct ldb_dn *dn = NULL; + uint32_t direction; + + dn = ipa_subdom_ldb_dn(mem_ctx, ldb_ctx, attrs); + if (dn == NULL) { + /* Shouldn't happen, but let's try system keytab in this case */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot determine subdomain DN, falling back to two-way trust\n"); + return (LSA_TRUST_DIRECTION_INBOUND|LSA_TRUST_DIRECTION_OUTBOUND); + } + + if (ipa_subdom_is_member_dom(dn) == true) { + /* It's expected member domains do not have the direction */ + direction = 0; + } else { + /* Old server? Default to 2way trust */ + direction = (LSA_TRUST_DIRECTION_INBOUND|LSA_TRUST_DIRECTION_OUTBOUND); + } + + talloc_free(dn); + return direction; +} + +errno_t ipa_server_get_trust_direction(struct sysdb_attrs *sd, + struct ldb_context *ldb_ctx, + uint32_t *_direction) +{ + uint32_t ipa_trust_direction = 0; + uint32_t direction; + int ret; + + ret = sysdb_attrs_get_uint32_t(sd, IPA_TRUST_DIRECTION, + &ipa_trust_direction); + DEBUG(SSSDBG_TRACE_INTERNAL, + "Raw %s value: %d\n", IPA_TRUST_DIRECTION, ipa_trust_direction); + if (ret == ENOENT) { + direction = default_direction(sd, ldb_ctx, sd); + } else if (ret == EOK) { + /* Just store the AD value in SYSDB, we will check it while we're + * trying to use the trust */ + direction = ipa_trust_direction; + } else { + return ret; + } + + *_direction = direction; + return EOK; +} + +const char *ipa_trust_dir2str(uint32_t direction) +{ + if ((direction & LSA_TRUST_DIRECTION_OUTBOUND) + && (direction & LSA_TRUST_DIRECTION_INBOUND)) { + return "two-way trust"; + } else if (direction & LSA_TRUST_DIRECTION_OUTBOUND) { + return "one-way outbound: local domain is trusted by remote domain"; + } else if (direction & LSA_TRUST_DIRECTION_INBOUND) { + return "one-way inbound: local domain trusts the remote domain"; + } else if (direction == 0) { + return "not set"; + } + + return "unknown"; +} + +#ifndef IPA_GETKEYTAB_TIMEOUT +#define IPA_GETKEYTAB_TIMEOUT 5 +#endif /* IPA_GETKEYTAB_TIMEOUT */ + +static struct ad_options * +ipa_create_1way_trust_ctx(struct ipa_id_ctx *id_ctx, + struct be_ctx *be_ctx, + const char *subdom_conf_path, + const char *forest, + const char *forest_realm, + struct sss_domain_info *subdom) +{ + char *keytab; + char *principal; + struct ad_options *ad_options; + + keytab = forest_keytab(id_ctx, forest); + principal = subdomain_trust_princ(id_ctx, forest_realm, subdom); + if (keytab == NULL || principal == NULL) { + return NULL; + } + + ad_options = ad_create_1way_trust_options(id_ctx, + be_ctx->cdb, + subdom_conf_path, + be_ctx->provider, + subdom, + id_ctx->server_mode->hostname, + keytab, + principal); + if (ad_options == NULL) { + talloc_free(keytab); + talloc_free(principal); + return NULL; + } + + return ad_options; +} + +static struct ad_options *ipa_ad_options_new(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom) +{ + struct ad_options *ad_options = NULL; + uint32_t direction; + const char *forest; + const char *forest_realm; + char *subdom_conf_path; + int ret; + + /* Trusts are only established with forest roots */ + direction = subdom->forest_root->trust_direction; + forest_realm = subdom->forest_root->realm; + forest = subdom->forest_root->forest; + + subdom_conf_path = subdomain_create_conf_path(id_ctx, subdom); + if (subdom_conf_path == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "subdom_conf_path failed\n"); + return NULL; + } + + /* In both inbound and outbound trust cases we should be + * using trusted domain object in a trusted domain space, + * thus we always should be initializing principals/keytabs + * as if we are running one-way trust */ + if (direction & LSA_TRUST_DIRECTION_MASK) { + ad_options = ipa_create_1way_trust_ctx(id_ctx, be_ctx, + subdom_conf_path, forest, + forest_realm, subdom); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported trust direction!\n"); + ad_options = NULL; + } + + if (ad_options == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD options\n"); + talloc_free(subdom_conf_path); + return NULL; + } + + ret = ad_inherit_opts_if_needed(id_ctx->ipa_options->id->basic, + ad_options->id->basic, be_ctx->cdb, + subdom_conf_path, SDAP_SASL_MECH); + talloc_free(subdom_conf_path); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to inherit option [%s] to sub-domain [%s]. " + "This error is ignored but might cause issues or unexpected " + "behavior later on.\n", + id_ctx->ipa_options->id->basic[SDAP_SASL_MECH].opt_name, + subdom->name); + + return NULL; + } + + return ad_options; +} + + +static errno_t +ipa_ad_ctx_new(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom, + struct ad_id_ctx **_ad_id_ctx) +{ + struct ad_options *ad_options; + struct ad_id_ctx *ad_id_ctx; + const char *gc_service_name; + const char *service_name; + struct ad_srv_plugin_ctx *srv_ctx; + const char *ad_domain; + const char *ad_site_override; + const char *ad_servers; + const char *ad_backup_servers; + struct sdap_domain *sdom; + errno_t ret; + const char *extra_attrs; + bool use_kdcinfo = false; + size_t n_lookahead_primary = (size_t)-1; + size_t n_lookahead_backup = (size_t)-1; + + ad_domain = subdom->name; + DEBUG(SSSDBG_TRACE_LIBS, "Setting up AD subdomain %s\n", subdom->name); + + ad_options = ipa_ad_options_new(be_ctx, id_ctx, subdom); + if (ad_options == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD options\n"); + talloc_free(ad_options); + return ENOMEM; + } + + extra_attrs = dp_opt_get_string(id_ctx->sdap_id_ctx->opts->basic, + SDAP_USER_EXTRA_ATTRS); + if (extra_attrs != NULL) { + DEBUG(SSSDBG_TRACE_ALL, + "Setting extra attrs for subdomain [%s] to [%s].\n", ad_domain, + extra_attrs); + + ret = dp_opt_set_string(ad_options->id->basic, SDAP_USER_EXTRA_ATTRS, + extra_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "dp_opt_set_string failed.\n"); + talloc_free(ad_options); + return ret; + } + + ret = sdap_extend_map_with_list(ad_options->id, ad_options->id, + SDAP_USER_EXTRA_ATTRS, + ad_options->id->user_map, + SDAP_OPTS_USER, + &ad_options->id->user_map, + &ad_options->id->user_map_cnt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_extend_map_with_list failed.\n"); + talloc_free(ad_options); + return ret; + } + } else { + DEBUG(SSSDBG_TRACE_ALL, "No extra attrs set.\n"); + } + + gc_service_name = talloc_asprintf(ad_options, "sd_gc_%s", subdom->name); + if (gc_service_name == NULL) { + talloc_free(ad_options); + return ENOMEM; + } + + service_name = talloc_asprintf(ad_options, "sd_%s", subdom->name); + if (service_name == NULL) { + talloc_free(ad_options); + return ENOMEM; + } + + ad_servers = dp_opt_get_string(ad_options->basic, AD_SERVER); + ad_backup_servers = dp_opt_get_string(ad_options->basic, AD_BACKUP_SERVER); + + if (id_ctx->ipa_options != NULL && id_ctx->ipa_options->auth != NULL) { + use_kdcinfo = dp_opt_get_bool(id_ctx->ipa_options->auth, + KRB5_USE_KDCINFO); + sss_krb5_parse_lookahead( + dp_opt_get_string(id_ctx->ipa_options->auth, KRB5_KDCINFO_LOOKAHEAD), + &n_lookahead_primary, + &n_lookahead_backup); + } + + DEBUG(SSSDBG_TRACE_ALL, + "Init failover for [%s][%s] with use_kdcinfo [%s].\n", + subdom->name, subdom->realm, use_kdcinfo ? "true" : "false"); + + /* Set KRB5 realm to same as the one of IPA when IPA + * is able to attach PAC. For testing, use hardcoded. */ + /* Why? */ + ret = ad_failover_init(ad_options, be_ctx, ad_servers, ad_backup_servers, + subdom->realm, + service_name, gc_service_name, + subdom->name, use_kdcinfo, false, + n_lookahead_primary, n_lookahead_backup, + &ad_options->service); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD failover\n"); + talloc_free(ad_options); + return ret; + } + + ad_id_ctx = ad_id_ctx_init(ad_options, be_ctx); + if (ad_id_ctx == NULL) { + talloc_free(ad_options); + return ENOMEM; + } + ad_id_ctx->sdap_id_ctx->opts = ad_options->id; + ad_options->id_ctx = ad_id_ctx; + + ad_site_override = dp_opt_get_string(ad_options->basic, AD_SITE); + + /* use AD plugin */ + srv_ctx = ad_srv_plugin_ctx_init(be_ctx, be_ctx, be_ctx->be_res, + default_host_dbs, + ad_id_ctx->ad_options->id, + ad_id_ctx->ad_options, + id_ctx->server_mode->hostname, + ad_domain, + ad_site_override); + if (srv_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory?\n"); + return ENOMEM; + } + be_fo_set_srv_lookup_plugin(be_ctx, ad_srv_plugin_send, + ad_srv_plugin_recv, srv_ctx, "AD"); + + ret = sdap_domain_subdom_add(ad_id_ctx->sdap_id_ctx, + ad_id_ctx->sdap_id_ctx->opts->sdom, + subdom->parent); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize sdap domain\n"); + talloc_free(ad_options); + return ret; + } + + sdom = sdap_domain_get(ad_id_ctx->sdap_id_ctx->opts, subdom); + if (sdom == NULL) { + return EFAULT; + } + + ret = ad_set_search_bases(ad_options->id, sdom); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot initialize AD search bases\n"); + talloc_free(ad_options); + return ret; + } + + sdap_inherit_options(subdom->parent->sd_inherit, + id_ctx->sdap_id_ctx->opts, + ad_id_ctx->sdap_id_ctx->opts); + + ret = sdap_id_setup_tasks(be_ctx, + ad_id_ctx->sdap_id_ctx, + sdom, + ldap_id_enumeration_send, + ldap_id_enumeration_recv, + ad_id_ctx->sdap_id_ctx); + if (ret != EOK) { + talloc_free(ad_options); + return ret; + } + + sdom->pvt = ad_id_ctx; + + /* Set up the ID mapping object */ + ad_id_ctx->sdap_id_ctx->opts->idmap_ctx = + id_ctx->sdap_id_ctx->opts->idmap_ctx; + + /* Set up the certificate mapping context */ + ad_id_ctx->sdap_id_ctx->opts->sdap_certmap_ctx = + id_ctx->sdap_id_ctx->opts->sdap_certmap_ctx; + + *_ad_id_ctx = ad_id_ctx; + return EOK; +} + +struct ipa_getkeytab_state { + int child_status; + struct sss_child_ctx_old *child_ctx; + struct tevent_timer *timeout_handler; +}; + +static void ipa_getkeytab_exec(const char *ccache, + const char *server, + const char *principal, + const char *keytab_path); +static void ipa_getkeytab_done(int child_status, + struct tevent_signal *sige, + void *pvt); +static void ipa_getkeytab_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt); + +static struct tevent_req *ipa_getkeytab_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *ccache, + const char *server, + const char *principal, + const char *keytab) + + +{ + errno_t ret; + struct tevent_req *req = NULL; + struct ipa_getkeytab_state *state; + pid_t child_pid; + struct timeval tv; + + req = tevent_req_create(mem_ctx, &state, struct ipa_getkeytab_state); + if (req == NULL) { + return NULL; + } + state->child_status = EFAULT; + + if (server == NULL || principal == NULL || keytab == NULL) { + ret = EINVAL; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Retrieving keytab for %s from %s into %s using ccache %s\n", + principal, server, keytab, ccache); + + child_pid = fork(); + if (child_pid == 0) { /* child */ + ipa_getkeytab_exec(ccache, server, principal, keytab); + } else if (child_pid > 0) { /* parent */ + /* Set up SIGCHLD handler */ + ret = child_handler_setup(ev, child_pid, ipa_getkeytab_done, req, + &state->child_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_IPA_GETKEYTAB_FAILED; + goto done; + } + + /* Set up timeout handler */ + tv = tevent_timeval_current_ofs(IPA_GETKEYTAB_TIMEOUT, 0); + state->timeout_handler = tevent_add_timer(ev, req, tv, + ipa_getkeytab_timeout, req); + if(state->timeout_handler == NULL) { + ret = ERR_IPA_GETKEYTAB_FAILED; + goto done; + } + + /* Now either wait for the timeout to fire or the child + * to finish + */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fork failed [%d][%s].\n", ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void ipa_getkeytab_exec(const char *ccache, + const char *server, + const char *principal, + const char *keytab_path) +{ + errno_t ret; + int debug_fd; + const char *gkt_env[3] = { NULL, "_SSS_LOOPS=NO", NULL }; + + if (debug_level >= SSSDBG_TRACE_LIBS) { + debug_fd = get_fd_from_debug_file(); + ret = dup2(debug_fd, STDERR_FILENO); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "dup2 failed [%d][%s].\n", ret, sss_strerror(ret)); + /* stderr is not fatal */ + } + } + + gkt_env[0] = talloc_asprintf(NULL, "KRB5CCNAME=%s", ccache); + if (gkt_env[0] == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to format KRB5CCNAME\n"); + exit(1); + } + + /* ipa-getkeytab cannot add keys to an empty file, let's unlink it and only + * use the filename */ + ret = unlink(keytab_path); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to unlink the temporary ccname [%d][%s]\n", + ret, sss_strerror(ret)); + exit(1); + } + + errno = 0; + ret = execle(IPA_GETKEYTAB_PATH, IPA_GETKEYTAB_PATH, + "-r", "-s", server, "-p", principal, "-k", keytab_path, NULL, + gkt_env); + + DEBUG(SSSDBG_FATAL_FAILURE, + "execle returned %d, this shouldn't happen!\n", ret); + + /* The child should never end up here */ + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "execle failed [%d][%s].\n", ret, sss_strerror(ret)); + exit(1); +} + +static void ipa_getkeytab_done(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct ipa_getkeytab_state *state = + tevent_req_data(req, struct ipa_getkeytab_state); + + state->child_status = child_status; + + if (WIFEXITED(child_status) && WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa-getkeytab failed with status [%d]\n", child_status); + tevent_req_error(req, ERR_IPA_GETKEYTAB_FAILED); + return; + } + + if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + "ipa-getkeytab was terminated by signal [%d]\n", + WTERMSIG(child_status)); + tevent_req_error(req, ERR_IPA_GETKEYTAB_FAILED); + return; + } + + tevent_req_done(req); +} + +static void ipa_getkeytab_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = + talloc_get_type(pvt, struct tevent_req); + struct ipa_getkeytab_state *state = + tevent_req_data(req, struct ipa_getkeytab_state); + + DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for retrieving keytab from IPA server\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + state->child_status = ETIMEDOUT; + tevent_req_error(req, ERR_IPA_GETKEYTAB_FAILED); +} + +static errno_t ipa_getkeytab_recv(struct tevent_req *req, int *child_status) +{ + struct ipa_getkeytab_state *state = + tevent_req_data(req, struct ipa_getkeytab_state); + + DEBUG(SSSDBG_TRACE_INTERNAL, + "ipa-getkeytab status %d\n", state->child_status); + if (child_status) { + *child_status = state->child_status; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static errno_t ipa_check_keytab(const char *keytab, + uid_t kt_owner_uid, + gid_t kt_owner_gid) +{ + errno_t ret; + + ret = check_file(keytab, getuid(), getgid(), S_IFREG|0600, 0, NULL, false); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, "Keytab %s is not present\n", keytab); + goto done; + } else if (ret != EOK) { + if (kt_owner_uid) { + ret = check_file(keytab, kt_owner_uid, kt_owner_gid, + S_IFREG|0600, 0, NULL, false); + } + + if (ret != EOK) { + if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to check for %s\n", keytab); + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Keytab %s is not present\n", keytab); + } + } + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "keytab %s already exists\n", keytab); + ret = EOK; +done: + return ret; +} + +struct ipa_server_trusted_dom_setup_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct ipa_id_ctx *id_ctx; + struct sss_domain_info *subdom; + + uint32_t direction; + const char *forest; + const char *keytab; + char *new_keytab; + const char *principal; + const char *forest_realm; + const char *ccache; +}; + +static errno_t ipa_server_trusted_dom_setup_1way(struct tevent_req *req); +static void ipa_server_trust_1way_kt_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_server_trusted_dom_setup_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom) +{ + struct tevent_req *req = NULL; + struct ipa_server_trusted_dom_setup_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_server_trusted_dom_setup_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->be_ctx = be_ctx; + state->id_ctx = id_ctx; + state->subdom = subdom; + + /* Trusts are only established with forest roots */ + if (subdom->forest_root == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Subdomain %s has no forest root?\n", subdom->name); + ret = ERR_TRUST_FOREST_UNKNOWN; + goto immediate; + } + + state->direction = subdom->forest_root->trust_direction; + state->forest = subdom->forest_root->forest; + state->forest_realm = subdom->forest_root->realm; + state->ccache = talloc_asprintf(state, "%s/ccache_%s", + DB_PATH, subdom->parent->realm); + if (state->ccache == NULL) { + ret = ENOMEM; + goto immediate; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Trust direction of subdom %s from forest %s is: %s\n", + subdom->name, state->forest, + ipa_trust_dir2str(state->direction)); + + /* For both inbound and outbound trusts use a special keytab + * as this allows us to reuse the same logic in FreeIPA for + * both Microsoft AD and Samba AD */ + if (state->direction & LSA_TRUST_DIRECTION_MASK) { + /* Need special keytab */ + ret = ipa_server_trusted_dom_setup_1way(req); + if (ret == EAGAIN) { + /* In progress.. */ + return req; + } else if (ret == EOK) { + /* Keytab available, shortcut */ + ret = EOK; + goto immediate; + } + } else { + /* Even unset is an error at this point */ + DEBUG(SSSDBG_OP_FAILURE, + "Subdomain %s has trust direction %d\n", + subdom->name, subdom->trust_direction); + ret = ERR_TRUST_NOT_SUPPORTED; + } + +immediate: + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not add trusted subdomain %s from forest %s\n", + subdom->name, state->forest); + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ipa_server_trusted_dom_setup_1way(struct tevent_req *req) +{ + errno_t ret; + struct tevent_req *subreq = NULL; + struct ipa_server_trusted_dom_setup_state *state = + tevent_req_data(req, struct ipa_server_trusted_dom_setup_state); + const char *hostname; + + state->keytab = forest_keytab(state, state->forest); + if (state->keytab == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot set up ipa_get_keytab\n"); + return EIO; + } + + state->new_keytab = talloc_asprintf(state, "%sXXXXXX", state->keytab); + if (state->new_keytab == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot set up ipa_get_keytab. talloc_asprintf() failed\n"); + return ENOMEM; + } + + ret = sss_unique_filename(state, state->new_keytab); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create temporary keytab name\n"); + return ret; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Will re-fetch keytab for %s\n", state->subdom->name); + + hostname = dp_opt_get_string(state->id_ctx->ipa_options->basic, + IPA_HOSTNAME); + + state->principal = subdomain_trust_princ(state, + state->forest_realm, + state->subdom); + if (state->principal == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot set up ipa_get_keytab\n"); + return EIO; + } + + subreq = ipa_getkeytab_send(state->be_ctx, state->be_ctx->ev, + state->ccache, + hostname, + state->principal, + state->new_keytab); + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_server_trust_1way_kt_done, req); + return EAGAIN; +} + +static void ipa_server_trust_1way_kt_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_server_trusted_dom_setup_state *state = + tevent_req_data(req, struct ipa_server_trusted_dom_setup_state); + + ret = ipa_getkeytab_recv(subreq, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + /* Do not fail here, but try to check and use the previous keytab, + * if any */ + DEBUG(SSSDBG_MINOR_FAILURE, "ipa_getkeytab_recv failed: %d\n", ret); + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Keytab successfully retrieved to %s\n", state->new_keytab); + } + + ret = ipa_check_keytab(state->new_keytab, + state->id_ctx->server_mode->kt_owner_uid, + state->id_ctx->server_mode->kt_owner_gid); + if (ret == EOK) { + ret = rename(state->new_keytab, state->keytab); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "rename failed [%d][%s].\n", ret, strerror(ret)); + tevent_req_error(req, ret); + return; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Keytab renamed to %s\n", state->keytab); + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Trying to recover and use the previous keytab, if available\n"); + ret = ipa_check_keytab(state->keytab, + state->id_ctx->server_mode->kt_owner_uid, + state->id_ctx->server_mode->kt_owner_gid); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "The previous keytab %s contains the expected principal\n", + state->keytab); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot use the old keytab: %d\n", ret); + /* Nothing we can do now */ + tevent_req_error(req, ret); + return; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Keytab %s contains the expected principals\n", state->new_keytab); + + DEBUG(SSSDBG_TRACE_FUNC, + "Established trust context for %s\n", state->subdom->name); + tevent_req_done(req); +} + +errno_t ipa_server_trusted_dom_setup_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct ipa_server_create_trusts_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct ipa_id_ctx *id_ctx; + struct sss_domain_info *domiter; +}; + +static errno_t ipa_server_create_trusts_step(struct tevent_req *req); +static errno_t ipa_server_create_trusts_ctx(struct tevent_req *req); +static void ipa_server_create_trusts_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_server_create_trusts_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *parent) +{ + struct tevent_req *req = NULL; + struct ipa_server_create_trusts_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_server_create_trusts_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->be_ctx = be_ctx; + state->id_ctx = id_ctx; + state->domiter = parent; + + ret = ipa_server_create_trusts_step(req); + if (ret != EAGAIN) { + goto immediate; + } + + return req; + +immediate: + if (ret != EOK) { + tevent_req_error(req, ret); + } else { + tevent_req_done(req); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ipa_server_create_trusts_step(struct tevent_req *req) +{ + struct tevent_req *subreq = NULL; + struct ipa_ad_server_ctx *trust_iter; + struct ipa_ad_server_ctx *trust_i; + struct ipa_server_create_trusts_state *state = NULL; + + state = tevent_req_data(req, struct ipa_server_create_trusts_state); + + for (state->domiter = get_next_domain(state->domiter, SSS_GND_DESCEND); + state->domiter && IS_SUBDOMAIN(state->domiter); + state->domiter = get_next_domain(state->domiter, 0)) { + + /* Check if we already have an ID context for this subdomain */ + DLIST_FOR_EACH(trust_iter, state->id_ctx->server_mode->trusts) { + if (trust_iter->dom == state->domiter) { + break; + } + } + + /* Newly detected trust */ + if (trust_iter == NULL) { + subreq = ipa_server_trusted_dom_setup_send(state, + state->ev, + state->be_ctx, + state->id_ctx, + state->domiter); + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, ipa_server_create_trusts_done, req); + return EAGAIN; + } + } + + /* Refresh all sdap_dom lists in all ipa_ad_server_ctx contexts */ + DLIST_FOR_EACH(trust_iter, state->id_ctx->server_mode->trusts) { + struct sdap_domain *sdom_a; + + sdom_a = sdap_domain_get(trust_iter->ad_id_ctx->sdap_id_ctx->opts, + trust_iter->dom); + if (sdom_a == NULL) { + continue; + } + + DLIST_FOR_EACH(trust_i, state->id_ctx->server_mode->trusts) { + struct sdap_domain *sdom_b; + + if (strcmp(trust_iter->dom->name, trust_i->dom->name) == 0) { + continue; + } + + sdom_b = sdap_domain_get(trust_i->ad_id_ctx->sdap_id_ctx->opts, + sdom_a->dom); + if (sdom_b == NULL) { + continue; + } + + /* Replace basedn and search bases from sdom_b with values + * from sdom_a */ + sdap_domain_copy_search_bases(sdom_b, sdom_a); + } + } + + return EOK; +} + +static void ipa_server_create_trusts_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + ret = ipa_server_trusted_dom_setup_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = ipa_server_create_trusts_ctx(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = ipa_server_create_trusts_step(req); + if (ret == EOK) { + tevent_req_done(req); + return; + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + + /* Will cycle back */ +} + +static errno_t ipa_server_create_trusts_ctx(struct tevent_req *req) +{ + struct ipa_ad_server_ctx *trust_ctx; + struct ad_id_ctx *ad_id_ctx; + errno_t ret; + struct ipa_server_create_trusts_state *state = NULL; + + state = tevent_req_data(req, struct ipa_server_create_trusts_state); + + ret = ipa_ad_ctx_new(state->be_ctx, state->id_ctx, state->domiter, &ad_id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot create ad_id_ctx for subdomain %s\n", state->domiter->name); + return ret; + } + + trust_ctx = talloc(state->id_ctx->server_mode, struct ipa_ad_server_ctx); + if (trust_ctx == NULL) { + return ENOMEM; + } + trust_ctx->dom = state->domiter; + trust_ctx->ad_id_ctx = ad_id_ctx; + + DLIST_ADD(state->id_ctx->server_mode->trusts, trust_ctx); + return EOK; +} + +errno_t ipa_server_create_trusts_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +void ipa_ad_subdom_remove(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *subdom) +{ + struct ipa_ad_server_ctx *iter; + struct sdap_domain *sdom; + + if (dp_opt_get_bool(id_ctx->ipa_options->basic, + IPA_SERVER_MODE) == false) { + return; + } + + DLIST_FOR_EACH(iter, id_ctx->server_mode->trusts) { + if (iter->dom == subdom) break; + } + + if (iter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No IPA-AD context for subdomain %s\n", + subdom->name); + return; + } + + sdom = sdap_domain_get(iter->ad_id_ctx->sdap_id_ctx->opts, subdom); + if (sdom == NULL) return; + + sdap_domain_remove(iter->ad_id_ctx->sdap_id_ctx->opts, subdom); + DLIST_REMOVE(id_ctx->server_mode->trusts, iter); + + /* terminate all requests for this subdomain so we can free it */ + dp_terminate_domain_requests(be_ctx->provider, subdom->name); + talloc_zfree(sdom); +} + +struct ipa_ad_subdom_reinit_state { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct ipa_id_ctx *id_ctx; + struct sss_domain_info *parent; +}; + +static void create_trusts_at_startup_done(struct tevent_req *req) +{ + errno_t ret; + + ret = ipa_server_create_trusts_recv(req); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ipa_server_create_trusts_send request failed [%d]: %s\n", + ret, sss_strerror(ret)); + } +} + +static void create_trusts_at_startup(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt) +{ + struct tevent_req *req; + struct ipa_ad_subdom_reinit_state *state; + + state = talloc_get_type(pvt, struct ipa_ad_subdom_reinit_state); + + req = ipa_server_create_trusts_send(state, state->ev, state->be_ctx, + state->id_ctx, state->parent); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_server_create_trusts_send failed.\n"); + talloc_free(state); + return; + } + + tevent_req_set_callback(req, create_trusts_at_startup_done, state); + return; +} + +static errno_t ipa_ad_subdom_reinit(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct sss_domain_info *parent) +{ + struct tevent_immediate *imm; + struct ipa_ad_subdom_reinit_state *state; + + state = talloc(mem_ctx, struct ipa_ad_subdom_reinit_state); + if (state == NULL) { + return ENOMEM; + } + state->ev = ev; + state->be_ctx = be_ctx; + state->id_ctx = id_ctx; + state->parent = parent; + + if (dp_opt_get_bool(id_ctx->ipa_options->basic, + IPA_SERVER_MODE) == false) { + return EOK; + } + + imm = tevent_create_immediate(mem_ctx); + if (imm == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_create_immediate failed.\n"); + talloc_free(state); + return ENOMEM; + } + + tevent_schedule_immediate(imm, ev, create_trusts_at_startup, state); + return EOK; +} + +int ipa_ad_subdom_init(struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx) +{ + char *realm; + char *hostname; + errno_t ret; + + if (dp_opt_get_bool(id_ctx->ipa_options->basic, + IPA_SERVER_MODE) == false) { + return EOK; + } + + /* The IPA code relies on the default FQDN format to unparse user + * names. Warn loudly if the full_name_format was customized on the + * IPA server + */ + if ((strcmp(be_ctx->domain->names->fq_fmt, + CONFDB_DEFAULT_FULL_NAME_FORMAT) != 0) + && (strcmp(be_ctx->domain->names->fq_fmt, + CONFDB_DEFAULT_FULL_NAME_FORMAT_INTERNAL) != 0)) { + DEBUG(SSSDBG_FATAL_FAILURE, "%s is set to a non-default value [%s] " \ + "lookups of subdomain users will likely fail!\n", + CONFDB_FULL_NAME_FORMAT, be_ctx->domain->names->fq_fmt); + sss_log(SSS_LOG_ERR, "%s is set to a non-default value [%s] " \ + "lookups of subdomain users will likely fail!\n", + CONFDB_FULL_NAME_FORMAT, be_ctx->domain->names->fq_fmt); + /* Attempt to continue */ + } + + realm = dp_opt_get_string(id_ctx->ipa_options->basic, IPA_KRB5_REALM); + if (realm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No Kerberos realm for IPA?\n"); + return EINVAL; + } + + hostname = dp_opt_get_string(id_ctx->ipa_options->basic, IPA_HOSTNAME); + if (hostname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No host name for IPA?\n"); + return EINVAL; + } + + id_ctx->server_mode = talloc_zero(id_ctx, struct ipa_server_mode_ctx); + if (id_ctx->server_mode == NULL) { + return ENOMEM; + } + id_ctx->server_mode->realm = realm; + id_ctx->server_mode->hostname = hostname; + id_ctx->server_mode->trusts = NULL; + id_ctx->server_mode->ext_groups = NULL; + id_ctx->server_mode->kt_owner_uid = 0; + id_ctx->server_mode->kt_owner_gid = 0; + + if (getuid() == 0) { + /* We need to handle keytabs created by IPA oddjob script gracefully + * even if we're running as root and IPA creates them as the SSSD user + */ + ret = sss_user_by_name_or_uid(SSSD_USER, + &id_ctx->server_mode->kt_owner_uid, + &id_ctx->server_mode->kt_owner_gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to get ID of %s\n", SSSD_USER); + } + } + + ret = ipa_ad_subdom_reinit(be_ctx, be_ctx->ev, + be_ctx, id_ctx, be_ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_ad_subdom_refresh failed.\n"); + return ret; + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_subdomains_utils.c b/src/providers/ipa/ipa_subdomains_utils.c new file mode 100644 index 0000000..27fc0a4 --- /dev/null +++ b/src/providers/ipa/ipa_subdomains_utils.c @@ -0,0 +1,100 @@ +/* + SSSD + + IPA Subdomains Module - utilities + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2015 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "providers/ipa/ipa_subdomains.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_id.h" + +struct ldb_dn *ipa_subdom_ldb_dn(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb_ctx, + struct sysdb_attrs *attrs) +{ + int ret; + const char *orig_dn; + struct ldb_dn *dn = NULL; + + if (attrs == NULL || ldb_ctx == NULL) { + return NULL; + } + + ret = sysdb_attrs_get_string(attrs, SYSDB_ORIG_DN, &orig_dn); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_get_string failed: %d\n", ret); + return NULL; + } + + dn = ldb_dn_new(mem_ctx, ldb_ctx, orig_dn); + if (dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "ldb_dn_new failed.\n"); + return NULL; + } + + if (!ldb_dn_validate(dn)) { + DEBUG(SSSDBG_OP_FAILURE, "Original DN [%s] is not a valid DN.\n", + orig_dn); + talloc_free(dn); + return NULL; + } + + return dn; +} + +bool ipa_subdom_is_member_dom(struct ldb_dn *dn) +{ + const struct ldb_val *val; + + if (dn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Wrong input!\n"); + return false; + } + + if (ldb_dn_get_comp_num(dn) < 5) { + /* We are only interested in the member domain objects. In IPA the + * forest root object is stored as e.g. + * cn=AD.DOM,cn=ad,cn=trusts,dc=example,dc=com. Member domains in the + * forest are children of the forest root object e.g. + * cn=SUB.AD.DOM,cn=AD.DOM,cn=ad,cn=trusts,dc=example,dc=com. Since + * the forest name is not stored in the member objects we derive it + * from the RDN of the forest root object. */ + DEBUG(SSSDBG_TRACE_FUNC, + "DN too short, not a member domain\n"); + return false; + } + + val = ldb_dn_get_component_val(dn, 3); + if (strncasecmp("trusts", (const char *) val->data, val->length) != 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "4th component is not 'trust', not a member domain\n"); + return false; + } + + val = ldb_dn_get_component_val(dn, 2); + if (strncasecmp("ad", (const char *) val->data, val->length) != 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "3rd component is not 'ad', not a member domain\n"); + return false; + } + + return true; +} diff --git a/src/providers/ipa/ipa_sudo.c b/src/providers/ipa/ipa_sudo.c new file mode 100644 index 0000000..32ff1ce --- /dev/null +++ b/src/providers/ipa/ipa_sudo.c @@ -0,0 +1,337 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "providers/ipa/ipa_opts.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ldap/sdap_sudo.h" +#include "providers/ldap/ldap_opts.h" +#include "providers/ipa/ipa_sudo.h" +#include "db/sysdb_sudo.h" + +struct ipa_sudo_handler_state { + uint32_t type; + struct dp_reply_std reply; + struct ipa_sudo_ctx *sudo_ctx; +}; + +static void ipa_sudo_handler_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_sudo_handler_send(TALLOC_CTX *mem_ctx, + struct ipa_sudo_ctx *sudo_ctx, + struct dp_sudo_data *data, + struct dp_req_params *params) +{ + struct ipa_sudo_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ipa_sudo_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->type = data->type; + state->sudo_ctx = sudo_ctx; + + switch (data->type) { + case BE_REQ_SUDO_FULL: + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a full refresh of sudo rules\n"); + subreq = ipa_sudo_full_refresh_send(state, params->ev, sudo_ctx); + break; + case BE_REQ_SUDO_RULES: + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a refresh of specific sudo rules\n"); + subreq = ipa_sudo_rules_refresh_send(state, params->ev, sudo_ctx, + data->rules); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request type: %d\n", data->type); + ret = EINVAL; + goto immediately; + } + + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send request: %d\n", data->type); + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_sudo_handler_done, req); + + return req; + +immediately: + dp_reply_std_set(&state->reply, DP_ERR_DECIDE, ret, NULL); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void ipa_sudo_handler_done(struct tevent_req *subreq) +{ + struct ipa_sudo_handler_state *state; + struct tevent_req *req; + int dp_error; + bool deleted; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_handler_state); + + switch (state->type) { + case BE_REQ_SUDO_FULL: + ret = ipa_sudo_full_refresh_recv(subreq, &dp_error); + talloc_zfree(subreq); + + /* Postpone the periodic task since the refresh was just finished + * per user request. */ + if (ret == EOK && dp_error == DP_ERR_OK) { + be_ptask_postpone(state->sudo_ctx->full_refresh); + } + break; + case BE_REQ_SUDO_RULES: + ret = ipa_sudo_rules_refresh_recv(subreq, &dp_error, &deleted); + talloc_zfree(subreq); + if (ret == EOK && deleted == true) { + ret = ENOENT; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request type: %d\n", state->type); + dp_error = DP_ERR_FATAL; + ret = ERR_INTERNAL; + break; + } + + /* TODO For backward compatibility we always return EOK to DP now. */ + dp_reply_std_set(&state->reply, dp_error, ret, NULL); + tevent_req_done(req); +} + +static errno_t +ipa_sudo_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct ipa_sudo_handler_state *state = NULL; + + state = tevent_req_data(req, struct ipa_sudo_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} + +enum sudo_schema { + SUDO_SCHEMA_IPA, + SUDO_SCHEMA_LDAP +}; + +static errno_t +ipa_sudo_choose_schema(struct dp_option *ipa_opts, + struct dp_option *sdap_opts, + enum sudo_schema *_schema) +{ + TALLOC_CTX *tmp_ctx; + char *ipa_search_base; + char *search_base; + char *basedn; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = domain_to_basedn(tmp_ctx, dp_opt_get_string(ipa_opts, + IPA_KRB5_REALM), &basedn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to obtain basedn\n"); + goto done; + } + + ipa_search_base = talloc_asprintf(tmp_ctx, "cn=sudo,%s", basedn); + if (ipa_search_base == NULL) { + ret = ENOMEM; + goto done; + } + + search_base = dp_opt_get_string(sdap_opts, SDAP_SUDO_SEARCH_BASE); + if (search_base == NULL) { + ret = dp_opt_set_string(sdap_opts, SDAP_SUDO_SEARCH_BASE, + ipa_search_base); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n", + sdap_opts[SDAP_SUDO_SEARCH_BASE].opt_name, ipa_search_base); + + search_base = ipa_search_base; + } + + /* Use IPA schema only if search base is cn=sudo,$dc. */ + if (strcmp(ipa_search_base, search_base) == 0) { + *_schema = SUDO_SCHEMA_IPA; + } else { + *_schema = SUDO_SCHEMA_LDAP; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int +ipa_sudo_init_ipa_schema(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_method *dp_methods) +{ + struct ipa_sudo_ctx *sudo_ctx; + errno_t ret; + + sudo_ctx = talloc_zero(be_ctx, struct ipa_sudo_ctx); + if (sudo_ctx == NULL) { + return ENOMEM; + } + + sudo_ctx->id_ctx = id_ctx->sdap_id_ctx; + sudo_ctx->ipa_opts = id_ctx->ipa_options; + sudo_ctx->sdap_opts = id_ctx->sdap_id_ctx->opts; + + ret = sdap_get_map(sudo_ctx, be_ctx->cdb, be_ctx->conf_path, + ipa_sudorule_map, IPA_OPTS_SUDORULE, + &sudo_ctx->sudorule_map); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse attribute map (rule) " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = sdap_get_map(sudo_ctx, be_ctx->cdb, be_ctx->conf_path, + ipa_sudocmdgroup_map, IPA_OPTS_SUDOCMDGROUP, + &sudo_ctx->sudocmdgroup_map); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse attribute map (cmdgroup) " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = sdap_get_map(sudo_ctx, be_ctx->cdb, be_ctx->conf_path, + ipa_sudocmd_map, IPA_OPTS_SUDOCMD, + &sudo_ctx->sudocmd_map); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse attribute map (cmd) " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_int(be_ctx->cdb, CONFDB_SUDO_CONF_ENTRY, + CONFDB_SUDO_THRESHOLD, CONFDB_DEFAULT_SUDO_THRESHOLD, + &sudo_ctx->sudocmd_threshold); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not get sudo threshold\n"); + goto done; + } + + ret = sdap_parse_search_base(sudo_ctx, + sysdb_ctx_get_ldb(be_ctx->domain->sysdb), + sudo_ctx->sdap_opts->basic, + SDAP_SUDO_SEARCH_BASE, + &sudo_ctx->sudo_sb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not parse sudo search base\n"); + goto done; + } + + ret = ipa_sudo_ptask_setup(be_ctx, sudo_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup periodic tasks " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + dp_set_method(dp_methods, DPM_SUDO_HANDLER, + ipa_sudo_handler_send, ipa_sudo_handler_recv, sudo_ctx, + struct ipa_sudo_ctx, struct dp_sudo_data, struct dp_reply_std); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(sudo_ctx); + } + + return ret; +} + +int ipa_sudo_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct ipa_id_ctx *id_ctx, + struct dp_method *dp_methods) +{ + enum sudo_schema schema; + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Initializing IPA sudo back end\n"); + + ret = ipa_sudo_choose_schema(id_ctx->ipa_options->basic, + id_ctx->ipa_options->id->basic, + &schema); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to choose sudo schema [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + switch (schema) { + case SUDO_SCHEMA_IPA: + DEBUG(SSSDBG_TRACE_FUNC, "Using IPA schema for sudo\n"); + ret = ipa_sudo_init_ipa_schema(mem_ctx, be_ctx, id_ctx, dp_methods); + break; + case SUDO_SCHEMA_LDAP: + DEBUG(SSSDBG_TRACE_FUNC, "Using LDAP schema for sudo\n"); + ret = sdap_sudo_init(mem_ctx, + be_ctx, + id_ctx->sdap_id_ctx, + native_sudorule_map, + dp_methods); + break; + } + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize sudo provider" + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_sudo.h b/src/providers/ipa/ipa_sudo.h new file mode 100644 index 0000000..026fc29 --- /dev/null +++ b/src/providers/ipa/ipa_sudo.h @@ -0,0 +1,134 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef _IPA_SUDO_H_ +#define _IPA_SUDO_H_ + +#include "providers/ipa/ipa_common.h" + +struct ipa_sudo_ctx { + struct sdap_id_ctx *id_ctx; + struct ipa_options *ipa_opts; + struct sdap_options *sdap_opts; + struct be_ptask *full_refresh; + struct be_ptask *smart_refresh; + + /* sudo */ + struct sdap_attr_map *sudocmdgroup_map; + struct sdap_attr_map *sudorule_map; + struct sdap_attr_map *sudocmd_map; + struct sdap_search_base **sudo_sb; + int sudocmd_threshold; +}; + +errno_t +ipa_sudo_ptask_setup(struct be_ctx *be_ctx, struct ipa_sudo_ctx *sudo_ctx); + +struct tevent_req * +ipa_sudo_full_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_sudo_ctx *sudo_ctx); + +int +ipa_sudo_full_refresh_recv(struct tevent_req *req, + int *dp_error); + +int +ipa_sudo_rules_refresh_recv(struct tevent_req *req, + int *dp_error, + bool *deleted); + +struct tevent_req * +ipa_sudo_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_sudo_ctx *sudo_ctx, + const char *cmdgroups_filter, + const char *search_filter, + const char *delete_filter, + bool update_usn); + +struct tevent_req * +ipa_sudo_rules_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_sudo_ctx *sudo_ctx, + const char **rules); + +errno_t +ipa_sudo_refresh_recv(struct tevent_req *req, + int *dp_error, + size_t *_num_rules); + +struct ipa_sudo_conv; + +struct ipa_sudo_conv * +ipa_sudo_conv_init(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + struct sdap_attr_map *map_rule, + struct sdap_attr_map *map_cmdgroup, + struct sdap_attr_map *map_cmd, + struct sdap_attr_map *map_user, + struct sdap_attr_map *map_group, + struct sdap_attr_map *map_host, + struct sdap_attr_map *map_hostgroup); + +errno_t +ipa_sudo_conv_rules(struct ipa_sudo_conv *conv, + struct sysdb_attrs **rules, + size_t num_rules); + +errno_t +ipa_sudo_conv_cmdgroups(struct ipa_sudo_conv *conv, + struct sysdb_attrs **cmdgroups, + size_t num_cmdgroups); + +errno_t +ipa_sudo_conv_cmds(struct ipa_sudo_conv *conv, + struct sysdb_attrs **cmds, + size_t num_cmds); + +bool +ipa_sudo_conv_has_cmdgroups(struct ipa_sudo_conv *conv); + +bool +ipa_sudo_conv_has_cmds(struct ipa_sudo_conv *conv); + +bool +ipa_sudo_cmdgroups_exceed_threshold(struct ipa_sudo_conv *conv, int threshold); + +bool +ipa_sudo_cmds_exceed_threshold(struct ipa_sudo_conv *conv, int threshold); + +char * +ipa_sudo_conv_cmdgroup_filter(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + int cmd_threshold); + +char * +ipa_sudo_conv_cmd_filter(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + int cmd_threshold); + +errno_t +ipa_sudo_conv_result(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + struct sysdb_attrs ***_rules, + size_t *_num_rules); + +#endif /* _IPA_SUDO_H_ */ diff --git a/src/providers/ipa/ipa_sudo_async.c b/src/providers/ipa/ipa_sudo_async.c new file mode 100644 index 0000000..c531ecb --- /dev/null +++ b/src/providers/ipa/ipa_sudo_async.c @@ -0,0 +1,1141 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <talloc.h> +#include <tevent.h> +#include <dhash.h> + +#include "providers/ldap/sdap_ops.h" +#include "providers/ldap/sdap_sudo_shared.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_hosts.h" +#include "providers/ipa/ipa_sudo.h" +#include "providers/ipa/ipa_dn.h" +#include "db/sysdb.h" +#include "db/sysdb_sudo.h" + +struct ipa_hostinfo { + size_t num_hosts; + size_t num_hostgroups; + struct sysdb_attrs **hosts; + struct sysdb_attrs **hostgroups; +}; + +static char * +ipa_sudo_filter_append_origdn(char *filter, + struct sysdb_attrs *attrs, + const char *attr_name) +{ + const char *origdn; + char *sanitizeddn; + errno_t ret; + + ret = sysdb_attrs_get_string(attrs, SYSDB_ORIG_DN, &origdn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get original DN " + "[%d]: %s\n", ret, sss_strerror(ret)); + return NULL; + } + + ret = sss_filter_sanitize(NULL, origdn, &sanitizeddn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to sanitize DN " + "[%d]: %s\n", ret, sss_strerror(ret)); + return NULL; + } + + filter = talloc_asprintf_append(filter, "(%s=%s)", attr_name, sanitizeddn); + talloc_free(sanitizeddn); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf_append() failed\n"); + } + + return filter; +} + +/** + * (|(hostCategory=ALL)(memberHost=$DN(fqdn))(memberHost=$DN(hostgroup))...) + */ +static char * +ipa_sudo_host_filter(TALLOC_CTX *mem_ctx, + struct ipa_hostinfo *host, + struct sdap_attr_map *map) +{ + TALLOC_CTX *tmp_ctx; + char *filter; + size_t i; + + /* If realloc fails we will free all data through tmp_ctx. */ + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return NULL; + } + + filter = talloc_asprintf(tmp_ctx, "(&(!(%s=*))(%s=defaults))", + map[IPA_AT_SUDORULE_HOST].name, + map[IPA_AT_SUDORULE_NAME].name); + if (filter == NULL) { + goto fail; + } + + /* Append hostCategory=ALL */ + filter = talloc_asprintf_append(filter, "(%s=ALL)", + map[IPA_AT_SUDORULE_HOSTCATEGORY].name); + if (filter == NULL) { + goto fail; + } + + /* Append client machine */ + for (i = 0; i < host->num_hosts; i++) { + filter = ipa_sudo_filter_append_origdn(filter, host->hosts[i], + map[IPA_AT_SUDORULE_HOST].name); + if (filter == NULL) { + goto fail; + } + } + + /* Append hostgroups */ + for (i = 0; i < host->num_hostgroups; i++) { + filter = ipa_sudo_filter_append_origdn(filter, host->hostgroups[i], + map[IPA_AT_SUDORULE_HOST].name); + if (filter == NULL) { + goto fail; + } + } + + /* OR filters */ + filter = talloc_asprintf(tmp_ctx, "(|%s)", filter); + if (filter == NULL) { + goto fail; + } + + talloc_steal(mem_ctx, filter); + talloc_free(tmp_ctx); + return filter; + +fail: + talloc_free(tmp_ctx); + return NULL; +} + +static errno_t +ipa_sudo_highest_usn(TALLOC_CTX *mem_ctx, + struct sysdb_attrs **attrs, + size_t num_attrs, + char **current_usn) +{ + errno_t ret; + char *usn; + + ret = sysdb_get_highest_usn(mem_ctx, attrs, num_attrs, &usn); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to get highest USN [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (sysdb_compare_usn(usn, *current_usn) > 0) { + talloc_free(*current_usn); + *current_usn = usn; + return EOK; + } + + talloc_free(usn); + return EOK; +} + +static errno_t +ipa_sudo_assoc_rules_filter(TALLOC_CTX *mem_ctx, + struct sysdb_attrs **cmdgroups, + size_t num_cmdgroups, + char **_filter) +{ + TALLOC_CTX *tmp_ctx; + const char *origdn; + char *sanitized; + char *filter; + errno_t ret; + size_t i; + + if (num_cmdgroups == 0) { + return ENOENT; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + filter = talloc_strdup(tmp_ctx, ""); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_cmdgroups; i++) { + ret = sysdb_attrs_get_string(cmdgroups[i], SYSDB_ORIG_DN, &origdn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get original dn [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_INTERNAL; + goto done; + } + + ret = sss_filter_sanitize(tmp_ctx, origdn, &sanitized); + if (ret != EOK) { + goto done; + } + + filter = talloc_asprintf_append(filter, "(%s=%s)", + SYSDB_IPA_SUDORULE_ORIGCMD, sanitized); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + } + + filter = talloc_asprintf(tmp_ctx, "(&(objectClass=%s)(|%s)))", + SYSDB_SUDO_CACHE_OC, filter); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + *_filter = talloc_steal(mem_ctx, filter); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ipa_sudo_assoc_rules(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct sysdb_attrs **cmdgroups, + size_t num_cmdgroups, + struct sysdb_attrs ***_rules, + size_t *_num_rules) +{ + TALLOC_CTX *tmp_ctx; + const char *attrs[] = {SYSDB_NAME, NULL}; + struct sysdb_attrs **rules; + struct ldb_message **msgs; + size_t num_rules; + char *filter; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = ipa_sudo_assoc_rules_filter(tmp_ctx, cmdgroups, + num_cmdgroups, &filter); + if (ret != EOK) { + goto done; + } + + ret = sysdb_search_custom(tmp_ctx, domain, filter, + SUDORULE_SUBDIR, attrs, + &num_rules, &msgs); + if (ret == ENOENT) { + *_rules = NULL; + *_num_rules = 0; + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error looking up sudo rules [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_msg2attrs(tmp_ctx, num_rules, msgs, &rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not convert ldb message to " + "sysdb_attrs [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + *_rules = talloc_steal(mem_ctx, rules); + *_num_rules = num_rules; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +ipa_sudo_filter_rules_bycmdgroups(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct sysdb_attrs **cmdgroups, + size_t num_cmdgroups, + struct sdap_attr_map *map_rule, + char **_filter) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs **rules; + size_t num_rules; + const char *name; + char *sanitized; + char *filter; + errno_t ret; + size_t i; + + if (num_cmdgroups == 0) { + *_filter = NULL; + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = ipa_sudo_assoc_rules(tmp_ctx, domain, cmdgroups, num_cmdgroups, + &rules, &num_rules); + if (ret != EOK) { + goto done; + } + + if (num_rules == 0) { + *_filter = NULL; + ret = EOK; + goto done; + } + + filter = talloc_strdup(tmp_ctx, ""); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_rules; i++) { + ret = sysdb_attrs_get_string(rules[i], SYSDB_NAME, &name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get name [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sss_filter_sanitize(tmp_ctx, name, &sanitized); + if (ret != EOK) { + goto done; + } + + filter = talloc_asprintf_append(filter, "(%s=%s)", + map_rule[IPA_AT_SUDORULE_NAME].name, sanitized); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + } + + filter = talloc_asprintf(tmp_ctx, "(|%s)", filter); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + *_filter = talloc_steal(mem_ctx, filter); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +struct ipa_sudo_fetch_state { + struct tevent_context *ev; + struct sss_domain_info *domain; + struct ipa_sudo_ctx *sudo_ctx; + struct sdap_options *sdap_opts; + struct ipa_hostinfo *host; + struct sdap_handle *sh; + const char *search_filter; + const char *cmdgroups_filter; + + struct sdap_attr_map *map_cmdgroup; + struct sdap_attr_map *map_rule; + struct sdap_attr_map *map_cmd; + struct sdap_search_base **sudo_sb; + + struct ipa_sudo_conv *conv; + struct sysdb_attrs **rules; + size_t num_rules; + int cmd_threshold; + char *usn; +}; + +static errno_t ipa_sudo_fetch_addtl_cmdgroups(struct tevent_req *req); +static void ipa_sudo_fetch_addtl_cmdgroups_done(struct tevent_req *subreq); +static errno_t ipa_sudo_fetch_rules(struct tevent_req *req); +static void ipa_sudo_fetch_rules_done(struct tevent_req *subreq); +static errno_t ipa_sudo_fetch_cmdgroups(struct tevent_req *req); +static void ipa_sudo_fetch_cmdgroups_done(struct tevent_req *subreq); +static errno_t ipa_sudo_fetch_cmds(struct tevent_req *req); +static void ipa_sudo_fetch_cmds_done(struct tevent_req *subreq); +static void ipa_sudo_fetch_done(struct tevent_req *req); + +static struct tevent_req * +ipa_sudo_fetch_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sss_domain_info *domain, + struct ipa_sudo_ctx *sudo_ctx, + struct ipa_hostinfo *host, + struct sdap_attr_map *map_user, + struct sdap_attr_map *map_group, + struct sdap_attr_map *map_host, + struct sdap_attr_map *map_hostgroup, + struct sdap_handle *sh, + const char *cmdgroups_filter, + const char *search_filter) +{ + struct ipa_sudo_fetch_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_sudo_fetch_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->domain = domain; + state->sudo_ctx = sudo_ctx; + state->sdap_opts = sudo_ctx->sdap_opts; + state->host = host; + state->sh = sh; + state->search_filter = search_filter == NULL ? "" : search_filter; + state->cmdgroups_filter = cmdgroups_filter; + + state->map_cmdgroup = sudo_ctx->sudocmdgroup_map; + state->map_rule = sudo_ctx->sudorule_map; + state->map_cmd = sudo_ctx->sudocmd_map; + state->sudo_sb = sudo_ctx->sudo_sb; + state->cmd_threshold = sudo_ctx->sudocmd_threshold; + + state->conv = ipa_sudo_conv_init(state, domain, state->map_rule, + state->map_cmdgroup, state->map_cmd, + map_user, map_group, map_host, + map_hostgroup); + if (state->conv == NULL) { + ret = ENOMEM; + goto immediately; + } + + if (state->cmdgroups_filter != NULL) { + /* We need to fetch additional cmdgroups that may not be revealed + * during normal search. Such as when using entryUSN filter in smart + * refresh, some command groups may have change but none rule was + * modified but we need to fetch associated rules anyway. */ + ret = ipa_sudo_fetch_addtl_cmdgroups(req); + } else { + ret = ipa_sudo_fetch_rules(req); + } + if (ret != EAGAIN) { + goto immediately; + } + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, state->ev); + + return req; +} + +static errno_t +ipa_sudo_fetch_addtl_cmdgroups(struct tevent_req *req) +{ + struct ipa_sudo_fetch_state *state; + struct tevent_req *subreq; + struct sdap_attr_map *map; + char *filter; + + DEBUG(SSSDBG_TRACE_FUNC, "About to fetch additional command groups\n"); + + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + map = state->map_cmdgroup; + + filter = talloc_asprintf(state, "(&(objectClass=%s)%s)", + map[IPA_OC_SUDOCMDGROUP].name, + state->cmdgroups_filter); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build filter\n"); + return ENOMEM; + } + + subreq = sdap_search_bases_send(state, state->ev, state->sdap_opts, + state->sh, state->sudo_sb, map, true, 0, + filter, NULL, NULL); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_sudo_fetch_addtl_cmdgroups_done, req); + return EAGAIN; +} + +static void +ipa_sudo_fetch_addtl_cmdgroups_done(struct tevent_req *subreq) +{ + struct ipa_sudo_fetch_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs **attrs; + size_t num_attrs; + char *filter; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + ret = sdap_search_bases_recv(subreq, state, &num_attrs, &attrs); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Received %zu additional command groups\n", + num_attrs); + + ret = ipa_sudo_filter_rules_bycmdgroups(state, state->domain, attrs, + num_attrs, state->map_rule, + &filter); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to construct rules filter " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + state->search_filter = sdap_or_filters(state, state->search_filter, filter); + if (state->search_filter == NULL) { + ret = ENOMEM; + goto done; + } + + ret = ipa_sudo_fetch_rules(req); + +done: + if (ret == EOK) { + ipa_sudo_fetch_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t +ipa_sudo_fetch_rules(struct tevent_req *req) +{ + struct ipa_sudo_fetch_state *state; + struct tevent_req *subreq; + struct sdap_attr_map *map; + char *host_filter; + char *filter; + + DEBUG(SSSDBG_TRACE_FUNC, "About to fetch sudo rules\n"); + + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + map = state->map_rule; + + host_filter = ipa_sudo_host_filter(state, state->host, map); + if (host_filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build host filter\n"); + return ENOMEM; + } + + filter = talloc_asprintf(state, "(&(objectClass=%s)(%s=TRUE)%s%s)", + map[IPA_OC_SUDORULE].name, + map[IPA_AT_SUDORULE_ENABLED].name, + host_filter, state->search_filter); + talloc_zfree(host_filter); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build filter\n"); + return ENOMEM; + } + + subreq = sdap_search_bases_send(state, state->ev, state->sdap_opts, + state->sh, state->sudo_sb, map, true, 0, + filter, NULL, NULL); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_sudo_fetch_rules_done, req); + return EAGAIN; +} + +static void +ipa_sudo_fetch_rules_done(struct tevent_req *subreq) +{ + struct ipa_sudo_fetch_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs **attrs; + size_t num_attrs; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + ret = sdap_search_bases_recv(subreq, state, &num_attrs, &attrs); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Received %zu sudo rules\n", num_attrs); + + ret = ipa_sudo_conv_rules(state->conv, attrs, num_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed when converting rules " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = ipa_sudo_highest_usn(state, attrs, num_attrs, &state->usn); + if (ret != EOK) { + goto done; + } + + ret = ipa_sudo_fetch_cmdgroups(req); + +done: + if (ret == EOK) { + ipa_sudo_fetch_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t +ipa_sudo_fetch_cmdgroups(struct tevent_req *req) +{ + struct ipa_sudo_fetch_state *state; + struct tevent_req *subreq; + char *filter; + + DEBUG(SSSDBG_TRACE_FUNC, "About to fetch sudo command groups\n"); + + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + if (ipa_sudo_conv_has_cmdgroups(state->conv)) { + DEBUG(SSSDBG_TRACE_FUNC, "No command groups needs to be downloaded\n"); + return ipa_sudo_fetch_cmds(req); + } + + filter = ipa_sudo_conv_cmdgroup_filter(state, state->conv, + state->cmd_threshold); + + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build filter\n"); + return ENOMEM; + } + + subreq = sdap_search_bases_send(state, state->ev, state->sdap_opts, + state->sh, state->sudo_sb, + state->map_cmdgroup, true, 0, + filter, NULL, NULL); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_sudo_fetch_cmdgroups_done, req); + return EAGAIN; +} + +static void +ipa_sudo_fetch_cmdgroups_done(struct tevent_req *subreq) +{ + struct ipa_sudo_fetch_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs **attrs; + size_t num_attrs; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + ret = sdap_search_bases_recv(subreq, state, &num_attrs, &attrs); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Received %zu sudo command groups\n", + num_attrs); + + ret = ipa_sudo_conv_cmdgroups(state->conv, attrs, num_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed when converting command groups " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = ipa_sudo_highest_usn(state, attrs, num_attrs, &state->usn); + if (ret != EOK) { + goto done; + } + + ret = ipa_sudo_fetch_cmds(req); + +done: + if (ret == EOK) { + ipa_sudo_fetch_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t +ipa_sudo_fetch_cmds(struct tevent_req *req) +{ + struct ipa_sudo_fetch_state *state; + struct tevent_req *subreq; + char *filter; + + DEBUG(SSSDBG_TRACE_FUNC, "About to fetch sudo commands\n"); + + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + if (ipa_sudo_conv_has_cmds(state->conv)) { + DEBUG(SSSDBG_TRACE_FUNC, "No commands needs to be downloaded\n"); + return EOK; + } + + filter = ipa_sudo_conv_cmd_filter(state, state->conv, state->cmd_threshold); + + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build filter\n"); + return ENOMEM; + } + + subreq = sdap_search_bases_send(state, state->ev, state->sdap_opts, + state->sh, state->sudo_sb, + state->map_cmd, true, 0, + filter, NULL, NULL); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, ipa_sudo_fetch_cmds_done, req); + return EAGAIN; +} + +static void +ipa_sudo_fetch_cmds_done(struct tevent_req *subreq) +{ + struct ipa_sudo_fetch_state *state = NULL; + struct tevent_req *req = NULL; + struct sysdb_attrs **attrs; + size_t num_attrs; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + ret = sdap_search_bases_recv(subreq, state, &num_attrs, &attrs); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Received %zu sudo commands\n", num_attrs); + + ret = ipa_sudo_conv_cmds(state->conv, attrs, num_attrs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed when converting commands " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + +done: + if (ret == EOK) { + ipa_sudo_fetch_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static void +ipa_sudo_fetch_done(struct tevent_req *req) +{ + struct ipa_sudo_fetch_state *state = NULL; + errno_t ret; + + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + DEBUG(SSSDBG_TRACE_FUNC, "About to convert rules\n"); + + ret = ipa_sudo_conv_result(state, state->conv, + &state->rules, &state->num_rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to convert rules [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t +ipa_sudo_fetch_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs ***_rules, + size_t *_num_rules, + char **_usn) +{ + struct ipa_sudo_fetch_state *state = NULL; + state = tevent_req_data(req, struct ipa_sudo_fetch_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_rules = talloc_steal(mem_ctx, state->rules); + *_num_rules = state->num_rules; + *_usn = talloc_steal(mem_ctx, state->usn); + + return EOK; +} + + +struct ipa_sudo_refresh_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; + struct ipa_sudo_ctx *sudo_ctx; + struct ipa_options *ipa_opts; + struct sdap_options *sdap_opts; + const char *cmdgroups_filter; + const char *search_filter; + const char *delete_filter; + bool update_usn; + + struct sdap_id_op *sdap_op; + struct sdap_handle *sh; + int dp_error; + + struct sysdb_attrs **rules; + size_t num_rules; +}; + +static errno_t ipa_sudo_refresh_retry(struct tevent_req *req); +static void ipa_sudo_refresh_connect_done(struct tevent_req *subreq); +static void ipa_sudo_refresh_host_done(struct tevent_req *subreq); +static void ipa_sudo_refresh_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_sudo_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_sudo_ctx *sudo_ctx, + const char *cmdgroups_filter, + const char *search_filter, + const char *delete_filter, + bool update_usn) +{ + struct ipa_sudo_refresh_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ipa_sudo_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->sysdb = sudo_ctx->id_ctx->be->domain->sysdb; + state->domain = sudo_ctx->id_ctx->be->domain; + state->sudo_ctx = sudo_ctx; + state->ipa_opts = sudo_ctx->ipa_opts; + state->sdap_opts = sudo_ctx->sdap_opts; + state->dp_error = DP_ERR_FATAL; + state->update_usn = update_usn; + + state->sdap_op = sdap_id_op_create(state, + sudo_ctx->id_ctx->conn->conn_cache); + if (!state->sdap_op) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n"); + ret = ENOMEM; + goto immediately; + } + + state->cmdgroups_filter = talloc_strdup(state, cmdgroups_filter); + if (cmdgroups_filter != NULL && state->cmdgroups_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + state->search_filter = talloc_strdup(state, search_filter); + if (search_filter != NULL && state->search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + state->delete_filter = talloc_strdup(state, delete_filter); + if (delete_filter != NULL && state->delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + ret = ipa_sudo_refresh_retry(req); + if (ret == EAGAIN) { + /* asynchronous processing */ + return req; + } + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, state->ev); + + return req; +} + +static errno_t +ipa_sudo_refresh_retry(struct tevent_req *req) +{ + struct ipa_sudo_refresh_state *state; + struct tevent_req *subreq; + int ret; + + state = tevent_req_data(req, struct ipa_sudo_refresh_state); + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed: " + "%d(%s)\n", ret, strerror(ret)); + return ret; + } + + tevent_req_set_callback(subreq, ipa_sudo_refresh_connect_done, req); + + return EAGAIN; +} + +static void +ipa_sudo_refresh_connect_done(struct tevent_req *subreq) +{ + struct ipa_sudo_refresh_state *state; + const char *hostname; + struct tevent_req *req; + int dp_error; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_refresh_state); + + ret = sdap_id_op_connect_recv(subreq, &dp_error); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "SUDO LDAP connection failed " + "[%d]: %s\n", ret, strerror(ret)); + state->dp_error = dp_error; + tevent_req_error(req, ret); + return; + } + + state->sh = sdap_id_op_handle(state->sdap_op); + + DEBUG(SSSDBG_TRACE_FUNC, "SUDO LDAP connection successful\n"); + DEBUG(SSSDBG_TRACE_FUNC, "About to fetch host information\n"); + + /* Obtain host information. */ + hostname = dp_opt_get_string(state->ipa_opts->basic, IPA_HOSTNAME); + + subreq = ipa_host_info_send(state, state->ev, + state->sh, state->sdap_opts, hostname, + state->ipa_opts->id->host_map, + state->ipa_opts->hostgroup_map, + state->ipa_opts->id->sdom->host_search_bases); + if (subreq == NULL) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_sudo_refresh_host_done, req); +} + +static void +ipa_sudo_refresh_host_done(struct tevent_req *subreq) +{ + struct ipa_sudo_refresh_state *state; + struct ipa_hostinfo *host; + struct tevent_req *req; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_refresh_state); + + host = talloc_zero(state, struct ipa_hostinfo); + if (host == NULL) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ENOMEM); + return; + } + + ret = ipa_host_info_recv(subreq, host, &host->num_hosts, &host->hosts, + &host->num_hostgroups, &host->hostgroups); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve host information " + "[%d]: %s\n", ret, sss_strerror(ret)); + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + return; + } + + subreq = ipa_sudo_fetch_send(state, state->ev, state->domain, + state->sudo_ctx, host, + state->sdap_opts->user_map, + state->sdap_opts->group_map, + state->ipa_opts->id->host_map, + state->ipa_opts->hostgroup_map, state->sh, + state->cmdgroups_filter, state->search_filter); + if (subreq == NULL) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ipa_sudo_refresh_done, req); +} + +static void +ipa_sudo_refresh_done(struct tevent_req *subreq) +{ + struct ipa_sudo_refresh_state *state; + struct tevent_req *req; + char *usn = NULL; + bool in_transaction = false; + errno_t sret; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_refresh_state); + + ret = ipa_sudo_fetch_recv(state, subreq, &state->rules, + &state->num_rules, &usn); + talloc_zfree(subreq); + + ret = sdap_id_op_done(state->sdap_op, ret, &state->dp_error); + if (state->dp_error == DP_ERR_OK && ret != EOK) { + /* retry */ + ret = ipa_sudo_refresh_retry(req); + if (ret != EOK) { + tevent_req_error(req, ret); + } + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = sysdb_transaction_start(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + ret = sysdb_sudo_purge(state->domain, state->delete_filter, + state->rules, state->num_rules); + if (ret != EOK) { + goto done; + } + + ret = sysdb_sudo_store(state->domain, state->rules, state->num_rules); + if (ret != EOK) { + goto done; + } + + ret = sysdb_transaction_commit(state->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + + if (usn != NULL && state->update_usn) { + sdap_sudo_set_usn(state->sudo_ctx->id_ctx->srv_opts, usn); + } + + DEBUG(SSSDBG_TRACE_FUNC, "Sudo rules are successfully stored in cache\n"); + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(state->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +ipa_sudo_refresh_recv(struct tevent_req *req, + int *dp_error, + size_t *_num_rules) +{ + struct ipa_sudo_refresh_state *state = NULL; + state = tevent_req_data(req, struct ipa_sudo_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + if (_num_rules != NULL) { + *_num_rules = state->num_rules; + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_sudo_conversion.c b/src/providers/ipa/ipa_sudo_conversion.c new file mode 100644 index 0000000..220d937 --- /dev/null +++ b/src/providers/ipa/ipa_sudo_conversion.c @@ -0,0 +1,1369 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <ldb.h> +#include <talloc.h> +#include <dhash.h> + +#include "providers/ldap/sdap.h" +#include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_dn.h" +#include "db/sysdb_sudo.h" +#include "db/sysdb.h" +#include "util/util.h" + +#define SUDO_DN_CMDGROUPS "sudocmdgroups" +#define SUDO_DN_CMDS "sudocmds" +#define SUDO_DN_CONTAINER "sudo" +#define SUDO_DN_CN "cn" + +#define MATCHDN(cat) SUDO_DN_CN, (cat), SUDO_DN_CN, SUDO_DN_CONTAINER +#define MATCHDN_CMDGROUPS MATCHDN(SUDO_DN_CMDGROUPS) +#define MATCHDN_CMDS MATCHDN(SUDO_DN_CMDS) + +#define MATCHRDN_CMDGROUPS(map) (map)[IPA_AT_SUDOCMDGROUP_NAME].name, MATCHDN_CMDGROUPS +#define MATCHRDN_CMDS(attr, map) (map)[attr].name, MATCHDN_CMDS + +#define MATCHRDN_USER(map) (map)[SDAP_AT_USER_NAME].name, "cn", "users", "cn", "accounts" +#define MATCHRDN_GROUP(map) (map)[SDAP_AT_GROUP_NAME].name, "cn", "groups", "cn", "accounts" +#define MATCHRDN_HOST(map) (map)[SDAP_AT_HOST_FQDN].name, "cn", "computers", "cn", "accounts" +#define MATCHRDN_HOSTGROUP(map) (map)[IPA_AT_HOSTGROUP_NAME].name, "cn", "hostgroups", "cn", "accounts" + +struct ipa_sudo_conv { + struct sss_domain_info *dom; + + struct sdap_attr_map *map_rule; + struct sdap_attr_map *map_cmdgroup; + struct sdap_attr_map *map_cmd; + struct sdap_attr_map *map_user; + struct sdap_attr_map *map_group; + struct sdap_attr_map *map_host; + struct sdap_attr_map *map_hostgroup; + + hash_table_t *rules; + hash_table_t *cmdgroups; + hash_table_t *cmds; +}; + +struct ipa_sudo_dn_list { + struct ipa_sudo_dn_list *prev, *next; + const char *dn; +}; + +struct ipa_sudo_rulemember { + struct ipa_sudo_dn_list *cmdgroups; + struct ipa_sudo_dn_list *cmds; +}; + +struct ipa_sudo_rule { + struct sysdb_attrs *attrs; + struct ipa_sudo_rulemember allow; + struct ipa_sudo_rulemember deny; +}; + +struct ipa_sudo_cmdgroup { + struct ipa_sudo_dn_list *cmds; + const char **expanded; +}; + +static size_t +ipa_sudo_dn_list_count(struct ipa_sudo_dn_list *list) +{ + struct ipa_sudo_dn_list *item; + size_t i; + + for (i = 0, item = list; item != NULL; item = item->next, i++) { + /* no op */ + } + + return i; +} + +static errno_t +ipa_sudo_conv_store(hash_table_t *table, + const char *key, + void *value) +{ + hash_key_t hkey; + hash_value_t hvalue; + int hret; + + if (table == NULL || key == NULL) { + return EINVAL; + } + + hkey.type = HASH_KEY_STRING; + hkey.str = discard_const(key); + + /* If value is NULL we don't want to override existing entry. */ + if (value == NULL && hash_has_key(table, &hkey)) { + return EEXIST; + } + + hvalue.type = HASH_VALUE_PTR; + hvalue.ptr = value; + + hret = hash_enter(table, &hkey, &hvalue); + if (hret != HASH_SUCCESS) { + return EIO; + } + + if (value != NULL) { + talloc_steal(table, value); + } + + return EOK; +} + +static void * +ipa_sudo_conv_lookup(hash_table_t *table, + const char *key) +{ + hash_key_t hkey; + hash_value_t hvalue; + int hret; + + hkey.type = HASH_KEY_STRING; + hkey.str = discard_const(key); + + hret = hash_lookup(table, &hkey, &hvalue); + if (hret == HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_OP_FAILURE, "Key not found %s\n", key); + return NULL; + } else if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup value [%d]\n", hret); + return NULL; + } + + return hvalue.ptr; +} + +static errno_t +store_rulemember(TALLOC_CTX *mem_ctx, + struct ipa_sudo_dn_list **list, + hash_table_t *table, + const char *dn) +{ + struct ipa_sudo_dn_list *item; + errno_t ret; + + item = talloc_zero(mem_ctx, struct ipa_sudo_dn_list); + if (item == NULL) { + return ENOMEM; + } + + ret = ipa_sudo_conv_store(table, dn, NULL); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to store DN %s [%d]: %s\n", + dn, ret, sss_strerror(ret)); + goto done; + } + + item->dn = talloc_steal(item, dn); + DLIST_ADD(*list, item); + +done: + if (ret != EOK && ret != EEXIST) { + talloc_free(item); + } + + return ret; +} + +static bool is_ipacmdgroup(struct ipa_sudo_conv *conv, const char *dn) +{ + if (ipa_check_rdn_bool(conv->dom->sysdb, dn, + MATCHRDN_CMDGROUPS(conv->map_cmdgroup))) { + return true; + } + + return false; +} + +static bool is_ipacmd(struct ipa_sudo_conv *conv, const char *dn) +{ + if (ipa_check_rdn_bool(conv->dom->sysdb, dn, + MATCHRDN_CMDS(IPA_AT_SUDOCMD_UUID, conv->map_cmd))) { + return true; + } + + /* For older versions of FreeIPA than 3.1. */ + if (ipa_check_rdn_bool(conv->dom->sysdb, dn, + MATCHRDN_CMDS(IPA_AT_SUDOCMD_CMD, conv->map_cmd))) { + return true; + } + + return false; +} + +static errno_t +process_rulemember(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + struct ipa_sudo_rulemember *rulemember, + struct sysdb_attrs *rule, + const char *attr) +{ + TALLOC_CTX *tmp_ctx; + const char **members; + errno_t ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_attrs_get_string_array(rule, attr, tmp_ctx, &members); + if (ret == ENOENT) { + ret = EOK; + goto done; + } else if (ret != EOK) { + goto done; + } + + for (i = 0; members[i] != NULL; i++) { + if (is_ipacmdgroup(conv, members[i])) { + ret = store_rulemember(mem_ctx, &rulemember->cmdgroups, + conv->cmdgroups, members[i]); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Found sudo command group %s\n", + members[i]); + } else if (ret != EEXIST) { + goto done; + } + } else if (is_ipacmd(conv, members[i])) { + ret = store_rulemember(mem_ctx, &rulemember->cmds, + conv->cmds, members[i]); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Found sudo command %s\n", + members[i]); + } else if (ret != EEXIST) { + goto done; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Invalid member DN %s, skipping...\n", + members[i]); + continue; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +process_allowcmd(struct ipa_sudo_conv *conv, + struct ipa_sudo_rule *rule) +{ + return process_rulemember(rule, conv, &rule->allow, rule->attrs, + SYSDB_IPA_SUDORULE_ALLOWCMD); +} + +static errno_t +process_denycmd(struct ipa_sudo_conv *conv, + struct ipa_sudo_rule *rule) +{ + return process_rulemember(rule, conv, &rule->deny, rule->attrs, + SYSDB_IPA_SUDORULE_DENYCMD); +} + +static errno_t +process_cmdgroupmember(struct ipa_sudo_conv *conv, + struct ipa_sudo_cmdgroup *cmdgroup, + struct sysdb_attrs *attrs) +{ + TALLOC_CTX *tmp_ctx; + struct ipa_sudo_dn_list *item; + const char **members; + errno_t ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_attrs_get_string_array(attrs, SYSDB_MEMBER, tmp_ctx, &members); + if (ret == ENOENT) { + ret = EOK; + goto done; + } else if (ret != EOK) { + goto done; + } + + for (i = 0; members[i] != NULL; i++) { + ret = ipa_sudo_conv_store(conv->cmds, members[i], NULL); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Found sudo command %s\n", + members[i]); + } else if (ret != EEXIST) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to store DN [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + item = talloc_zero(tmp_ctx, struct ipa_sudo_dn_list); + if (item == NULL) { + ret = ENOMEM; + goto done; + } + + item->dn = talloc_steal(item, members[i]); + DLIST_ADD(cmdgroup->cmds, item); + talloc_steal(cmdgroup, item); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +struct ipa_sudo_conv * +ipa_sudo_conv_init(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + struct sdap_attr_map *map_rule, + struct sdap_attr_map *map_cmdgroup, + struct sdap_attr_map *map_cmd, + struct sdap_attr_map *map_user, + struct sdap_attr_map *map_group, + struct sdap_attr_map *map_host, + struct sdap_attr_map *map_hostgroup) +{ + struct ipa_sudo_conv *conv; + errno_t ret; + + conv = talloc_zero(mem_ctx, struct ipa_sudo_conv); + if (conv == NULL) { + return NULL; + } + + conv->dom = dom; + conv->map_rule = map_rule; + conv->map_cmdgroup = map_cmdgroup; + conv->map_cmd = map_cmd; + conv->map_user = map_user; + conv->map_group = map_group; + conv->map_host = map_host; + conv->map_hostgroup = map_hostgroup; + + ret = sss_hash_create(conv, 0, &conv->rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sss_hash_create(conv, 0, &conv->cmdgroups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sss_hash_create(conv, 0, &conv->cmds); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + if (ret != EOK) { + talloc_free(conv); + return NULL; + } + + return conv; +} + +errno_t +ipa_sudo_conv_rules(struct ipa_sudo_conv *conv, + struct sysdb_attrs **rules, + size_t num_rules) +{ + struct ipa_sudo_rule *rule = NULL; + const char *key; + errno_t ret; + size_t i; + + if (num_rules == 0) { + /* We're done here. */ + return EOK; + } + + for (i = 0; i < num_rules; i++) { + ret = sysdb_attrs_get_string(rules[i], SYSDB_NAME, &key); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to get rule name, skipping " + "[%d]: %s\n", ret, sss_strerror(ret)); + continue; + } + + rule = talloc_zero(conv->rules, struct ipa_sudo_rule); + if (rule == NULL) { + ret = ENOMEM; + goto done; + } + + rule->attrs = rules[i]; + + ret = process_allowcmd(conv, rule); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to process memberAllowCmd " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + ret = process_denycmd(conv, rule); + if (ret != EOK && ret != EEXIST) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to process memberDenyCmd " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + ret = ipa_sudo_conv_store(conv->rules, key, rule); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store rule into table " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + talloc_steal(rule, rule->attrs); + rule = NULL; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(rule); + } + + return ret; +} + +errno_t +ipa_sudo_conv_cmdgroups(struct ipa_sudo_conv *conv, + struct sysdb_attrs **cmdgroups, + size_t num_cmdgroups) +{ + struct ipa_sudo_cmdgroup *cmdgroup = NULL; + const char *key; + errno_t ret; + size_t i; + + if (num_cmdgroups == 0) { + /* We're done here. */ + return EOK; + } + + for (i = 0; i < num_cmdgroups; i++) { + ret = sysdb_attrs_get_string(cmdgroups[i], SYSDB_ORIG_DN, &key); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to get command group DN, " + "skipping [%d]: %s\n", ret, sss_strerror(ret)); + continue; + } + + cmdgroup = talloc_zero(conv->cmdgroups, struct ipa_sudo_cmdgroup); + if (cmdgroup == NULL) { + ret = ENOMEM; + goto done; + } + + ret = process_cmdgroupmember(conv, cmdgroup, cmdgroups[i]); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to process member " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + ret = ipa_sudo_conv_store(conv->cmdgroups, key, cmdgroup); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store command group into " + "table [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + cmdgroup = NULL; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cmdgroup); + } + + return ret; +} + +errno_t +ipa_sudo_conv_cmds(struct ipa_sudo_conv *conv, + struct sysdb_attrs **cmds, + size_t num_cmds) +{ + const char *key; + const char *cmd; + errno_t ret; + size_t i; + + if (num_cmds == 0) { + /* We're done here. */ + return EOK; + } + + for (i = 0; i < num_cmds; i++) { + ret = sysdb_attrs_get_string(cmds[i], SYSDB_ORIG_DN, &key); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to get command DN, skipping " + "[%d]: %s\n", ret, sss_strerror(ret)); + continue; + } + + ret = sysdb_attrs_get_string(cmds[i], SYSDB_IPA_SUDOCMD_SUDOCMD, &cmd); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to get command, skipping " + "[%d]: %s\n", ret, sss_strerror(ret)); + continue; + } + + ret = ipa_sudo_conv_store(conv->cmds, key, discard_const(cmd)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to store command into table " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + return ret; +} + +bool +ipa_sudo_conv_has_cmdgroups(struct ipa_sudo_conv *conv) +{ + return hash_count(conv->cmdgroups) == 0; +} + +bool +ipa_sudo_conv_has_cmds(struct ipa_sudo_conv *conv) +{ + return hash_count(conv->cmds) == 0; +} + +bool +ipa_sudo_cmdgroups_exceed_threshold(struct ipa_sudo_conv *conv, int threshold) +{ + return (hash_count(conv->cmdgroups)) > threshold; +} +bool +ipa_sudo_cmds_exceed_threshold(struct ipa_sudo_conv *conv, int threshold) +{ + return (hash_count(conv->cmds)) > threshold; +} + +typedef errno_t (*ipa_sudo_conv_rdn_fn)(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + struct sysdb_ctx *sysdb, + const char *dn, + char **_rdn_val, + const char **_rdn_attr); + +static errno_t get_sudo_cmdgroup_rdn(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + struct sysdb_ctx *sysdb, + const char *dn, + char **_rdn_val, + const char **_rdn_attr) +{ + char *rdn_val; + errno_t ret; + + ret = ipa_get_rdn(mem_ctx, sysdb, dn, &rdn_val, + MATCHRDN_CMDGROUPS(map)); + if (ret != EOK) { + return ret; + } + + *_rdn_val = rdn_val; + *_rdn_attr = map[IPA_AT_SUDOCMDGROUP_NAME].name; + + return EOK; +} + +static errno_t get_sudo_cmd_rdn(TALLOC_CTX *mem_ctx, + struct sdap_attr_map *map, + struct sysdb_ctx *sysdb, + const char *dn, + char **_rdn_val, + const char **_rdn_attr) +{ + char *rdn_val; + errno_t ret; + + ret = ipa_get_rdn(mem_ctx, sysdb, dn, &rdn_val, + MATCHRDN_CMDS(IPA_AT_SUDOCMD_UUID, map)); + if (ret == EOK) { + *_rdn_val = rdn_val; + *_rdn_attr = map[IPA_AT_SUDOCMD_UUID].name; + + return EOK; + } else if (ret != ENOENT) { + return ret; + } + + /* For older versions of FreeIPA than 3.1. */ + ret = ipa_get_rdn(mem_ctx, sysdb, dn, &rdn_val, + MATCHRDN_CMDS(IPA_AT_SUDOCMD_CMD, map)); + if (ret != EOK) { + return ret; + } + + *_rdn_val = rdn_val; + *_rdn_attr = map[IPA_AT_SUDOCMD_CMD].name; + + return EOK; +} + +static char * +build_filter(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + hash_table_t *table, + struct sdap_attr_map *map, + ipa_sudo_conv_rdn_fn rdn_fn) +{ + TALLOC_CTX *tmp_ctx; + hash_key_t *keys; + unsigned long int count; + unsigned long int i; + char *filter = NULL; + char *rdn_val; + const char *rdn_attr; + char *safe_rdn; + errno_t ret; + int hret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return NULL; + } + + hret = hash_keys(table, &count, &keys); + if (hret != HASH_SUCCESS) { + ret = ENOMEM; + goto done; + } + + talloc_steal(tmp_ctx, keys); + + filter = talloc_strdup(tmp_ctx, ""); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < count; i++) { + ret = rdn_fn(tmp_ctx, map, sysdb, keys[i].str, &rdn_val, &rdn_attr); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get member %s [%d]: %s\n", + keys[i].str, ret, sss_strerror(ret)); + goto done; + } + + ret = sss_filter_sanitize(tmp_ctx, rdn_val, &safe_rdn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to sanitize DN " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + filter = talloc_asprintf_append(filter, "(%s=%s)", rdn_attr, safe_rdn); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + } + + /* objectClass is always first */ + filter = talloc_asprintf(filter, "(&(objectClass=%s)(|%s))", + map[0].name, filter); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_steal(mem_ctx, filter); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + if (ret != EOK) { + return NULL; + } + + return filter; +} + +char * +ipa_sudo_conv_cmdgroup_filter(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + int cmd_threshold) +{ + if (ipa_sudo_cmdgroups_exceed_threshold(conv, cmd_threshold)) { + DEBUG(SSSDBG_TRACE_FUNC, + "Command threshold [%d] exceeded, retrieving all sudo command " + "groups\n", cmd_threshold); + return talloc_asprintf(mem_ctx, "(objectClass=%s)", + conv->map_cmdgroup->name); + } else { + return build_filter(mem_ctx, conv->dom->sysdb, conv->cmdgroups, + conv->map_cmdgroup, get_sudo_cmdgroup_rdn); + } +} + +char * +ipa_sudo_conv_cmd_filter(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + int cmd_threshold) +{ + if (ipa_sudo_cmdgroups_exceed_threshold(conv, cmd_threshold)) { + DEBUG(SSSDBG_TRACE_FUNC, + "Command threshold [%d] exceeded, retrieving all sudo commands\n", + cmd_threshold); + return talloc_asprintf(mem_ctx, "(objectClass=%s)", + conv->map_cmd->name); + } else { + return build_filter(mem_ctx, conv->dom->sysdb, conv->cmds, + conv->map_cmd, get_sudo_cmd_rdn); + } +} + +struct ipa_sudo_conv_result_ctx { + struct ipa_sudo_conv *conv; + struct sysdb_attrs **rules; + size_t num_rules; + errno_t ret; +}; + +static const char * +convert_host(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + char *rdn; + const char *group; + errno_t ret; + + *skip_entry = false; + + ret = ipa_get_rdn(mem_ctx, conv->dom->sysdb, value, &rdn, + MATCHRDN_HOST(conv->map_host)); + if (ret == EOK) { + return rdn; + } else if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_rdn() failed on value %s [%d]: %s\n", + value, ret, sss_strerror(ret)); + return NULL; + } + + ret = ipa_get_rdn(mem_ctx, conv->dom->sysdb, value, &rdn, + MATCHRDN_HOSTGROUP(conv->map_hostgroup)); + if (ret == ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected DN %s: Skipping\n", value); + *skip_entry = true; + return NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "ipa_get_rdn() failed on value %s [%d]: %s\n", + value, ret, sss_strerror(ret)); + return NULL; + } + + group = talloc_asprintf(mem_ctx, "+%s", rdn); + talloc_free(rdn); + + return group; +} + +static const char * +convert_user(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + char *rdn; + const char *group; + errno_t ret; + + *skip_entry = false; + + ret = ipa_get_rdn(mem_ctx, conv->dom->sysdb, value, &rdn, + MATCHRDN_USER(conv->map_user)); + if (ret == EOK) { + return rdn; + } else if (ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_rdn() failed on value %s [%d]: %s\n", + value, ret, sss_strerror(ret)); + return NULL; + } + + ret = ipa_get_rdn(mem_ctx, conv->dom->sysdb, value, &rdn, + MATCHRDN_GROUP(conv->map_group)); + if (ret == ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected DN %s: Skipping\n", value); + *skip_entry = true; + return NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "ipa_get_rdn() failed on value %s [%d]: %s\n", + value, ret, sss_strerror(ret)); + return NULL; + } + + group = talloc_asprintf(mem_ctx, "%%%s", rdn); + talloc_free(rdn); + + return group; +} + +static const char * +convert_user_fqdn(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + const char *shortname = NULL; + char *fqdn = NULL; + + *skip_entry = false; + + shortname = convert_user(mem_ctx, conv, value, skip_entry); + if (shortname == NULL) { + return NULL; + } + + fqdn = sss_create_internal_fqname(mem_ctx, shortname, conv->dom->name); + talloc_free(discard_const(shortname)); + return fqdn; +} + +static const char * +convert_ext_user(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + /* If value is already fully qualified, return it as it is */ + if (strrchr(value, '@') != NULL) { + return talloc_strdup(mem_ctx, value); + } + return sss_create_internal_fqname(mem_ctx, value, conv->dom->name); +} + +static const char * +convert_group(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + char *rdn; + errno_t ret; + + *skip_entry = false; + + ret = ipa_get_rdn(mem_ctx, conv->dom->sysdb, value, &rdn, + MATCHRDN_GROUP(conv->map_group)); + if (ret == ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected DN %s: Skipping\n", value); + *skip_entry = true; + return NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "ipa_get_rdn() failed on value %s [%d]: %s\n", + value, ret, sss_strerror(ret)); + return NULL; + } + + return rdn; +} + +static const char * +convert_group_fqdn(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + const char *shortname = NULL; + char *fqdn = NULL; + + *skip_entry = false; + + shortname = convert_group(mem_ctx, conv, value, skip_entry); + if (shortname == NULL) { + return NULL; + } + + fqdn = sss_create_internal_fqname(mem_ctx, shortname, conv->dom->name); + talloc_free(discard_const(shortname)); + return fqdn; +} + +static const char * +convert_runasextusergroup(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + if (value == NULL) + return NULL; + + if (value[0] == '%') + return talloc_strdup(mem_ctx, value); + + return talloc_asprintf(mem_ctx, "%%%s", value); +} + +static const char * +convert_cat(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry) +{ + + *skip_entry = false; + + if (strcmp(value, "all") == 0) { + return talloc_strdup(mem_ctx, "ALL"); + } + + return value; +} + +static errno_t +convert_attributes(struct ipa_sudo_conv *conv, + struct ipa_sudo_rule *rule, + struct sysdb_attrs *attrs) +{ + TALLOC_CTX *tmp_ctx; + const char **values; + const char *value; + errno_t ret; + int i, j; + bool skip_entry; + static struct { + const char *ipa; + const char *sudo; + const char *(*conv_fn)(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + const char *value, + bool *skip_entry); + } table[] = {{SYSDB_NAME, SYSDB_SUDO_CACHE_AT_CN , NULL}, + {SYSDB_IPA_SUDORULE_HOST, SYSDB_SUDO_CACHE_AT_HOST , convert_host}, + {SYSDB_IPA_SUDORULE_USER, SYSDB_SUDO_CACHE_AT_USER , convert_user_fqdn}, + {SYSDB_IPA_SUDORULE_RUNASUSER, SYSDB_SUDO_CACHE_AT_RUNASUSER , convert_user_fqdn}, + {SYSDB_IPA_SUDORULE_RUNASGROUP, SYSDB_SUDO_CACHE_AT_RUNASGROUP , convert_group_fqdn}, + {SYSDB_IPA_SUDORULE_OPTION, SYSDB_SUDO_CACHE_AT_OPTION , NULL}, + {SYSDB_IPA_SUDORULE_NOTAFTER, SYSDB_SUDO_CACHE_AT_NOTAFTER , NULL}, + {SYSDB_IPA_SUDORULE_NOTBEFORE, SYSDB_SUDO_CACHE_AT_NOTBEFORE , NULL}, + {SYSDB_IPA_SUDORULE_SUDOORDER, SYSDB_SUDO_CACHE_AT_ORDER , NULL}, + {SYSDB_IPA_SUDORULE_CMDCATEGORY, SYSDB_SUDO_CACHE_AT_COMMAND , convert_cat}, + {SYSDB_IPA_SUDORULE_HOSTCATEGORY, SYSDB_SUDO_CACHE_AT_HOST , convert_cat}, + {SYSDB_IPA_SUDORULE_USERCATEGORY, SYSDB_SUDO_CACHE_AT_USER , convert_cat}, + {SYSDB_IPA_SUDORULE_RUNASUSERCATEGORY, SYSDB_SUDO_CACHE_AT_RUNASUSER , convert_cat}, + {SYSDB_IPA_SUDORULE_RUNASGROUPCATEGORY, SYSDB_SUDO_CACHE_AT_RUNASGROUP , convert_cat}, + {SYSDB_IPA_SUDORULE_RUNASEXTUSER, SYSDB_SUDO_CACHE_AT_RUNASUSER , NULL}, + {SYSDB_IPA_SUDORULE_RUNASEXTGROUP, SYSDB_SUDO_CACHE_AT_RUNASGROUP , NULL}, + {SYSDB_IPA_SUDORULE_RUNASEXTUSERGROUP, SYSDB_SUDO_CACHE_AT_RUNASUSER , convert_runasextusergroup}, + {SYSDB_IPA_SUDORULE_EXTUSER, SYSDB_SUDO_CACHE_AT_USER , convert_ext_user}, + {SYSDB_IPA_SUDORULE_ALLOWCMD, SYSDB_IPA_SUDORULE_ORIGCMD , NULL}, + {SYSDB_IPA_SUDORULE_DENYCMD, SYSDB_IPA_SUDORULE_ORIGCMD , NULL}, + {NULL, NULL, NULL}}; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + for (i = 0; table[i].ipa != NULL; i++) { + ret = sysdb_attrs_get_string_array(rule->attrs, table[i].ipa, + tmp_ctx, &values); + if (ret == ENOENT) { + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to read attribute " + "%s [%d]: %s\n", table[i].ipa, ret, sss_strerror(ret)); + goto done; + } + + for (j = 0; values[j] != NULL; j++) { + if (table[i].conv_fn != NULL) { + value = table[i].conv_fn(tmp_ctx, conv, values[j], &skip_entry); + if (value == NULL) { + if (skip_entry) { + continue; + } else { + ret = ENOMEM; + goto done; + } + } + } else { + value = values[j]; + } + + ret = sysdb_attrs_add_string_safe(attrs, table[i].sudo, value); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add attribute " + "%s [%d]: %s\n", table[i].sudo, ret, sss_strerror(ret)); + goto done; + } + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char ** +combine_cmdgroups(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + struct ipa_sudo_dn_list *list) +{ + TALLOC_CTX *tmp_ctx; + struct ipa_sudo_cmdgroup *cmdgroup; + struct ipa_sudo_dn_list *listitem; + const char **values = NULL; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return NULL; + } + + values = talloc_zero_array(tmp_ctx, const char *, 1); + if (values == NULL) { + talloc_free(tmp_ctx); + return NULL; + } + + DLIST_FOR_EACH(listitem, list) { + cmdgroup = ipa_sudo_conv_lookup(conv->cmdgroups, listitem->dn); + if (cmdgroup == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ipa_sudo_conv_lookup failed for DN:%s\n", listitem->dn); + continue; + } + + ret = add_strings_lists(mem_ctx, values, cmdgroup->expanded, + false, &values); + if (ret != EOK) { + talloc_free(tmp_ctx); + return NULL; + } + } + + talloc_steal(mem_ctx, values); + talloc_free(tmp_ctx); + + return values; +} + +static const char ** +combine_cmds(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + struct ipa_sudo_dn_list *list) +{ + struct ipa_sudo_dn_list *listitem; + const char **values; + const char *command; + size_t count; + size_t i; + + count = ipa_sudo_dn_list_count(list); + + values = talloc_zero_array(mem_ctx, const char *, count + 1); + if (values == NULL) { + return NULL; + } + + i = 0; + DLIST_FOR_EACH(listitem, list) { + command = ipa_sudo_conv_lookup(conv->cmds, listitem->dn); + if (command == NULL) { + continue; + } + + values[i] = command; + i++; + } + + return values; +} + +static errno_t +build_sudocommand(struct ipa_sudo_conv *conv, + struct ipa_sudo_rulemember *mlist, + struct sysdb_attrs *attrs, + char prefix) +{ + TALLOC_CTX *tmp_ctx; + const char **cmds[2]; + const char *command; + errno_t ret; + int i, j; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + cmds[0] = combine_cmdgroups(tmp_ctx, conv, mlist->cmdgroups); + if (cmds[0] == NULL) { + ret = ENOMEM; + goto done; + } + + cmds[1] = combine_cmds(tmp_ctx, conv, mlist->cmds); + if (cmds[1] == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < 2; i++) { + for (j = 0; cmds[i][j] != NULL; j++) { + if (prefix == '\0') { + command = cmds[i][j]; + } else { + command = talloc_asprintf(tmp_ctx, "%c%s", prefix, cmds[i][j]); + if (command == NULL) { + ret = ENOMEM; + goto done; + } + } + + ret = sysdb_attrs_add_string_safe(attrs, + SYSDB_SUDO_CACHE_AT_COMMAND, command); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add attribute " + "%s [%d]: %s\n", SYSDB_SUDO_CACHE_AT_COMMAND, + ret, sss_strerror(ret)); + goto done; + } + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +convert_sudocommand(struct ipa_sudo_conv *conv, + struct ipa_sudo_rule *rule, + struct sysdb_attrs *attrs) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = build_sudocommand(conv, &rule->allow, attrs, '\0'); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build allow commands " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = build_sudocommand(conv, &rule->deny, attrs, '!'); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build deny commands " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static bool +rules_iterator(hash_entry_t *item, + void *user_data) +{ + struct ipa_sudo_conv_result_ctx *ctx = user_data; + struct ipa_sudo_rule *rule = item->value.ptr; + struct sysdb_attrs *attrs; + + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: ctx is NULL\n"); + return false; + } + + if (rule == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: rule is NULL\n"); + ctx->ret = ERR_INTERNAL; + return false; + } + + attrs = sysdb_new_attrs(ctx->rules); + if (attrs == NULL) { + ctx->ret = ENOMEM; + return false; + } + + ctx->ret = convert_attributes(ctx->conv, rule, attrs); + if (ctx->ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to convert attributes [%d]: %s\n", + ctx->ret, sss_strerror(ctx->ret)); + talloc_free(attrs); + return false; + } + + ctx->ret = convert_sudocommand(ctx->conv, rule, attrs); + if (ctx->ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to build sudoCommand [%d]: %s\n", + ctx->ret, sss_strerror(ctx->ret)); + talloc_free(attrs); + return false; + } + + ctx->rules[ctx->num_rules] = attrs; + ctx->num_rules++; + + return true; +} + +static bool +cmdgroups_iterator(hash_entry_t *item, + void *user_data) +{ + struct ipa_sudo_conv_result_ctx *ctx = user_data; + struct ipa_sudo_cmdgroup *cmdgroup = item->value.ptr; + const char **values; + + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: ctx is NULL\n"); + return false; + } + + if (cmdgroup == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: rule is NULL\n"); + ctx->ret = ERR_INTERNAL; + return false; + } + + values = combine_cmds(cmdgroup, ctx->conv, cmdgroup->cmds); + if (values == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to expand commands\n"); + ctx->ret = ENOMEM; + return false; + } + + cmdgroup->expanded = values; + ctx->ret = EOK; + + return true; +} + +errno_t +ipa_sudo_conv_result(TALLOC_CTX *mem_ctx, + struct ipa_sudo_conv *conv, + struct sysdb_attrs ***_rules, + size_t *_num_rules) +{ + struct ipa_sudo_conv_result_ctx ctx; + struct sysdb_attrs **rules; + unsigned long num_rules; + int hret; + + num_rules = hash_count(conv->rules); + if (num_rules == 0) { + *_rules = NULL; + *_num_rules = 0; + return EOK; + } + + ctx.conv = conv; + ctx.rules = NULL; + ctx.num_rules = 0; + + /* If there are no cmdgroups the iterator is not called and ctx.ret is + * uninitialized. Since it is ok that there are no cmdgroups initializing + * ctx.ret to EOK. */ + ctx.ret = EOK; + + /* Expand commands in command groups. */ + hret = hash_iterate(conv->cmdgroups, cmdgroups_iterator, &ctx); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to iterate over command groups " + "[%d]\n", hret); + return EIO; + } + + if (ctx.ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to expand command groups " + "[%d]: %s\n", ctx.ret, sss_strerror(ctx.ret)); + return ctx.ret; + } + + /* Convert rules. */ + rules = talloc_zero_array(mem_ctx, struct sysdb_attrs *, num_rules); + if (rules == NULL) { + return ENOMEM; + } + + ctx.rules = rules; + ctx.num_rules = 0; + + hret = hash_iterate(conv->rules, rules_iterator, &ctx); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to iterate over rules [%d]\n", hret); + return EIO; + } + + if (ctx.ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to convert rules [%d]: %s\n", + ctx.ret, sss_strerror(ctx.ret)); + talloc_free(rules); + return ctx.ret; + } + + *_rules = ctx.rules; + *_num_rules = ctx.num_rules; + + return EOK; +} diff --git a/src/providers/ipa/ipa_sudo_refresh.c b/src/providers/ipa/ipa_sudo_refresh.c new file mode 100644 index 0000000..7386a01 --- /dev/null +++ b/src/providers/ipa/ipa_sudo_refresh.c @@ -0,0 +1,470 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <errno.h> +#include <talloc.h> +#include <tevent.h> + +#include "util/util.h" +#include "providers/be_ptask.h" +#include "providers/ipa/ipa_sudo.h" +#include "providers/ldap/sdap_sudo_shared.h" +#include "db/sysdb_sudo.h" + +struct ipa_sudo_full_refresh_state { + struct ipa_sudo_ctx *sudo_ctx; + struct sss_domain_info *domain; + int dp_error; +}; + +static void ipa_sudo_full_refresh_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_sudo_full_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_sudo_ctx *sudo_ctx) +{ + struct ipa_sudo_full_refresh_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + char *delete_filter; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_sudo_full_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->domain = sudo_ctx->id_ctx->be->domain; + state->sudo_ctx = sudo_ctx; + + /* Remove all rules from cache */ + delete_filter = talloc_asprintf(state, "(%s=%s)", SYSDB_OBJECTCLASS, + SYSDB_SUDO_CACHE_OC); + if (delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a full refresh of sudo rules\n"); + + subreq = ipa_sudo_refresh_send(state, ev, sudo_ctx, + NULL, NULL, delete_filter, true); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_sudo_full_refresh_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void +ipa_sudo_full_refresh_done(struct tevent_req *subreq) +{ + struct ipa_sudo_full_refresh_state *state; + struct tevent_req *req; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_full_refresh_state); + + ret = ipa_sudo_refresh_recv(subreq, &state->dp_error, NULL); + talloc_zfree(subreq); + if (ret != EOK || state->dp_error != DP_ERR_OK) { + goto done; + } + + ret = sysdb_sudo_set_last_full_refresh(state->domain, time(NULL)); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to save time of " + "a successful full refresh\n"); + } + + DEBUG(SSSDBG_TRACE_FUNC, "Successful full refresh of sudo rules\n"); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + /* We just finished full request, we can postpone smart refresh. */ + be_ptask_postpone(state->sudo_ctx->smart_refresh); + + tevent_req_done(req); +} + +int +ipa_sudo_full_refresh_recv(struct tevent_req *req, + int *dp_error) +{ + struct ipa_sudo_full_refresh_state *state; + state = tevent_req_data(req, struct ipa_sudo_full_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + return EOK; +} + +struct ipa_sudo_smart_refresh_state { + int dp_error; +}; + +static void ipa_sudo_smart_refresh_done(struct tevent_req *subreq); + +static struct tevent_req * +ipa_sudo_smart_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_sudo_ctx *sudo_ctx) +{ + struct sdap_server_opts *srv_opts = sudo_ctx->id_ctx->srv_opts; + struct ipa_sudo_smart_refresh_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + char *cmdgroups_filter; + char *search_filter; + const char *usn; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ipa_sudo_smart_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + if (be_ptask_running(sudo_ctx->full_refresh)) { + DEBUG(SSSDBG_TRACE_FUNC, "Skipping smart refresh because " + "there is ongoing full refresh.\n"); + state->dp_error = DP_ERR_OK; + ret = EOK; + goto immediately; + } + + /* Download all rules from LDAP that are newer than usn */ + if (srv_opts == NULL || srv_opts->max_sudo_value == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "USN value is unknown, assuming zero.\n"); + usn = "0"; + search_filter = NULL; + } else { + usn = srv_opts->max_sudo_value; + search_filter = talloc_asprintf(state, "(%s>=%s)", + sudo_ctx->sudorule_map[IPA_AT_SUDORULE_ENTRYUSN].name, usn); + if (search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + } + + cmdgroups_filter = talloc_asprintf(state, "(%s>=%s)", + sudo_ctx->sudocmdgroup_map[IPA_AT_SUDOCMDGROUP_ENTRYUSN].name, usn); + if (cmdgroups_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + /* Do not remove any rules that are already in the sysdb. */ + + DEBUG(SSSDBG_TRACE_FUNC, "Issuing a smart refresh of sudo rules " + "(USN >= %s)\n", usn); + + subreq = ipa_sudo_refresh_send(state, ev, sudo_ctx, cmdgroups_filter, + search_filter, NULL, true); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_sudo_smart_refresh_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + tevent_req_post(req, ev); + + return req; +} + +static void ipa_sudo_smart_refresh_done(struct tevent_req *subreq) +{ + struct tevent_req *req = NULL; + struct ipa_sudo_smart_refresh_state *state = NULL; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_smart_refresh_state); + + ret = ipa_sudo_refresh_recv(subreq, &state->dp_error, NULL); + talloc_zfree(subreq); + if (ret != EOK || state->dp_error != DP_ERR_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Successful smart refresh of sudo rules\n"); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int ipa_sudo_smart_refresh_recv(struct tevent_req *req, + int *dp_error) +{ + struct ipa_sudo_smart_refresh_state *state = NULL; + state = tevent_req_data(req, struct ipa_sudo_smart_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + + return EOK; +} + +struct ipa_sudo_rules_refresh_state { + size_t num_rules; + int dp_error; + bool deleted; +}; + +static void ipa_sudo_rules_refresh_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_sudo_rules_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ipa_sudo_ctx *sudo_ctx, + const char **rules) +{ + TALLOC_CTX *tmp_ctx; + struct ipa_sudo_rules_refresh_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + char *search_filter; + char *delete_filter; + char *safe_rule; + errno_t ret; + int i; + + req = tevent_req_create(mem_ctx, &state, struct ipa_sudo_rules_refresh_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + ret = ENOMEM; + goto immediately; + } + + if (rules == NULL || rules[0] == NULL) { + state->dp_error = DP_ERR_OK; + state->num_rules = 0; + state->deleted = false; + ret = EOK; + goto immediately; + } + + search_filter = talloc_zero(tmp_ctx, char); /* assign to tmp_ctx */ + delete_filter = talloc_zero(tmp_ctx, char); /* assign to tmp_ctx */ + + /* Download only selected rules from LDAP. */ + /* Remove all selected rules from cache. */ + for (i = 0; rules[i] != NULL; i++) { + ret = sss_filter_sanitize(tmp_ctx, rules[i], &safe_rule); + if (ret != EOK) { + ret = ENOMEM; + goto immediately; + } + + search_filter = talloc_asprintf_append_buffer(search_filter, "(%s=%s)", + sudo_ctx->sudorule_map[IPA_AT_SUDORULE_NAME].name, + safe_rule); + if (search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + delete_filter = talloc_asprintf_append_buffer(delete_filter, "(%s=%s)", + SYSDB_NAME, safe_rule); + if (delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + } + + state->num_rules = i; + + search_filter = talloc_asprintf(tmp_ctx, "(|%s)", search_filter); + if (search_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + delete_filter = talloc_asprintf(tmp_ctx, "(&(%s=%s)(|%s))", + SYSDB_OBJECTCLASS, SYSDB_SUDO_CACHE_OC, + delete_filter); + if (delete_filter == NULL) { + ret = ENOMEM; + goto immediately; + } + + subreq = ipa_sudo_refresh_send(req, ev, sudo_ctx, NULL, search_filter, + delete_filter, false); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, ipa_sudo_rules_refresh_done, req); + + ret = EOK; + +immediately: + talloc_free(tmp_ctx); + + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void +ipa_sudo_rules_refresh_done(struct tevent_req *subreq) +{ + struct ipa_sudo_rules_refresh_state *state; + struct tevent_req *req = NULL; + size_t downloaded_rules_num; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ipa_sudo_rules_refresh_state); + + ret = ipa_sudo_refresh_recv(subreq, &state->dp_error, &downloaded_rules_num); + talloc_zfree(subreq); + if (ret != EOK || state->dp_error != DP_ERR_OK) { + goto done; + } + + state->deleted = downloaded_rules_num != state->num_rules ? true : false; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int +ipa_sudo_rules_refresh_recv(struct tevent_req *req, + int *dp_error, + bool *deleted) +{ + struct ipa_sudo_rules_refresh_state *state; + state = tevent_req_data(req, struct ipa_sudo_rules_refresh_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *dp_error = state->dp_error; + *deleted = state->deleted; + + return EOK; +} + +static struct tevent_req * +ipa_sudo_ptask_full_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct ipa_sudo_ctx *sudo_ctx; + sudo_ctx = talloc_get_type(pvt, struct ipa_sudo_ctx); + + return ipa_sudo_full_refresh_send(mem_ctx, be_ctx->ev, sudo_ctx); +} + +static errno_t +ipa_sudo_ptask_full_refresh_recv(struct tevent_req *req) +{ + int dp_error; + + return ipa_sudo_full_refresh_recv(req, &dp_error); +} + +static struct tevent_req * +ipa_sudo_ptask_smart_refresh_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct be_ctx *be_ctx, + struct be_ptask *be_ptask, + void *pvt) +{ + struct ipa_sudo_ctx *sudo_ctx; + sudo_ctx = talloc_get_type(pvt, struct ipa_sudo_ctx); + + return ipa_sudo_smart_refresh_send(mem_ctx, be_ctx->ev, sudo_ctx); +} + +static errno_t +ipa_sudo_ptask_smart_refresh_recv(struct tevent_req *req) +{ + int dp_error; + + return ipa_sudo_smart_refresh_recv(req, &dp_error); +} + +errno_t +ipa_sudo_ptask_setup(struct be_ctx *be_ctx, struct ipa_sudo_ctx *sudo_ctx) +{ + return sdap_sudo_ptask_setup_generic(be_ctx, sudo_ctx->id_ctx->opts->basic, + ipa_sudo_ptask_full_refresh_send, + ipa_sudo_ptask_full_refresh_recv, + ipa_sudo_ptask_smart_refresh_send, + ipa_sudo_ptask_smart_refresh_recv, + sudo_ctx, + &sudo_ctx->full_refresh, + &sudo_ctx->smart_refresh); +} diff --git a/src/providers/ipa/ipa_utils.c b/src/providers/ipa/ipa_utils.c new file mode 100644 index 0000000..86ba51c --- /dev/null +++ b/src/providers/ipa/ipa_utils.c @@ -0,0 +1,63 @@ +/* + SSSD + + IPA Module utility functions + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2014 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" + +#define OVERRIDE_ANCHOR_IPA_PREFIX ":IPA:" +#define OVERRIDE_ANCHOR_IPA_PREFIX_LEN (sizeof(OVERRIDE_ANCHOR_IPA_PREFIX) -1 ) + +errno_t split_ipa_anchor(TALLOC_CTX *mem_ctx, const char *anchor, + char **_anchor_domain, char **_ipa_uuid) +{ + const char *sep; + + if (anchor == NULL) { + return EINVAL; + } + if (strncmp(OVERRIDE_ANCHOR_IPA_PREFIX, anchor, + OVERRIDE_ANCHOR_IPA_PREFIX_LEN) != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "No IPA anchor [%s].\n", anchor); + return ENOMSG; + } + + sep = strchr(anchor + OVERRIDE_ANCHOR_IPA_PREFIX_LEN, ':'); + if (sep == NULL || sep[1] == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Broken IPA anchor [%s].\n", anchor); + return EINVAL; + } + + *_anchor_domain = talloc_strndup(mem_ctx, + anchor + OVERRIDE_ANCHOR_IPA_PREFIX_LEN, + sep - anchor - OVERRIDE_ANCHOR_IPA_PREFIX_LEN); + *_ipa_uuid = talloc_strdup(mem_ctx, sep + 1); + + if (*_anchor_domain == NULL || *_ipa_uuid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + talloc_free(*_anchor_domain); + talloc_free(*_ipa_uuid); + return ENOMEM; + } + + return EOK; +} diff --git a/src/providers/ipa/ipa_views.c b/src/providers/ipa/ipa_views.c new file mode 100644 index 0000000..3e58949 --- /dev/null +++ b/src/providers/ipa/ipa_views.c @@ -0,0 +1,653 @@ +/* + SSSD + + IPA Identity Backend Module for views and overrides + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2014 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "util/util.h" +#include "util/strtonum.h" +#include "util/cert.h" +#include "providers/ldap/sdap_async.h" +#include "providers/ipa/ipa_id.h" +#include "db/sysdb.h" + +#define MAX_USER_AND_GROUP_REPLIES 2 + +static errno_t get_user_or_group(TALLOC_CTX *mem_ctx, + struct ipa_options *ipa_opts, + struct sysdb_attrs *attrs, + enum sysdb_obj_type *_what_is) +{ + errno_t ret; + const char **values; + const char **value; + bool is_user = false; + bool is_group = false; + const char *ov_user_name = ipa_opts->override_map[IPA_OC_OVERRIDE_USER].name; + const char *ov_group_name = ipa_opts->override_map[IPA_OC_OVERRIDE_GROUP].name; + + ret = sysdb_attrs_get_string_array(attrs, SYSDB_ORIG_OBJECTCLASS, mem_ctx, &values); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to retrieve attribute [%s].\n", + SYSDB_ORIG_OBJECTCLASS); + return ret; + } + + /* We assume an entry can be a user or a group override but not both. + * So we leave as soon as we identify one of them. */ + if (values != NULL) { + for (value = values; *value != NULL; value++) { + if (strcasecmp(*value, ov_user_name) == 0) { + is_user = true; + break; + } else if (strcasecmp(*value, ov_group_name) == 0) { + is_group = true; + break; + } + } + talloc_free(values); + } + + /* We also assume it must be necessarily a user or a group. */ + if (!is_user && !is_group) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected override found.\n"); + return EINVAL; + } + + if (_what_is != NULL) { + *_what_is = is_user ? SYSDB_USER : SYSDB_GROUP; + } + + return EOK; +} + +/* Verify there are exactly 1 user and 1 group override. Any other combination + * is wrong. Then keep only the group override. */ +static errno_t check_and_filter_user_and_group(struct ipa_options *ipa_opts, + struct sysdb_attrs **reply, + size_t *reply_count) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + enum sysdb_obj_type entry_is[MAX_USER_AND_GROUP_REPLIES]; + int i; + + if (*reply_count != MAX_USER_AND_GROUP_REPLIES) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Expected %i replies but got %lu\n", + MAX_USER_AND_GROUP_REPLIES, *reply_count); + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate memory.\n"); + return ENOMEM; + } + + for (i = 0; i < MAX_USER_AND_GROUP_REPLIES; i++) { + ret = get_user_or_group(tmp_ctx, ipa_opts, reply[i], &entry_is[i]); + if (ret != EOK) { + goto done; + } + } + + if (entry_is[0] == SYSDB_USER && entry_is[1] == SYSDB_USER) { + DEBUG(SSSDBG_CRIT_FAILURE, "Found 2 user overrides.\n"); + ret = EINVAL; + goto done; + } else if (entry_is[0] == SYSDB_GROUP && entry_is[1] == SYSDB_GROUP) { + DEBUG(SSSDBG_CRIT_FAILURE, "Found 2 group overrides.\n"); + ret = EINVAL; + goto done; + } + + /* We have one user and one group override. Keep only the group override. */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Keeping only the group override.\n"); + if (entry_is[0] == SYSDB_USER) { + talloc_free(reply[0]); + reply[0] = reply[1]; + } else { + talloc_free(reply[1]); + } + reply[1] = NULL; + *reply_count = 1; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t dp_id_data_to_override_filter(TALLOC_CTX *mem_ctx, + struct ipa_options *ipa_opts, + struct dp_id_data *ar, + char **override_filter) +{ + char *filter; + uint32_t id; + char *endptr; + char *cert_filter; + int ret; + char *shortname; + char *sanitized_name; + + switch (ar->filter_type) { + case BE_FILTER_NAME: + ret = sss_parse_internal_fqname(mem_ctx, ar->filter_value, + &shortname, NULL); + if (ret != EOK) { + return ret; + } + + ret = sss_filter_sanitize(mem_ctx, shortname, &sanitized_name); + talloc_free(shortname); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_filter_sanitize failed.\n"); + return ret; + } + + switch ((ar->entry_type & BE_REQ_TYPE_MASK)) { + case BE_REQ_USER: + case BE_REQ_INITGROUPS: + filter = talloc_asprintf(mem_ctx, "(&(objectClass=%s)(%s=%s))", + ipa_opts->override_map[IPA_OC_OVERRIDE_USER].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_USER_NAME].name, + sanitized_name); + break; + + case BE_REQ_GROUP: + filter = talloc_asprintf(mem_ctx, "(&(objectClass=%s)(%s=%s))", + ipa_opts->override_map[IPA_OC_OVERRIDE_GROUP].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_GROUP_NAME].name, + sanitized_name); + break; + + case BE_REQ_USER_AND_GROUP: + filter = talloc_asprintf(mem_ctx, + "(|(&(objectClass=%s)(%s=%s))(&(objectClass=%s)(%s=%s)))", + ipa_opts->override_map[IPA_OC_OVERRIDE_USER].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_USER_NAME].name, + sanitized_name, + ipa_opts->override_map[IPA_OC_OVERRIDE_GROUP].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_GROUP_NAME].name, + sanitized_name); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected entry type [%d] for name filter.\n", + ar->entry_type); + talloc_free(sanitized_name); + return EINVAL; + } + talloc_free(sanitized_name); + break; + + case BE_FILTER_IDNUM: + id = strtouint32(ar->filter_value, &endptr, 10); + if (errno != 0|| *endptr != '\0' || (ar->filter_value == endptr)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid id value [%s].\n", + ar->filter_value); + return EINVAL; + } + switch ((ar->entry_type & BE_REQ_TYPE_MASK)) { + case BE_REQ_USER: + case BE_REQ_INITGROUPS: + filter = talloc_asprintf(mem_ctx, "(&(objectClass=%s)(%s=%"PRIu32"))", + ipa_opts->override_map[IPA_OC_OVERRIDE_USER].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_UID_NUMBER].name, + id); + break; + + case BE_REQ_GROUP: + filter = talloc_asprintf(mem_ctx, + "(&(objectClass=%s)(%s=%"PRIu32"))", + ipa_opts->override_map[IPA_OC_OVERRIDE_GROUP].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_GROUP_GID_NUMBER].name, + id); + break; + + case BE_REQ_USER_AND_GROUP: + filter = talloc_asprintf(mem_ctx, + "(|(&(objectClass=%s)(%s=%"PRIu32"))(&(objectClass=%s)(%s=%"PRIu32")))", + ipa_opts->override_map[IPA_OC_OVERRIDE_USER].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_UID_NUMBER].name, + id, + ipa_opts->override_map[IPA_OC_OVERRIDE_GROUP].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_GROUP_GID_NUMBER].name, + id); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected entry type [%d] for id filter.\n", + ar->entry_type); + return EINVAL; + } + break; + + case BE_FILTER_SECID: + if ((ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_BY_SECID) { + filter = talloc_asprintf(mem_ctx, "(&(objectClass=%s)(%s=:SID:%s))", + ipa_opts->override_map[IPA_OC_OVERRIDE].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_ANCHOR_UUID].name, + ar->filter_value); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected entry type [%d] for SID filter.\n", + ar->entry_type); + return EINVAL; + } + break; + + case BE_FILTER_UUID: + if ((ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_BY_UUID) { + filter = talloc_asprintf(mem_ctx, "(&(objectClass=%s)(%s=:IPA:%s:%s))", + ipa_opts->override_map[IPA_OC_OVERRIDE].name, + ipa_opts->override_map[IPA_AT_OVERRIDE_ANCHOR_UUID].name, + dp_opt_get_string(ipa_opts->basic, IPA_DOMAIN), + ar->filter_value); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected entry type [%d] for UUID filter.\n", + ar->entry_type); + return EINVAL; + } + break; + + case BE_FILTER_CERT: + if ((ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_BY_CERT) { + ret = sss_cert_derb64_to_ldap_filter(mem_ctx, ar->filter_value, + ipa_opts->override_map[IPA_AT_OVERRIDE_USER_CERT].name, + NULL, NULL, &cert_filter); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_cert_derb64_to_ldap_filter failed.\n"); + return ret; + } + filter = talloc_asprintf(mem_ctx, "(&(objectClass=%s)%s)", + ipa_opts->override_map[IPA_OC_OVERRIDE_USER].name, + cert_filter); + talloc_free(cert_filter); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected entry type [%d] for certificate filter.\n", + ar->entry_type); + return EINVAL; + } + break; + + default: + DEBUG(SSSDBG_OP_FAILURE, "Invalid sub-domain filter type.\n"); + return EINVAL; + } + + if (filter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + return ENOMEM; + } + + *override_filter = filter; + + return EOK; +} + +static errno_t get_dp_id_data_for_xyz(TALLOC_CTX *mem_ctx, const char *val, + const char *domain_name, + int type, + struct dp_id_data **_ar) +{ + struct dp_id_data *ar; + + ar = talloc_zero(mem_ctx, struct dp_id_data); + if (ar == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + switch (type) { + case BE_REQ_BY_SECID: + ar->entry_type = BE_REQ_BY_SECID; + ar->filter_type = BE_FILTER_SECID; + break; + case BE_REQ_BY_UUID: + ar->entry_type = BE_REQ_BY_UUID; + ar->filter_type = BE_FILTER_UUID; + break; + case BE_REQ_USER: + ar->entry_type = BE_REQ_USER; + ar->filter_type = BE_FILTER_NAME; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported request type [%d].\n", type); + talloc_free(ar); + return EINVAL; + } + + ar->filter_value = talloc_strdup(ar, val); + ar->domain = talloc_strdup(ar, domain_name); + if (ar->filter_value == NULL || ar->domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); + talloc_free(ar); + return ENOMEM; + } + + + *_ar = ar; + + return EOK; +} + +errno_t get_dp_id_data_for_sid(TALLOC_CTX *mem_ctx, const char *sid, + const char *domain_name, + struct dp_id_data **_ar) +{ + return get_dp_id_data_for_xyz(mem_ctx, sid, domain_name, BE_REQ_BY_SECID, + _ar); +} + +errno_t get_dp_id_data_for_uuid(TALLOC_CTX *mem_ctx, const char *uuid, + const char *domain_name, + struct dp_id_data **_ar) +{ + return get_dp_id_data_for_xyz(mem_ctx, uuid, domain_name, BE_REQ_BY_UUID, + _ar); +} + +errno_t get_dp_id_data_for_user_name(TALLOC_CTX *mem_ctx, + const char *user_name, + const char *domain_name, + struct dp_id_data **_ar) +{ + return get_dp_id_data_for_xyz(mem_ctx, user_name, domain_name, BE_REQ_USER, + _ar); +} + +struct ipa_get_ad_override_state { + struct tevent_context *ev; + struct sdap_id_ctx *sdap_id_ctx; + struct ipa_options *ipa_options; + const char *ipa_realm; + const char *ipa_view_name; + struct dp_id_data *ar; + + struct sdap_id_op *sdap_op; + int dp_error; + struct sysdb_attrs *override_attrs; + char *filter; +}; + +static void ipa_get_ad_override_connect_done(struct tevent_req *subreq); +static errno_t ipa_get_ad_override_qualify_name( + struct ipa_get_ad_override_state *state); +static void ipa_get_ad_override_done(struct tevent_req *subreq); + +struct tevent_req *ipa_get_ad_override_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sdap_id_ctx *sdap_id_ctx, + struct ipa_options *ipa_options, + const char *ipa_realm, + const char *view_name, + struct dp_id_data *ar) +{ + int ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct ipa_get_ad_override_state *state; + + req = tevent_req_create(mem_ctx, &state, struct ipa_get_ad_override_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->sdap_id_ctx = sdap_id_ctx; + state->ipa_options = ipa_options; + state->ipa_realm = ipa_realm; + state->ar = ar; + state->dp_error = -1; + state->override_attrs = NULL; + state->filter = NULL; + + if (view_name == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "View not defined, nothing to do.\n"); + ret = EOK; + goto done; + } + + if (is_default_view(view_name)) { + state->ipa_view_name = IPA_DEFAULT_VIEW_NAME; + } else { + state->ipa_view_name = view_name; + } + + state->sdap_op = sdap_id_op_create(state, + state->sdap_id_ctx->conn->conn_cache); + if (state->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n"); + ret = ENOMEM; + goto done; + } + + subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed: %d(%s).\n", + ret, strerror(ret)); + goto done; + } + + tevent_req_set_callback(subreq, ipa_get_ad_override_connect_done, req); + + return req; + +done: + if (ret != EOK) { + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + } else { + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + } + tevent_req_post(req, state->ev); + + return req; +} + +static void ipa_get_ad_override_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_ad_override_state *state = tevent_req_data(req, + struct ipa_get_ad_override_state); + int ret; + char *basedn; + char *search_base; + struct ipa_options *ipa_opts = state->ipa_options; + + ret = sdap_id_op_connect_recv(subreq, &state->dp_error); + talloc_zfree(subreq); + if (ret != EOK) { + if (state->dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, + "No IPA server is available, going offline\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to connect to IPA server: [%d](%s)\n", + ret, strerror(ret)); + } + + goto fail; + } + + ret = domain_to_basedn(state, state->ipa_realm, &basedn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "domain_to_basedn failed.\n"); + goto fail; + } + + search_base = talloc_asprintf(state, "cn=%s,%s", state->ipa_view_name, + ipa_opts->views_search_bases[0]->basedn); + if (search_base == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto fail; + } + + ret = dp_id_data_to_override_filter(state, state->ipa_options, state->ar, + &state->filter); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "dp_id_data_to_override_filter failed.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Searching for overrides in view [%s] with filter [%s].\n", + state->ipa_view_name, state->filter); + + subreq = sdap_get_generic_send(state, state->ev, state->sdap_id_ctx->opts, + sdap_id_op_handle(state->sdap_op), search_base, + LDAP_SCOPE_SUBTREE, + state->filter, NULL, + state->ipa_options->override_map, + IPA_OPTS_OVERRIDE, + dp_opt_get_int(state->sdap_id_ctx->opts->basic, + SDAP_SEARCH_TIMEOUT), + false); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_send failed.\n"); + ret = ENOMEM; + goto fail; + } + + tevent_req_set_callback(subreq, ipa_get_ad_override_done, req); + return; + +fail: + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + return; +} + +static void ipa_get_ad_override_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ipa_get_ad_override_state *state = tevent_req_data(req, + struct ipa_get_ad_override_state); + int ret; + size_t reply_count = 0; + struct sysdb_attrs **reply = NULL; + + ret = sdap_get_generic_recv(subreq, state, &reply_count, &reply); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ipa_get_ad_override request failed.\n"); + goto fail; + } + + if (reply_count == 0) { + DEBUG(SSSDBG_TRACE_ALL, "No override found with filter [%s].\n", + state->filter); + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + } else if (reply_count == MAX_USER_AND_GROUP_REPLIES && + (state->ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_USER_AND_GROUP) { + DEBUG(SSSDBG_TRACE_ALL, + "Found two overrides with BE_REQ_USER_AND_GROUP filter [%s].\n", + state->filter); + ret = check_and_filter_user_and_group(state->ipa_options, reply, + &reply_count); + if (ret != EOK) { + goto fail; + } + } else if (reply_count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Found [%zu] overrides with filter [%s], expected only 1.\n", + reply_count, state->filter); + ret = EINVAL; + goto fail; + } + + DEBUG(SSSDBG_TRACE_ALL, "Found override for object with filter [%s].\n", + state->filter); + state->override_attrs = reply[0]; + + ret = ipa_get_ad_override_qualify_name(state); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot qualify object name\n"); + goto fail; + } + + state->dp_error = DP_ERR_OK; + tevent_req_done(req); + return; + +fail: + state->dp_error = DP_ERR_FATAL; + tevent_req_error(req, ret); + return; +} + +static errno_t ipa_get_ad_override_qualify_name( + struct ipa_get_ad_override_state *state) +{ + int ret; + struct ldb_message_element *name; + char *fqdn; + + ret = sysdb_attrs_get_el_ext(state->override_attrs, SYSDB_NAME, + false, &name); + if (ret == ENOENT) { + return EOK; /* Does not override name */ + } else if (ret != EOK && ret != ENOENT) { + return ret; + } + + fqdn = sss_create_internal_fqname(name->values, + (const char *) name->values[0].data, + state->ar->domain); + if (fqdn == NULL) { + return ENOMEM; + } + + name->values[0].data = (uint8_t *) fqdn; + name->values[0].length = strlen(fqdn); + return EOK; +} + +errno_t ipa_get_ad_override_recv(struct tevent_req *req, int *dp_error_out, + TALLOC_CTX *mem_ctx, + struct sysdb_attrs **override_attrs) +{ + struct ipa_get_ad_override_state *state = tevent_req_data(req, + struct ipa_get_ad_override_state); + + if (dp_error_out != NULL) { + *dp_error_out = state->dp_error; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (override_attrs != NULL) { + *override_attrs = talloc_steal(mem_ctx, state->override_attrs); + } + + return EOK; +} diff --git a/src/providers/ipa/selinux_child.c b/src/providers/ipa/selinux_child.c new file mode 100644 index 0000000..063bea4 --- /dev/null +++ b/src/providers/ipa/selinux_child.c @@ -0,0 +1,422 @@ +/* + SSSD + + IPA back end -- set SELinux context in a child module + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2014 Red Hat + + 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 <http://www.gnu.org/licenses/>. +*/ + + +#include <sys/types.h> +#include <unistd.h> +#include <sys/stat.h> +#include <popt.h> +#include <sys/prctl.h> + +#include "util/util.h" +#include "util/child_common.h" +#include "util/sss_chain_id.h" +#include "providers/backend.h" + +struct input_buffer { + const char *seuser; + const char *mls_range; + const char *username; +}; + +static errno_t unpack_buffer(uint8_t *buf, + size_t size, + struct input_buffer *ibuf) +{ + size_t p = 0; + uint32_t len; + + /* seuser */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + DEBUG(SSSDBG_TRACE_INTERNAL, "seuser length: %d\n", len); + if (len == 0) { + ibuf->seuser = ""; + DEBUG(SSSDBG_TRACE_INTERNAL, + "Empty SELinux user, will delete the mapping\n"); + } else { + if (len > size - p) return EINVAL; + ibuf->seuser = talloc_strndup(ibuf, (char *)(buf + p), len); + if (ibuf->seuser == NULL) return ENOMEM; + DEBUG(SSSDBG_TRACE_INTERNAL, "seuser: %s\n", ibuf->seuser); + p += len; + } + + /* MLS range */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + DEBUG(SSSDBG_TRACE_INTERNAL, "mls_range length: %d\n", len); + if (len == 0) { + if (strcmp(ibuf->seuser, "") != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "No MLS mapping!\n"); + return EINVAL; + } + } else { + if (len > size - p) return EINVAL; + ibuf->mls_range = talloc_strndup(ibuf, (char *)(buf + p), len); + if (ibuf->mls_range == NULL) return ENOMEM; + DEBUG(SSSDBG_TRACE_INTERNAL, "mls_range: %s\n", ibuf->mls_range); + p += len; + } + + /* username */ + SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p); + DEBUG(SSSDBG_TRACE_INTERNAL, "username length: %d\n", len); + if (len == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "No username set!\n"); + return EINVAL; + } else { + if (len > size - p) return EINVAL; + ibuf->username = talloc_strndup(ibuf, (char *)(buf + p), len); + if (ibuf->username == NULL) return ENOMEM; + DEBUG(SSSDBG_TRACE_INTERNAL, "username: %s\n", ibuf->username); + p += len; + } + + return EOK; +} + +static errno_t pack_buffer(struct response *r, int result) +{ + size_t p = 0; + + /* A buffer with the following structure must be created: + * uint32_t status of the request (required) + */ + r->size = sizeof(uint32_t); + + r->buf = talloc_array(r, uint8_t, r->size); + if(r->buf == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_FUNC, "result [%d]\n", result); + + /* result */ + SAFEALIGN_SET_UINT32(&r->buf[p], result, &p); + + return EOK; +} + +static errno_t prepare_response(TALLOC_CTX *mem_ctx, + int result, + struct response **rsp) +{ + int ret; + struct response *r = NULL; + + r = talloc_zero(mem_ctx, struct response); + if (r == NULL) { + return ENOMEM; + } + + r->buf = NULL; + r->size = 0; + + ret = pack_buffer(r, result); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pack_buffer failed\n"); + return ret; + } + + *rsp = r; + DEBUG(SSSDBG_TRACE_ALL, "r->size: %zu\n", r->size); + return EOK; +} + +static int sc_set_seuser(const char *login_name, const char *seuser_name, + const char *mls) +{ + int ret; + mode_t old_mask; + + /* Bug origin: https://bugzilla.redhat.com/show_bug.cgi?id=1186422 + * This workaround is required for libsemanage < 2.5-13.el7 + * It will remain here as a precaution in case of unexpected + * libsemanage behaviour. + */ + old_mask = umask(0); + if (strcmp(seuser_name, "") == 0) { + /* An empty SELinux user should cause SSSD to use the system + * default. We need to remove the SELinux user from the DB + * in that case + */ + ret = sss_del_seuser(login_name); + } else { + ret = sss_set_seuser(login_name, seuser_name, mls); + } + umask(old_mask); + return ret; +} + +static bool seuser_needs_update(const char *username, + const char *seuser, + const char *mls_range) +{ + bool needs_update = true; + char *db_seuser = NULL; + char *db_mls_range = NULL; + errno_t ret; + + ret = sss_get_seuser(username, &db_seuser, &db_mls_range); + DEBUG(SSSDBG_TRACE_INTERNAL, + "sss_get_seuser: ret: %d seuser: %s mls: %s\n", + ret, db_seuser ? db_seuser : "unknown", + db_mls_range ? db_mls_range : "unknown"); + if (ret == EOK && db_seuser && db_mls_range && + strcmp(db_seuser, seuser) == 0 && + strcmp(db_mls_range, mls_range) == 0) { + ret = sss_seuser_exists(username); + if (ret == EOK) { + needs_update = false; + } + } + /* OR */ + if (ret == ERR_SELINUX_NOT_MANAGED) { + needs_update = false; + } + + free(db_seuser); + free(db_mls_range); + DEBUG(SSSDBG_TRACE_FUNC, + "The SELinux user does %sneed an update\n", + needs_update ? "" : "not "); + return needs_update; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + int debug_fd = -1; + int dumpable = 1; + errno_t ret; + TALLOC_CTX *main_ctx = NULL; + uint8_t *buf = NULL; + ssize_t len = 0; + struct input_buffer *ibuf = NULL; + struct response *resp = NULL; + struct passwd *passwd = NULL; + ssize_t written; + bool needs_update; + const char *username; + const char *opt_logger = NULL; + long chain_id; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + {"dumpable", 0, POPT_ARG_INT, &dumpable, 0, + _("Allow core dumps"), NULL }, + {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, + _("An open file descriptor for the debug logs"), NULL}, + {"chain-id", 0, POPT_ARG_LONG, &chain_id, + 0, _("Tevent chain ID used for logging purposes"), NULL}, + SSSD_LOGGER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + } + + poptFreeContext(pc); + + prctl(PR_SET_DUMPABLE, (dumpable == 0) ? 0 : 1); + + debug_prg_name = talloc_asprintf(NULL, "selinux_child[%d]", getpid()); + if (debug_prg_name == NULL) { + ERROR("talloc_asprintf failed.\n"); + goto fail; + } + + if (debug_fd != -1) { + opt_logger = sss_logger_str[FILES_LOGGER]; + ret = set_debug_file_from_fd(debug_fd); + if (ret != EOK) { + opt_logger = sss_logger_str[STDERR_LOGGER]; + ERROR("set_debug_file_from_fd failed.\n"); + } + } + + sss_chain_id_set_format(DEBUG_CHAIN_ID_FMT_RID); + sss_chain_id_set((uint64_t)chain_id); + + DEBUG_INIT(debug_level, opt_logger); + + DEBUG(SSSDBG_TRACE_FUNC, "selinux_child started.\n"); + DEBUG(SSSDBG_TRACE_INTERNAL, + "Running with effective IDs: [%"SPRIuid"][%"SPRIgid"].\n", + geteuid(), getegid()); + + /* The functions semanage_genhomedircon and getseuserbyname use gepwnam_r + * and they might fail to return values if they are not in memory cache. + * [main] (0x0400): performing selinux operations + * [seuser_needs_update] (0x2000): getseuserbyname: ret: 0 + * seuser: unconfined_u mls: s0-s0:c0.c15 + * [libsemanage] (0x0020): semanage_genhomedircon returned error code -1. + * [sss_set_seuser] (0x0020): Cannot commit SELinux transaction + * [main] (0x0020): Cannot set SELinux login context. + * [main] (0x0020): selinux_child failed! + */ + if (unsetenv("_SSS_LOOPS") != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to unset _SSS_LOOPS, some libsemanage functions might " + "fail.\n"); + } + + /* libsemanage calls access(2) which works with real IDs, not effective. + * We need to switch also the real ID to 0. + */ + if (getuid() != 0) { + ret = setuid(0); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "setuid failed: %d, selinux_child might not work!\n", ret); + } + } + + if (getgid() != 0) { + ret = setgid(0); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "setgid failed: %d, selinux_child might not work!\n", ret); + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Running with real IDs [%"SPRIuid"][%"SPRIgid"].\n", + getuid(), getgid()); + + main_ctx = talloc_new(NULL); + if (main_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + talloc_free(discard_const(debug_prg_name)); + goto fail; + } + talloc_steal(main_ctx, debug_prg_name); + + buf = talloc_size(main_ctx, sizeof(uint8_t)*IN_BUF_SIZE); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + goto fail; + } + + ibuf = talloc_zero(main_ctx, struct input_buffer); + if (ibuf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "context initialized\n"); + + errno = 0; + len = sss_atomic_read_s(STDIN_FILENO, buf, IN_BUF_SIZE); + if (len == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "read failed [%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + close(STDIN_FILENO); + + ret = unpack_buffer(buf, len, ibuf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "unpack_buffer failed.[%d][%s].\n", ret, strerror(ret)); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "performing selinux operations\n"); + + /* When using domain_resolution_order the username will always be + * fully-qualified, what has been causing some SELinux issues as mappings + * for user 'admin' are not applied for 'admin@ipa.example'. + * + * In order to work this around we can take advantage that selinux_child + * queries SSSD since commit 92addd7ba and call getpwnam() in order to get + * the username in the correct format. */ + passwd = getpwnam(ibuf->username); + if (passwd == NULL) { + username = ibuf->username; + DEBUG(SSSDBG_MINOR_FAILURE, + "getpwnam() failed to get info for the user \"%s\". SELinux label " + "setting might fail as well!\n", + ibuf->username); + } else { + username = passwd->pw_name; + } + + needs_update = seuser_needs_update(username, ibuf->seuser, + ibuf->mls_range); + if (needs_update == true) { + ret = sc_set_seuser(username, ibuf->seuser, ibuf->mls_range); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot set SELinux login context.\n"); + goto fail; + } + } + + ret = prepare_response(main_ctx, ret, &resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to prepare response buffer.\n"); + goto fail; + } + + errno = 0; + + written = sss_atomic_write_s(STDOUT_FILENO, resp->buf, resp->size); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "write failed [%d][%s].\n", ret, + strerror(ret)); + goto fail; + } + + if (written != resp->size) { + DEBUG(SSSDBG_CRIT_FAILURE, "Expected to write %zu bytes, wrote %zu\n", + resp->size, written); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "selinux_child completed successfully\n"); + close(STDOUT_FILENO); + talloc_free(main_ctx); + return EXIT_SUCCESS; +fail: + DEBUG(SSSDBG_CRIT_FAILURE, "selinux_child failed!\n"); + close(STDOUT_FILENO); + talloc_free(main_ctx); + return EXIT_FAILURE; +} |