summaryrefslogtreecommitdiffstats
path: root/src/providers/ipa/ipa_hbac_services.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/ipa/ipa_hbac_services.c')
-rw-r--r--src/providers/ipa/ipa_hbac_services.c686
1 files changed, 686 insertions, 0 deletions
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;
+}