/* Copyright (C) 2017 Red Hat This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #include #include "util/util.h" #include "tools/common/sss_tools.h" #include "tools/sssctl/sssctl.h" /* * We're searching the cache directly.. */ #include "providers/ipa/ipa_hbac_private.h" #include "providers/ipa/ipa_rules_common.h" #ifdef HAVE_SECURITY_PAM_MISC_H # include #elif defined(HAVE_SECURITY_OPENPAM_H) # include #endif #ifdef HAVE_SECURITY_PAM_MISC_H static struct pam_conv conv = { misc_conv, NULL }; #elif defined(HAVE_SECURITY_OPENPAM_H) static struct pam_conv conv = { openpam_ttyconv, NULL }; #else # error "Missing text based pam conversation function" #endif #ifndef DEFAULT_SERVICE #define DEFAULT_SERVICE "system-auth" #endif /* DEFAULT_SERVICE */ #ifndef DEFAULT_USER #define DEFAULT_USER "admin" #endif /* DEFAULT_USER */ typedef errno_t (*sssctl_dom_access_reporter_fn)(struct sss_tool_ctx *tool_ctx, const char *user, const char *service, struct sss_domain_info *domain); static errno_t run_pam_acct(struct sss_tool_ctx *tool_ctx, const char *user, const char *service, struct sss_domain_info *domain) { errno_t ret; pam_handle_t *pamh; ret = pam_start(service, user, &conv, &pamh); if (ret != PAM_SUCCESS) { ERROR("pam_start failed: %s\n", pam_strerror(pamh, ret)); return EIO; } ret = pam_acct_mgmt(pamh, 0); pam_end(pamh, ret); return ret; } static errno_t get_rdn_value(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom, const char *dn_attr, const char **_rdn_value) { errno_t ret; TALLOC_CTX *tmp_ctx; struct ldb_dn *dn = NULL; const struct ldb_val *rdn_val; const char *rdn_str; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } dn = ldb_dn_new(tmp_ctx, sysdb_ctx_get_ldb(dom->sysdb), dn_attr); if (dn == NULL) { ret = ENOMEM; goto done; } rdn_val = ldb_dn_get_rdn_val(dn); if (rdn_val == NULL) { DEBUG(SSSDBG_OP_FAILURE, "No RDN value?\n"); ret = ENOMEM; goto done; } rdn_str = talloc_strndup(tmp_ctx, (const char *)rdn_val->data, rdn_val->length); if (rdn_str == NULL) { ret = ENOMEM; goto done; } ret = EOK; *_rdn_value = talloc_steal(mem_ctx, rdn_str); done: talloc_zfree(tmp_ctx); return ret; } static errno_t is_member_group(struct sss_domain_info *dom, const char *dn_attr, const char *group_rdn, bool *_is_group) { const char *comp_name; const struct ldb_val *comp_val; TALLOC_CTX *tmp_ctx; bool is_group = false; errno_t ret; struct ldb_dn *dn = NULL; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return ENOMEM; } dn = ldb_dn_new(tmp_ctx, sysdb_ctx_get_ldb(dom->sysdb), dn_attr); if (dn == NULL) { ret = ENOMEM; goto done; } comp_name = ldb_dn_get_component_name(dn, 1); comp_val = ldb_dn_get_component_val(dn, 1); if (strcasecmp("cn", comp_name) == 0 && strncasecmp(group_rdn, (const char *) comp_val->data, comp_val->length) == 0) { is_group = true; } ret = EOK; done: *_is_group = is_group; talloc_zfree(tmp_ctx); return ret; } static void print_category(struct sss_domain_info *domain, struct ldb_message *rule_msg, const char *category_attr_name, const char *category_label) { struct ldb_message_element *category_attr; category_attr = ldb_msg_find_element(rule_msg, category_attr_name); if (category_attr == NULL) { DEBUG(SSSDBG_MINOR_FAILURE, "Cannot find %s\n", category_attr_name); return; } if (category_attr->num_values > 0) { PRINT("\t%s: ", category_label); for (unsigned i = 0; i < category_attr->num_values; i++) { PRINT("%s%s", i > 0 ? ", " : "", (const char *) category_attr->values[i].data); } PRINT("\n"); } } static void print_member_attr(struct sss_domain_info *domain, struct ldb_message *rule_msg, const char *member_attr_name, const char *group_rdn, const char *object_label, const char *group_label) { errno_t ret; TALLOC_CTX *tmp_ctx = NULL; const char **member_names = NULL; size_t name_count = 0; const char **member_group_names = NULL; size_t group_count = 0; struct ldb_message_element *member_attr = NULL; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return; } member_attr = ldb_msg_find_element(rule_msg, member_attr_name); if (member_attr == NULL) { DEBUG(SSSDBG_MINOR_FAILURE, "Cannot find %s\n", member_attr_name); goto done; } member_names = talloc_zero_array(tmp_ctx, const char *, member_attr->num_values + 1); member_group_names = talloc_zero_array(tmp_ctx, const char *, member_attr->num_values + 1); if (member_names == NULL || member_group_names == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "OOM?\n"); goto done; } for (size_t i = 0; i < member_attr->num_values; i++) { bool is_group; const char *rdn_string; const char *dn_attr; dn_attr = (const char *) member_attr->values[i].data; ret = is_member_group(domain, dn_attr, group_rdn, &is_group); if (ret != EOK) { continue; } ret = get_rdn_value(tmp_ctx, domain, dn_attr, &rdn_string); if (ret != EOK) { continue; } if (is_group == false) { member_names[name_count] = talloc_steal(member_names, rdn_string); if (member_names[name_count] == NULL) { goto done; } name_count++; } else { member_group_names[group_count] = talloc_strdup(member_group_names, rdn_string); if (member_group_names[group_count] == NULL) { goto done; } group_count++; } } if (member_names[0] != NULL) { PRINT("\t%s: ", object_label); for (int i = 0; member_names[i]; i++) { PRINT("%s%s", i > 0 ? ", " : "", member_names[i]); } PRINT("\n"); } if (member_group_names[0] != NULL) { PRINT("\t%s: ", group_label); for (int i = 0; member_group_names[i]; i++) { PRINT("%s%s", i > 0 ? ", " : "", member_group_names[i]); } PRINT("\n"); } done: talloc_free(tmp_ctx); } static void print_ipa_hbac_rule(struct sss_domain_info *domain, struct ldb_message *rule_msg) { struct ldb_message_element *el; el = ldb_msg_find_element(rule_msg, IPA_CN); if (el == NULL || el->num_values < 1) { DEBUG(SSSDBG_MINOR_FAILURE, "A rule with no name\n"); return; } PRINT("Rule name: %1$s\n", el->values[0].data); print_member_attr(domain, rule_msg, IPA_MEMBER_USER, "groups", _("Member users"), _("Member groups")); print_category(domain, rule_msg, IPA_USER_CATEGORY, _("User category")); print_member_attr(domain, rule_msg, IPA_MEMBER_SERVICE, "hbacservicegroups", _("Member services"), _("Member service groups")); print_category(domain, rule_msg, IPA_SERVICE_CATEGORY, _("Service category")); PRINT("\n"); } static errno_t sssctl_ipa_access_report(struct sss_tool_ctx *tool_ctx, const char *user, const char *service, struct sss_domain_info *domain) { TALLOC_CTX *tmp_ctx = NULL; const char *filter = NULL; errno_t ret; const char *attrs[] = { OBJECTCLASS, IPA_CN, IPA_MEMBER_USER, IPA_USER_CATEGORY, IPA_MEMBER_SERVICE, IPA_SERVICE_CATEGORY, IPA_MEMBER_HOST, IPA_HOST_CATEGORY, NULL, }; size_t rule_count; struct ldb_message **msgs = NULL; /* Run the pam account phase to make sure the rules are fetched by SSSD */ ret = run_pam_acct(tool_ctx, user, service, domain); if (ret != PAM_SUCCESS && ret != PAM_PERM_DENIED) { ERROR("Cannot run the PAM account phase, reporting stale rules\n"); /* Non-fatal */ } tmp_ctx = talloc_new(tool_ctx); if (tmp_ctx == NULL) { return ENOMEM; } filter = talloc_asprintf(tmp_ctx, "(objectClass=%s)", IPA_HBAC_RULE); if (filter == NULL) { ret = ENOMEM; goto done; } ret = sysdb_search_custom(tmp_ctx, domain, filter, HBAC_RULES_SUBDIR, attrs, &rule_count, &msgs); if (ret != EOK && ret != ENOENT) { DEBUG(SSSDBG_CRIT_FAILURE, "Error looking up HBAC rules\n"); goto done; } if (ret == ENOENT) { PRINT("No cached rules. All users will be denied access\n"); ret = EOK; goto done; } PRINT("%1$zu rules cached\n\n", rule_count); for (size_t i = 0; i < rule_count; i++) { print_ipa_hbac_rule(domain, msgs[i]); } ret = EOK; done: talloc_zfree(tmp_ctx); return ret; } sssctl_dom_access_reporter_fn get_report_fn(const char *provider) { if (strcmp(provider, "ipa") == 0) { return sssctl_ipa_access_report; } return NULL; } errno_t sssctl_access_report(struct sss_cmdline *cmdline, struct sss_tool_ctx *tool_ctx, void *pvt) { errno_t ret; const char *domname = NULL; sssctl_dom_access_reporter_fn reporter; struct sss_domain_info *dom; const char *user = DEFAULT_USER; const char *service = DEFAULT_SERVICE; /* Parse command line. */ struct poptOption options[] = { { "user", 'u', POPT_ARG_STRING, &user, 0, _("PAM user, default: " DEFAULT_USER), NULL }, { "service", 's', POPT_ARG_STRING, &service, 0, _("PAM service, default: " DEFAULT_SERVICE), NULL }, POPT_TABLEEND }; ret = sss_tool_popt_ex(cmdline, options, SSS_TOOL_OPT_OPTIONAL, NULL, NULL, "DOMAIN", _("Specify domain name."), &domname, NULL); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); return ret; } dom = find_domain_by_name(tool_ctx->domains, domname, true); if (dom == NULL) { ERROR("Cannot find domain %1$s\n", domname); return ERR_DOMAIN_NOT_FOUND; } reporter = get_report_fn(dom->provider); if (reporter == NULL) { ERROR("Access report not implemented for domains of type %1$s\n", dom->provider); return ret; } return reporter(tool_ctx, user, service, dom); }