summaryrefslogtreecommitdiffstats
path: root/src/providers/ipa
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
commit74aa0bc6779af38018a03fd2cf4419fe85917904 (patch)
tree9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/providers/ipa
parentInitial commit. (diff)
downloadsssd-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')
-rw-r--r--src/providers/ipa/ipa_access.c787
-rw-r--r--src/providers/ipa/ipa_access.h76
-rw-r--r--src/providers/ipa/ipa_auth.c478
-rw-r--r--src/providers/ipa/ipa_auth.h42
-rw-r--r--src/providers/ipa/ipa_autofs.c65
-rw-r--r--src/providers/ipa/ipa_common.c1370
-rw-r--r--src/providers/ipa/ipa_common.h326
-rw-r--r--src/providers/ipa/ipa_config.c165
-rw-r--r--src/providers/ipa/ipa_config.h55
-rw-r--r--src/providers/ipa/ipa_deskprofile_config.c156
-rw-r--r--src/providers/ipa/ipa_deskprofile_config.h45
-rw-r--r--src/providers/ipa/ipa_deskprofile_private.h50
-rw-r--r--src/providers/ipa/ipa_deskprofile_rules.c367
-rw-r--r--src/providers/ipa/ipa_deskprofile_rules.h43
-rw-r--r--src/providers/ipa/ipa_deskprofile_rules_util.c1147
-rw-r--r--src/providers/ipa/ipa_deskprofile_rules_util.h74
-rw-r--r--src/providers/ipa/ipa_dn.c145
-rw-r--r--src/providers/ipa/ipa_dn.h43
-rw-r--r--src/providers/ipa/ipa_dyndns.c269
-rw-r--r--src/providers/ipa/ipa_dyndns.h35
-rw-r--r--src/providers/ipa/ipa_hbac_common.c748
-rw-r--r--src/providers/ipa/ipa_hbac_hosts.c335
-rw-r--r--src/providers/ipa/ipa_hbac_private.h132
-rw-r--r--src/providers/ipa/ipa_hbac_rules.c313
-rw-r--r--src/providers/ipa/ipa_hbac_rules.h41
-rw-r--r--src/providers/ipa/ipa_hbac_services.c686
-rw-r--r--src/providers/ipa/ipa_hbac_users.c369
-rw-r--r--src/providers/ipa/ipa_hostid.c30
-rw-r--r--src/providers/ipa/ipa_hosts.c365
-rw-r--r--src/providers/ipa/ipa_hosts.h44
-rw-r--r--src/providers/ipa/ipa_id.c1562
-rw-r--r--src/providers/ipa/ipa_id.h159
-rw-r--r--src/providers/ipa/ipa_idmap.c521
-rw-r--r--src/providers/ipa/ipa_init.c960
-rw-r--r--src/providers/ipa/ipa_netgroups.c1056
-rw-r--r--src/providers/ipa/ipa_opts.c428
-rw-r--r--src/providers/ipa/ipa_opts.h71
-rw-r--r--src/providers/ipa/ipa_refresh.c220
-rw-r--r--src/providers/ipa/ipa_rules_common.c455
-rw-r--r--src/providers/ipa/ipa_rules_common.h89
-rw-r--r--src/providers/ipa/ipa_s2n_exop.c3228
-rw-r--r--src/providers/ipa/ipa_selinux.c1698
-rw-r--r--src/providers/ipa/ipa_selinux.h50
-rw-r--r--src/providers/ipa/ipa_selinux_maps.c222
-rw-r--r--src/providers/ipa/ipa_selinux_maps.h45
-rw-r--r--src/providers/ipa/ipa_session.c861
-rw-r--r--src/providers/ipa/ipa_session.h54
-rw-r--r--src/providers/ipa/ipa_srv.c224
-rw-r--r--src/providers/ipa/ipa_srv.h48
-rw-r--r--src/providers/ipa/ipa_subdomains.c3180
-rw-r--r--src/providers/ipa/ipa_subdomains.h177
-rw-r--r--src/providers/ipa/ipa_subdomains_ext_groups.c1213
-rw-r--r--src/providers/ipa/ipa_subdomains_id.c1827
-rw-r--r--src/providers/ipa/ipa_subdomains_passkey.c146
-rw-r--r--src/providers/ipa/ipa_subdomains_passkey.h45
-rw-r--r--src/providers/ipa/ipa_subdomains_server.c1215
-rw-r--r--src/providers/ipa/ipa_subdomains_utils.c100
-rw-r--r--src/providers/ipa/ipa_sudo.c337
-rw-r--r--src/providers/ipa/ipa_sudo.h134
-rw-r--r--src/providers/ipa/ipa_sudo_async.c1141
-rw-r--r--src/providers/ipa/ipa_sudo_conversion.c1369
-rw-r--r--src/providers/ipa/ipa_sudo_refresh.c470
-rw-r--r--src/providers/ipa/ipa_utils.c63
-rw-r--r--src/providers/ipa/ipa_views.c653
-rw-r--r--src/providers/ipa/selinux_child.c422
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;
+}