From 6d7450e996e7c699aebf12422cc7080a0782b9ae Mon Sep 17 00:00:00 2001 From: Stephen Gallagher Date: Mon, 6 Jun 2011 22:26:28 -0400 Subject: Rewrite HBAC rule evaluator Add helper function msgs2attrs_array This function converts a list of ldb_messages into a list of sysdb_attrs. Conflicts: src/providers/ldap/ldap_common.c src/providers/ldap/ldap_common.h Add HBAC evaluator and tests Add helper functions for looking up HBAC rule components Remove old HBAC implementation Add new HBAC lookup and evaluation routines Conflicts: Makefile.am Add ipa_hbac_refresh option This option describes the time between refreshes of the HBAC rules on the IPA server. Add ipa_hbac_treat_deny_as option By default, we will treat the presence of any DENY rule as denying all users. This option will allow the admin to explicitly ignore DENY rules during a transitional period. Treat NULL or empty rhost as unknown Previously, we were assuming this meant it was coming from the localhost, but this is not a safe assumption. We will now treat it as unknown and it will fail to match any rule that requires a specified srchost or group of srchosts. libipa_hbac: Support case-insensitive comparisons with UTF8 UTF8 HBAC test Fix memory leak in ipa_hbac_evaluate_rules https://fedorahosted.org/sssd/ticket/933 Fix incorrect NULL check in ipa_hbac_common.c https://fedorahosted.org/sssd/ticket/936 Require matched version and release for libipa_hbac Add rule validator to libipa_hbac https://fedorahosted.org/sssd/ticket/943 --- Makefile.am | 38 +- configure.ac | 4 +- contrib/sssd.spec.in | 36 +- src/config/SSSDConfig.py | 2 + src/config/etc/sssd.api.d/sssd-ipa.conf | 2 + src/external/libunistring.m4 | 9 + src/man/sssd-ipa.5.xml | 42 + src/providers/ipa/hbac_evaluator.c | 356 +++++ src/providers/ipa/ipa_access.c | 2137 ++++++------------------------- src/providers/ipa/ipa_access.h | 31 +- src/providers/ipa/ipa_common.c | 4 +- src/providers/ipa/ipa_common.h | 2 + src/providers/ipa/ipa_hbac.h | 177 +++ src/providers/ipa/ipa_hbac.pc.in | 11 + src/providers/ipa/ipa_hbac_common.c | 883 +++++++++++++ src/providers/ipa/ipa_hbac_hosts.c | 524 ++++++++ src/providers/ipa/ipa_hbac_private.h | 194 +++ src/providers/ipa/ipa_hbac_rules.c | 231 ++++ src/providers/ipa/ipa_hbac_services.c | 451 +++++++ src/providers/ipa/ipa_hbac_users.c | 345 +++++ src/providers/ldap/ldap_common.c | 29 + src/providers/ldap/ldap_common.h | 4 + src/tests/ipa_hbac-tests.c | 850 ++++++++++++ 23 files changed, 4626 insertions(+), 1736 deletions(-) create mode 100644 src/external/libunistring.m4 create mode 100644 src/providers/ipa/hbac_evaluator.c create mode 100644 src/providers/ipa/ipa_hbac.h create mode 100644 src/providers/ipa/ipa_hbac.pc.in create mode 100644 src/providers/ipa/ipa_hbac_common.c create mode 100644 src/providers/ipa/ipa_hbac_hosts.c create mode 100644 src/providers/ipa/ipa_hbac_private.h create mode 100644 src/providers/ipa/ipa_hbac_rules.c create mode 100644 src/providers/ipa/ipa_hbac_services.c create mode 100644 src/providers/ipa/ipa_hbac_users.c create mode 100644 src/tests/ipa_hbac-tests.c diff --git a/Makefile.am b/Makefile.am index 49520f3be..d75300bdb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -32,6 +32,7 @@ pipepath = @pipepath@ initdir = @initdir@ logpath = @logpath@ pubconfpath = @pubconfpath@ +pkgconfigdir = $(libdir)/pkgconfig AM_CFLAGS = if WANT_AUX_INFO @@ -43,6 +44,8 @@ if HAVE_GCC -Werror-implicit-function-declaration endif +dist_pkgconfig_DATA = + ACLOCAL_AMFLAGS = -I m4 -I . sbin_PROGRAMS = \ @@ -78,7 +81,8 @@ if HAVE_CHECK ipa_ldap_opt-tests \ simple_access-tests \ crypto-tests \ - util-tests + util-tests \ + ipa_hbac-tests endif check_PROGRAMS = \ @@ -358,6 +362,17 @@ if HAVE_NSS endif +lib_LTLIBRARIES = libipa_hbac.la +dist_pkgconfig_DATA += src/providers/ipa/ipa_hbac.pc +libipa_hbac_la_SOURCES = \ + src/providers/ipa/hbac_evaluator.c +libipa_hbac_la_LDFLAGS = \ + -version 1:0:1 \ + -lunistring + +include_HEADERS = \ + src/providers/ipa/ipa_hbac.h + #################### # Program Binaries # #################### @@ -690,6 +705,18 @@ crypto_tests_LDADD = \ $(CHECK_LIBS) \ libsss_test_common.la +ipa_hbac_tests_SOURCES = \ + src/tests/ipa_hbac-tests.c \ + $(SSSD_UTIL_OBJ) +ipa_hbac_tests_CFLAGS = \ + $(AM_CFLAGS) \ + $(CHECK_CFLAGS) +ipa_hbac_tests_LDADD = \ + $(SSSD_LIBS) \ + $(CHECK_LIBS) \ + libsss_test_common.la \ + libipa_hbac.la + endif stress_tests_SOURCES = \ @@ -833,6 +860,12 @@ libsss_ipa_la_SOURCES = \ src/providers/ipa/ipa_auth.c \ src/providers/ipa/ipa_access.c \ src/providers/ipa/ipa_dyndns.c \ + src/providers/ipa/ipa_hbac_hosts.c \ + src/providers/ipa/ipa_hbac_private.h \ + src/providers/ipa/ipa_hbac_rules.c \ + src/providers/ipa/ipa_hbac_services.c \ + src/providers/ipa/ipa_hbac_users.c \ + src/providers/ipa/ipa_hbac_common.c \ src/providers/ldap/ldap_id.c \ src/providers/ldap/ldap_id_enum.c \ src/providers/ldap/ldap_id_cleanup.c \ @@ -870,7 +903,8 @@ libsss_ipa_la_LIBADD = \ $(DHASH_LIBS) \ $(KEYUTILS_LIBS) \ $(KRB5_LIBS) \ - libsss_crypt.la + libsss_crypt.la \ + libipa_hbac.la libsss_ipa_la_LDFLAGS = \ -version-info 1:0:0 \ -module diff --git a/configure.ac b/configure.ac index 84b83eb1f..f11dfb87e 100644 --- a/configure.ac +++ b/configure.ac @@ -121,6 +121,7 @@ m4_include([src/external/nsupdate.m4]) m4_include([src/external/libkeyutils.m4]) m4_include([src/external/libnl.m4]) m4_include([src/util/signal.m4]) +m4_include([src/external/libunistring.m4]) PKG_CHECK_MODULES([DBUS],[dbus-1]) dnl if test -n "`$PKG_CONFIG --modversion dbus-1 | grep '^0\.'`" ; then @@ -187,6 +188,7 @@ AC_DEFINE_UNQUOTED([ABS_BUILD_DIR], ["$abs_build_dir"], [Absolute path to the bu AC_SUBST([abs_builddir], $abs_build_dir) AC_CONFIG_FILES([Makefile contrib/sssd.spec src/examples/rwtab src/doxy.config - src/sysv/systemd/sssd.service po/Makefile.in src/man/Makefile]) + src/sysv/systemd/sssd.service po/Makefile.in src/man/Makefile + src/providers/ipa/ipa_hbac.pc]) AC_OUTPUT diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in index c08633a0e..db0ecb3f4 100644 --- a/contrib/sssd.spec.in +++ b/contrib/sssd.spec.in @@ -19,7 +19,8 @@ BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) Requires: libldb >= 0.9.3 Requires: libtdb >= 1.1.3 -Requires: sssd-client = %{version}-%{release} +Requires: sssd-client%{?_isa} = %{version}-%{release} +Requires: libipa_hbac = %{version}-%{release} Requires: cyrus-sasl-gssapi Requires: keyutils-libs Requires(post): initscripts chkconfig /sbin/ldconfig @@ -73,7 +74,9 @@ BuildRequires: bind-utils BuildRequires: keyutils-libs-devel BuildRequires: libnl-devel BuildRequires: nscd -BuildRequires: gettext +BuildRequires: gettext-devel +BuildRequires: pkgconfig +BuildRequires: libunistring-devel %description Provides a set of daemons to manage access to remote directories and @@ -104,6 +107,23 @@ SSSD when using id_provider = local in /etc/sssd/sssd.conf. Also provides a userspace tool for generating an obfuscated LDAP password for use with ldap_default_authtok_type = obfuscated_password. +%package -n libipa_hbac +Summary: FreeIPA HBAC Evaluator library +Group: Development/Libraries +License: LGPLv3+ + +%description -n libipa_hbac +Utility library to validate FreeIPA HBAC rules for authorization requests + +%package -n libipa_hbac-devel +Summary: FreeIPA HBAC Evaluator library +Group: Development/Libraries +License: LGPLv3+ +Requires: libipa_hbac = %{version}-%{release} + +%description -n libipa_hbac-devel +Utility library to validate FreeIPA HBAC rules for authorization requests + %prep %setup -q @@ -158,6 +178,7 @@ rm -f \ $RPM_BUILD_ROOT/%{_libdir}/sssd/libsss_ipa.la \ $RPM_BUILD_ROOT/%{_libdir}/sssd/libsss_simple.la \ $RPM_BUILD_ROOT/%{_libdir}/krb5/plugins/libkrb5/sssd_krb5_locator_plugin.la \ + $RPM_BUILD_ROOT/%{_libdir}/libipa_hbac.la \ $RPM_BUILD_ROOT/%{python_sitearch}/pysss.la # Older versions of rpmbuild can only handle one -f option @@ -232,6 +253,17 @@ rm -rf $RPM_BUILD_ROOT %{_mandir}/man8/sss_usermod.8* %{_mandir}/man8/sss_obfuscate.8* +%files -n libipa_hbac +%defattr(-,root,root,-) +%doc src/sss_client/COPYING src/sss_client/COPYING.LESSER +%{_libdir}/libipa_hbac.so.* + +%files -n libipa_hbac-devel +%defattr(-,root,root,-) +%{_includedir}/ipa_hbac.h +%{_libdir}/libipa_hbac.so +%{_libdir}/pkgconfig/ipa_hbac.pc + %post /sbin/ldconfig /sbin/chkconfig --add %{servicename} diff --git a/src/config/SSSDConfig.py b/src/config/SSSDConfig.py index fed19f079..920a8a056 100644 --- a/src/config/SSSDConfig.py +++ b/src/config/SSSDConfig.py @@ -97,6 +97,8 @@ option_strings = { 'ipa_dyndns_update' : _("Whether to automatically update the client's DNS entry in FreeIPA"), 'ipa_dyndns_iface' : _("The interface whose IP should be used for dynamic DNS updates"), 'ipa_hbac_search_base' : _("Search base for HBAC related objects"), + 'ipa_hbac_refresh' : _("The amount of time between lookups of the HBAC rules against the IPA server"), + 'ipa_hbac_treat_deny_as' : _("If DENY rules are present, either DENY_ALL or IGNORE"), # [provider/krb5] 'krb5_kdcip' : _('Kerberos server address'), diff --git a/src/config/etc/sssd.api.d/sssd-ipa.conf b/src/config/etc/sssd.api.d/sssd-ipa.conf index 31b7dc9be..d7992b608 100644 --- a/src/config/etc/sssd.api.d/sssd-ipa.conf +++ b/src/config/etc/sssd.api.d/sssd-ipa.conf @@ -100,6 +100,8 @@ krb5_renew_interval = int, None, false krb5_use_fast = str, None, false [provider/ipa/access] +ipa_hbac_refresh = int, None, false +ipa_hbac_treat_deny_as = str, None, false [provider/ipa/chpass] diff --git a/src/external/libunistring.m4 b/src/external/libunistring.m4 new file mode 100644 index 000000000..69c54fe3f --- /dev/null +++ b/src/external/libunistring.m4 @@ -0,0 +1,9 @@ +AC_CHECK_HEADERS(unistr.h, + [AC_CHECK_LIB([unistring], [u8_strlen], [ UNISTRING_LIBS="-lunistring" ], [AC_MSG_ERROR([No usable libunistring library found])])], + [AC_MSG_ERROR([libunistring header files are not installed])] +) + +AC_CHECK_HEADERS(unicase.h, + [AC_CHECK_LIB([unistring], [u8_casecmp], [ UNISTRING_LIBS="-lunistring" ], [AC_MSG_ERROR([No usable libunistring library found])])], + [AC_MSG_ERROR([libunistring header files are not installed])] +) \ No newline at end of file diff --git a/src/man/sssd-ipa.5.xml b/src/man/sssd-ipa.5.xml index 4604c55e2..8d2ba6818 100644 --- a/src/man/sssd-ipa.5.xml +++ b/src/man/sssd-ipa.5.xml @@ -175,6 +175,48 @@ + + ipa_hbac_refresh (integer) + + + The amount of time between lookups of the HBAC + rules against the IPA server. This will reduce the + latency and load on the IPA server if there are + many access-control requests made in a short + period. + + + Default: 5 (seconds) + + + + + ipa_hbac_treat_deny_as (string) + + + This option specifies how to treat the deprecated + DENY-type HBAC rules. As of FreeIPA v2.1, DENY + rules are no longer supported on the server. All + users of FreeIPA will need to migrate their rules + to use only the ALLOW rules. The client will + support two modes of operation during this + transition period: + + + DENY_ALL: If any HBAC DENY + rules are detected, all users will be denied + access. + + + IGNORE: SSSD will ignore any + DENY rules. Be very careful with this option, as + it may result in opening unintended access. + + + Default: DENY_ALL + + + diff --git a/src/providers/ipa/hbac_evaluator.c b/src/providers/ipa/hbac_evaluator.c new file mode 100644 index 000000000..476ad6482 --- /dev/null +++ b/src/providers/ipa/hbac_evaluator.c @@ -0,0 +1,356 @@ +/* + SSSD + + IPA Backend Module -- Access control + + Authors: + Sumit Bose + Stephen Gallagher + + 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 . +*/ + +#include +#include +#include +#include +#include +#include "providers/ipa/ipa_hbac.h" + +#ifndef HAVE_ERRNO_T +#define HAVE_ERRNO_T +typedef int errno_t; +#endif + +#ifndef EOK +#define EOK 0 +#endif + +/* Placeholder structure for future HBAC time-based + * evaluation rules + */ +struct hbac_time_rules { + int not_yet_implemented; +}; + +enum hbac_eval_result_int { + HBAC_EVAL_MATCH_ERROR = -1, + HBAC_EVAL_MATCHED, + HBAC_EVAL_UNMATCHED +}; + +static bool hbac_rule_element_is_complete(struct hbac_rule_element *el) +{ + if (el == NULL) return false; + if (el->category == HBAC_CATEGORY_ALL) return true; + + if (el->names == NULL && el->groups == NULL) return false; + + if ((el->names && el->names[0] != NULL) + || (el->groups && el->groups[0] != NULL)) + return true; + + /* If other categories are added, handle them here */ + + return false; +} + +bool hbac_rule_is_complete(struct hbac_rule *rule, uint32_t *missing_attrs) +{ + bool complete = true; + + *missing_attrs = 0; + + if (rule == NULL) { + /* No rule passed in? */ + return false; + } + + /* Make sure we have all elements */ + if (!hbac_rule_element_is_complete(rule->users)) { + complete = false; + *missing_attrs |= HBAC_RULE_ELEMENT_USERS; + } + + if (!hbac_rule_element_is_complete(rule->services)) { + complete = false; + *missing_attrs |= HBAC_RULE_ELEMENT_SERVICES; + } + + if (!hbac_rule_element_is_complete(rule->targethosts)) { + complete = false; + *missing_attrs |= HBAC_RULE_ELEMENT_TARGETHOSTS; + } + + if (!hbac_rule_element_is_complete(rule->srchosts)) { + complete = false; + *missing_attrs |= HBAC_RULE_ELEMENT_SOURCEHOSTS; + } + + return complete; +} + +enum hbac_eval_result_int hbac_evaluate_rule(struct hbac_rule *rule, + struct hbac_eval_req *hbac_req, + enum hbac_error_code *error); + +enum hbac_eval_result hbac_evaluate(struct hbac_rule **rules, + struct hbac_eval_req *hbac_req, + struct hbac_info **info) +{ + enum hbac_error_code ret; + enum hbac_eval_result result = HBAC_EVAL_DENY; + enum hbac_eval_result_int intermediate_result; + + if (info) { + *info = malloc(sizeof(struct hbac_info)); + if (!*info) { + return HBAC_EVAL_OOM; + } + (*info)->code = HBAC_ERROR_UNKNOWN; + (*info)->rule_name = NULL; + } + uint32_t i; + + for (i = 0; rules[i]; i++) { + intermediate_result = hbac_evaluate_rule(rules[i], hbac_req, &ret); + if (intermediate_result == HBAC_EVAL_UNMATCHED) { + /* This rule did not match at all. Skip it */ + continue; + } else if (intermediate_result == HBAC_EVAL_MATCHED) { + /* This request matched an ALLOW rule + * Set the result to ALLOW but continue checking + * the other rules in case a DENY rule trumps it. + */ + result = HBAC_EVAL_ALLOW; + if (info) { + (*info)->code = HBAC_SUCCESS; + (*info)->rule_name = strdup(rules[i]->name); + if (!(*info)->rule_name) { + result = HBAC_EVAL_ERROR; + (*info)->code = HBAC_ERROR_OUT_OF_MEMORY; + } + } + break; + } else { + /* An error occurred processing this rule */ + result = HBAC_EVAL_ERROR; + (*info)->code = ret; + (*info)->rule_name = strdup(rules[i]->name); + /* Explicitly not checking the result of strdup(), since if + * it's NULL, we can't do anything anyway. + */ + goto done; + } + } + + /* If we've reached the end of the loop, we have either set the + * result to ALLOW explicitly or we'll stick with the default DENY. + */ +done: + + return result; +} + +static errno_t hbac_evaluate_element(struct hbac_rule_element *rule_el, + struct hbac_request_element *req_el, + bool *matched); + +enum hbac_eval_result_int hbac_evaluate_rule(struct hbac_rule *rule, + struct hbac_eval_req *hbac_req, + enum hbac_error_code *error) +{ + errno_t ret; + bool matched; + + if (!rule->enabled) return HBAC_EVAL_UNMATCHED; + + /* Make sure we have all elements */ + if (!rule->users + || !rule->services + || !rule->targethosts + || !rule->srchosts) { + *error = HBAC_ERROR_UNPARSEABLE_RULE; + return HBAC_EVAL_MATCH_ERROR; + } + + /* Check users */ + ret = hbac_evaluate_element(rule->users, + hbac_req->user, + &matched); + if (ret != EOK) { + *error = HBAC_ERROR_UNPARSEABLE_RULE; + return HBAC_EVAL_MATCH_ERROR; + } else if (!matched) { + return HBAC_EVAL_UNMATCHED; + } + + /* Check services */ + ret = hbac_evaluate_element(rule->services, + hbac_req->service, + &matched); + if (ret != EOK) { + *error = HBAC_ERROR_UNPARSEABLE_RULE; + return HBAC_EVAL_MATCH_ERROR; + } else if (!matched) { + return HBAC_EVAL_UNMATCHED; + } + + /* Check target hosts */ + ret = hbac_evaluate_element(rule->targethosts, + hbac_req->targethost, + &matched); + if (ret != EOK) { + *error = HBAC_ERROR_UNPARSEABLE_RULE; + return HBAC_EVAL_MATCH_ERROR; + } else if (!matched) { + return HBAC_EVAL_UNMATCHED; + } + + /* Check source hosts */ + ret = hbac_evaluate_element(rule->srchosts, + hbac_req->srchost, + &matched); + if (ret != EOK) { + *error = HBAC_ERROR_UNPARSEABLE_RULE; + return HBAC_EVAL_MATCH_ERROR; + } else if (!matched) { + return HBAC_EVAL_UNMATCHED; + } + return HBAC_EVAL_MATCHED; +} + +static errno_t hbac_evaluate_element(struct hbac_rule_element *rule_el, + struct hbac_request_element *req_el, + bool *matched) +{ + size_t i, j; + const uint8_t *rule_name; + const uint8_t *req_name; + int result; + int ret; + + if (rule_el->category & HBAC_CATEGORY_ALL) { + *matched = true; + return EOK; + } + + /* First check the name list */ + if (rule_el->names) { + for (i = 0; rule_el->names[i]; i++) { + if (req_el->name != NULL) { + rule_name = (const uint8_t *) rule_el->names[i]; + req_name = (const uint8_t *) req_el->name; + + /* Do a case-insensitive comparison. + * The input must be encoded in UTF8. + * We have no way of knowing the language, + * so we'll pass NULL for the language and + * hope for the best. + */ + errno = 0; + ret = u8_casecmp(rule_name, u8_strlen(rule_name), + req_name, u8_strlen(req_name), + NULL, NULL, &result); + if (ret < 0) { + return errno; + } + + if (result == 0) { + *matched = true; + return EOK; + } + } + } + } + + if (rule_el->groups) { + /* Not found in the name list + * Check for group membership + */ + for (i = 0; rule_el->groups[i]; i++) { + rule_name = (const uint8_t *) rule_el->groups[i]; + + for (j = 0; req_el->groups[j]; j++) { + req_name = (const uint8_t *) req_el->groups[j]; + + /* Do a case-insensitive comparison. + * The input must be encoded in UTF8. + * We have no way of knowing the language, + * so we'll pass NULL for the language and + * hope for the best. + */ + errno = 0; + ret = u8_casecmp(rule_name, u8_strlen(rule_name), + req_name, u8_strlen(req_name), + NULL, NULL, &result); + if (ret < 0) { + return errno; + } + + if (result == 0) { + *matched = true; + return EOK; + } + } + } + } + + /* Not found in groups either */ + *matched = false; + return EOK; +} + +const char *hbac_result_string(enum hbac_eval_result result) +{ + switch(result) { + case HBAC_EVAL_ALLOW: + return "HBAC_EVAL_ALLOW"; + case HBAC_EVAL_DENY: + return "HBAC_EVAL_DENY"; + case HBAC_EVAL_ERROR: + return "HBAC_EVAL_ERROR"; + case HBAC_EVAL_OOM: + return "Could not allocate memory for hbac_info object"; + } + return "HBAC_EVAL_ERROR"; +} + +void hbac_free_info(struct hbac_info *info) +{ + if (info == NULL) return; + + free(info->rule_name); + free(info); + info = NULL; +} + +const char *hbac_error_string(enum hbac_error_code code) +{ + switch(code) { + case HBAC_SUCCESS: + return "Success"; + case HBAC_ERROR_NOT_IMPLEMENTED: + return "Function is not yet implemented"; + case HBAC_ERROR_OUT_OF_MEMORY: + return "Out of memory"; + case HBAC_ERROR_UNPARSEABLE_RULE: + return "Rule could not be evaluated"; + case HBAC_ERROR_UNKNOWN: + default: + return "Unknown error code"; + } +} diff --git a/src/providers/ipa/ipa_access.c b/src/providers/ipa/ipa_access.c index f07eb7b5b..3bd377320 100644 --- a/src/providers/ipa/ipa_access.c +++ b/src/providers/ipa/ipa_access.c @@ -29,1634 +29,77 @@ #include "providers/ldap/sdap_async.h" #include "providers/ipa/ipa_common.h" #include "providers/ipa/ipa_access.h" - -#define OBJECTCLASS "objectclass" -#define IPA_MEMBEROF "memberOf" -#define IPA_HOST_SERVERHOSTNAME "serverHostName" -#define IPA_HOST_FQDN "fqdn" -#define IPA_ACCESS_RULE_TYPE "accessRuleType" -#define IPA_MEMBER_USER "memberUser" -#define IPA_USER_CATEGORY "userCategory" -#define IPA_SERVICE_NAME "serviceName" -#define IPA_SOURCE_HOST "sourceHost" -#define IPA_SOURCE_HOST_CATEGORY "sourceHostCategory" -#define IPA_EXTERNAL_HOST "externalHost" -#define IPA_UNIQUE_ID "ipauniqueid" -#define IPA_ENABLED_FLAG "ipaenabledflag" -#define IPA_MEMBER_HOST "memberHost" -#define IPA_HOST_CATEGORY "hostCategory" -#define IPA_CN "cn" -#define IPA_MEMBER_SERVICE "memberService" -#define IPA_SERVICE_CATEGORY "serviceCategory" -#define IPA_TRUE_VALUE "TRUE" - -#define IPA_HOST_BASE_TMPL "cn=computers,cn=accounts,%s" -#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_HOSTS_SUBDIR "hbac_hosts" -#define HBAC_SERVICES_SUBDIR "hbac_services" +#include "providers/ipa/ipa_hbac.h" +#include "providers/ipa/ipa_hbac_private.h" static char *get_hbac_search_base(TALLOC_CTX *mem_ctx, - struct dp_option *ipa_options) -{ - char *base; - int ret; - - base = dp_opt_get_string(ipa_options, IPA_HBAC_SEARCH_BASE); - if (base != NULL) { - return talloc_strdup(mem_ctx, base); - } - - DEBUG(9, ("ipa_hbac_search_base not available, trying base DN.\n")); - - ret = domain_to_basedn(mem_ctx, - dp_opt_get_string(ipa_options, IPA_KRB5_REALM), - &base); - if (ret != EOK) { - DEBUG(1, ("domain_to_basedn failed.\n")); - return NULL; - } - - return base; -} - -static errno_t msgs2attrs_array(TALLOC_CTX *mem_ctx, size_t count, - struct ldb_message **msgs, - struct sysdb_attrs ***attrs) -{ - int i; - struct sysdb_attrs **a; - - a = talloc_array(mem_ctx, struct sysdb_attrs *, count); - if (a == NULL) { - DEBUG(1, ("talloc_array failed.\n")); - return ENOMEM; - } - - for (i = 0; i < count; i++) { - a[i] = talloc(a, struct sysdb_attrs); - if (a[i] == NULL) { - DEBUG(1, ("talloc_array failed.\n")); - talloc_free(a); - return ENOMEM; - } - a[i]->num = msgs[i]->num_elements; - a[i]->a = talloc_steal(a[i], msgs[i]->elements); - } - - *attrs = a; - - return EOK; -} - -static 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(1, ("sysdb_attrs_replace_name failed.\n")); - return ret; - } - } - - return EOK; -} - -static errno_t hbac_sdap_data_recv(struct tevent_req *subreq, - TALLOC_CTX *mem_ctx, size_t *count, - struct sysdb_attrs ***attrs) -{ - int ret; - - ret = sdap_get_generic_recv(subreq, mem_ctx, count, attrs); - if (ret != EOK) { - DEBUG(1, ("sdap_get_generic_recv failed.\n")); - return ret; - } - - ret = replace_attribute_name(IPA_MEMBEROF, SYSDB_ORIG_MEMBEROF, - *count, *attrs); - if (ret != EOK) { - DEBUG(1, ("replace_attribute_name failed.\n")); - return ret; - } - - return EOK; -} - -static errno_t hbac_sysdb_data_recv(TALLOC_CTX *mem_ctx, - struct sysdb_ctx *sysdb, - struct sss_domain_info *domain, - const char *filter, - const char *subtree_name, - const char **search_attrs, - size_t *count, - struct sysdb_attrs ***reply_attrs) -{ - int ret; - struct ldb_message **msgs; - - ret = sysdb_search_custom(mem_ctx, sysdb, domain, filter, subtree_name, - search_attrs, count, &msgs); - if (ret != EOK) { - if (ret == ENOENT) { - *count = 0; - *reply_attrs = NULL; - return EOK; - } - DEBUG(1, ("sysdb_search_custom failed.\n")); - return ret; - } - - ret = msgs2attrs_array(mem_ctx, *count, msgs, reply_attrs); - talloc_zfree(msgs); - if (ret != EOK) { - DEBUG(1, ("msgs2attrs_array failed.\n")); - return ret; - } - - return EOK; -} - -static errno_t set_local_and_remote_host_info(TALLOC_CTX *mem_ctx, - size_t host_count, - struct sysdb_attrs **host_list, - const char *local_hostname, - const char *remote_hostname, - struct hbac_host_info **local_hhi, - struct hbac_host_info **remote_hhi) - -{ - size_t c; - int ret; - struct hbac_host_info *hhi; - struct ldb_message_element *el; - TALLOC_CTX *tmp_ctx = NULL; - - if (local_hostname == NULL || *local_hostname == '\0') { - DEBUG(1, ("Missing local hostname.\n")); - ret = EINVAL; - goto done; - } - - if (host_count == 0) { - DEBUG(1, ("No host data available.\n")); - ret = EINVAL; - goto done; - } - - tmp_ctx = talloc_new(mem_ctx); - if (tmp_ctx == NULL) { - ret = ENOMEM; - goto done; - } - - for (c = 0; c < host_count; c++) { - hhi = talloc_zero(tmp_ctx, struct hbac_host_info); - if (hhi == NULL) { - DEBUG(1, ("talloc_zero failed.\n")); - ret = ENOMEM; - goto done; - } - - ret = sysdb_attrs_get_el(host_list[c], SYSDB_ORIG_DN, &el); - if (ret != EOK) { - DEBUG(1, ("sysdb_attrs_get_el failed.\n")); - goto done; - } - if (el->num_values == 0) { - DEBUG(1, ("Missing OriginalDN.\n")); - ret = EINVAL; - goto done; - } - DEBUG(9, ("OriginalDN: [%.*s].\n", el->values[0].length, - (char *)el->values[0].data)); - hhi->dn = talloc_strndup(hhi, (char *)el->values[0].data, - el->values[0].length); - if (hhi->dn == NULL) { - DEBUG(1, ("talloc_strndup failed.\n")); - ret = ENOMEM; - goto done; - } - - ret = sysdb_attrs_get_el(host_list[c], IPA_HOST_SERVERHOSTNAME, &el); - if (ret != EOK) { - DEBUG(1, ("sysdb_attrs_get_el failed.\n")); - goto done; - } - if (el->num_values == 0) { - DEBUG(1, ("Missing ServerHostName.\n")); - ret = EINVAL; - goto done; - } - DEBUG(9, ("ServerHostName: [%.*s].\n", el->values[0].length, - (char *)el->values[0].data)); - hhi->serverhostname = talloc_strndup(hhi, (char *)el->values[0].data, - el->values[0].length); - if (hhi->serverhostname == NULL) { - DEBUG(1, ("talloc_strndup failed.\n")); - ret = ENOMEM; - goto done; - } - - ret = sysdb_attrs_get_el(host_list[c], IPA_HOST_FQDN, &el); - if (ret != EOK) { - DEBUG(1, ("sysdb_attrs_get_el failed.\n")); - goto done; - } - if (el->num_values == 0) { - DEBUG(1, ("Missing FQDN.\n")); - ret = EINVAL; - goto done; - } - DEBUG(9, ("FQDN: [%.*s].\n", el->values[0].length, - (char *)el->values[0].data)); - hhi->fqdn = talloc_strndup(hhi, (char *)el->values[0].data, - el->values[0].length); - if (hhi->fqdn == NULL) { - DEBUG(1, ("talloc_strndup failed.\n")); - ret = ENOMEM; - goto done; - } - - ret = sysdb_attrs_get_string_array(host_list[c], SYSDB_ORIG_MEMBEROF, - hhi, &hhi->memberof); - if (ret != EOK) { - if (ret != ENOENT) { - DEBUG(1, ("sysdb_attrs_get_string_array failed.\n")); - goto done; - } - - hhi->memberof = talloc_array(hhi, const char *, 1); - if (hhi->memberof == NULL) { - DEBUG(1, ("talloc_array failed.\n")); - ret = ENOMEM; - goto done; - } - hhi->memberof[0] = NULL; - } - - if (strcmp(hhi->fqdn, local_hostname) == 0 || - strcmp(hhi->serverhostname, local_hostname) == 0) { - *local_hhi = talloc_steal(mem_ctx, hhi); - } - - if (remote_hostname != NULL && *remote_hostname != '\0') { - if (strcmp(hhi->fqdn, remote_hostname) == 0 || - strcmp(hhi->serverhostname, remote_hostname) == 0) { - *remote_hhi = talloc_steal(mem_ctx, hhi); - } - } - } - - ret = EOK; - -done: - talloc_free(tmp_ctx); - return ret; -} - -static void ipa_access_reply(struct hbac_ctx *hbac_ctx, int pam_status) -{ - struct be_req *be_req = hbac_ctx->be_req; - struct pam_data *pd; - pd = talloc_get_type(be_req->req_data, struct pam_data); - pd->pam_status = pam_status; - - /* destroy HBAC context now to release all used resources and LDAP connection */ - talloc_zfree(hbac_ctx); - - if (pam_status == PAM_SUCCESS || pam_status == PAM_PERM_DENIED) { - be_req->fn(be_req, DP_ERR_OK, pam_status, NULL); - } else { - be_req->fn(be_req, DP_ERR_FATAL, pam_status, NULL); - } -} - -static errno_t hbac_save_list(struct sysdb_ctx *sysdb, bool delete_subdir, - const char *subdir, struct sss_domain_info *domain, - 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(1, ("talloc_new failed.\n")); - return ENOMEM; - } - - if (delete_subdir) { - base_dn = sysdb_custom_subtree_dn(sysdb, tmp_ctx, domain->name, subdir); - if (base_dn == NULL) { - ret = ENOMEM; - goto done; - } - - ret = sysdb_delete_recursive(tmp_ctx, sysdb, base_dn, true); - if (ret != EOK) { - DEBUG(1, ("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(1, ("sysdb_attrs_get_el failed.\n")); - goto done; - } - if (el->num_values == 0) { - DEBUG(1, ("IPA_UNIQUE_ID not found.\n")); - 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(1, ("talloc_strndup failed.\n")); - ret = ENOMEM; - goto done; - } - DEBUG(9, ("Object name: [%s].\n", object_name)); - - ret = sysdb_store_custom(tmp_ctx, sysdb, domain, object_name, subdir, - list[c]); - if (ret != EOK) { - DEBUG(1, ("sysdb_store_custom failed.\n")); - goto done; - } - } - - ret = EOK; - -done: - talloc_free(tmp_ctx); - return ret; -} - -static errno_t hbac_save_data_to_sysdb(struct hbac_ctx *hbac_ctx) -{ - int ret; - bool in_transaction = false; - struct sysdb_ctx *sysdb = hbac_ctx_sysdb(hbac_ctx); - struct sss_domain_info *domain = hbac_ctx_be(hbac_ctx)->domain; - - ret = sysdb_transaction_start(sysdb); - if (ret != EOK) { - DEBUG(1, ("sysdb_transaction_start failed.\n")); - return ret; - } - in_transaction = true; - - ret = hbac_save_list(sysdb, true, HBAC_SERVICES_SUBDIR, domain, - IPA_UNIQUE_ID, hbac_ctx->hbac_services_count, - hbac_ctx->hbac_services_list); - if (ret != EOK) { - DEBUG(1, ("hbac_save_list failed.\n")); - goto done; - } - - ret = hbac_save_list(sysdb, true, HBAC_RULES_SUBDIR, domain, - IPA_UNIQUE_ID, hbac_ctx->hbac_rule_count, - hbac_ctx->hbac_rule_list); - if (ret != EOK) { - DEBUG(1, ("hbac_save_list failed.\n")); - goto done; - } - - ret = hbac_save_list(sysdb, false, HBAC_HOSTS_SUBDIR, domain, - IPA_HOST_FQDN, hbac_ctx->hbac_hosts_count, - hbac_ctx->hbac_hosts_list); - if (ret != EOK) { - DEBUG(1, ("hbac_save_list failed.\n")); - goto done; - } - - ret = sysdb_transaction_commit(sysdb); - if (ret != EOK) { - DEBUG(1, ("sysdb_transaction_commit failed.\n")); - goto done; - } - in_transaction = false; - - ret = EOK; - -done: - if (in_transaction) { - sysdb_transaction_cancel(sysdb); - } - return ret; -} - -struct hbac_get_service_data_state { - struct hbac_ctx *hbac_ctx; - bool offline; - - char *services_filter; - const char **services_attrs; - struct sysdb_attrs **services_reply_list; - size_t services_reply_count; - - size_t current_item; -}; - -static void hbac_services_get_done(struct tevent_req *subreq); - -struct tevent_req *hbac_get_service_data_send(TALLOC_CTX *memctx, - struct hbac_ctx *hbac_ctx) -{ - struct tevent_req *req = NULL; - struct tevent_req *subreq = NULL; - struct hbac_get_service_data_state *state; - struct sdap_handle *sdap_handle; - int ret; - - req = tevent_req_create(memctx, &state, struct hbac_get_service_data_state); - if (req == NULL) { - DEBUG(1, ("tevent_req_create failed.\n")); - return NULL; - } - - state->hbac_ctx = hbac_ctx; - - state->services_reply_list = NULL; - state->services_reply_count = 0; - - state->current_item = 0; - - state->services_attrs = talloc_array(state, const char *, 7); - if (state->services_attrs == NULL) { - DEBUG(1, ("Failed to allocate service attribute list.\n")); - ret = ENOMEM; - goto fail; - } - state->services_attrs[0] = IPA_CN; - state->services_attrs[1] = SYSDB_ORIG_DN; - state->services_attrs[2] = IPA_UNIQUE_ID; - state->services_attrs[3] = IPA_MEMBEROF; - state->services_attrs[4] = SYSDB_ORIG_MEMBEROF; - state->services_attrs[5] = OBJECTCLASS; - state->services_attrs[6] = NULL; - - state->services_filter = talloc_asprintf(state, - "(objectclass=ipaHBACService)"); - if (state->services_filter == NULL) { - ret = ENOMEM; - goto fail; - } - - DEBUG(9, ("Services filter: [%s].\n", state->services_filter)); - - if (hbac_ctx_is_offline(state->hbac_ctx)) { - ret = hbac_sysdb_data_recv(state, - hbac_ctx_sysdb(state->hbac_ctx), - hbac_ctx_be(state->hbac_ctx)->domain, - state->services_filter, HBAC_SERVICES_SUBDIR, - state->services_attrs, - &state->services_reply_count, - &state->services_reply_list); - if (ret) { - DEBUG(1, ("hbac_sysdb_data_recv failed.\n")); - goto fail; - } - - tevent_req_done(req); - tevent_req_post(req, hbac_ctx_ev(state->hbac_ctx)); - return req; - } - - sdap_handle = sdap_id_op_handle(hbac_ctx_sdap_id_op(state->hbac_ctx)); - if (sdap_handle == NULL) { - DEBUG(1, ("Bug: sdap_id_op is disconnected.\n")); - ret = EIO; - goto fail; - } - subreq = sdap_get_generic_send(state, - hbac_ctx_ev(state->hbac_ctx), - hbac_ctx_sdap_id_ctx(state->hbac_ctx)->opts, - sdap_handle, - state->hbac_ctx->hbac_search_base, - LDAP_SCOPE_SUB, - state->services_filter, - state->services_attrs, - NULL, 0, - dp_opt_get_int( - hbac_ctx_sdap_id_ctx(state->hbac_ctx)->opts->basic, - SDAP_ENUM_SEARCH_TIMEOUT)); - - if (subreq == NULL) { - DEBUG(1, ("sdap_get_generic_send failed.\n")); - ret = ENOMEM; - goto fail; - } - - tevent_req_set_callback(subreq, hbac_services_get_done, req); - - return req; - -fail: - tevent_req_error(req, ret); - tevent_req_post(req, hbac_ctx_ev(state->hbac_ctx)); - return req; -} - -static void hbac_services_get_done(struct tevent_req *subreq) -{ - struct tevent_req *req = tevent_req_callback_data(subreq, - struct tevent_req); - struct hbac_get_service_data_state *state = tevent_req_data(req, - struct hbac_get_service_data_state); - int ret; - - ret = hbac_sdap_data_recv(subreq, state, &state->services_reply_count, - &state->services_reply_list); - talloc_zfree(subreq); - if (ret != EOK) { - tevent_req_error(req, ret); - return; - } - - tevent_req_done(req); - return; -} - -static int hbac_get_service_data_recv(struct tevent_req *req, - TALLOC_CTX *memctx, - size_t *hbac_services_count, - struct sysdb_attrs ***hbac_services_list) -{ - struct hbac_get_service_data_state *state = tevent_req_data(req, - struct hbac_get_service_data_state); - int i; - - TEVENT_REQ_RETURN_ON_ERROR(req); - - *hbac_services_count = state->services_reply_count; - *hbac_services_list = talloc_steal(memctx, state->services_reply_list); - for (i = 0; i < state->services_reply_count; i++) { - talloc_steal(memctx, state->services_reply_list[i]); - } - - return EOK; -} - -static int hbac_get_user_info(TALLOC_CTX *memctx, - struct be_ctx *be_ctx, - const char *user, - const char **user_dn, - size_t *groups_count, - const char ***_groups) -{ - TALLOC_CTX *tmpctx; - const char *attrs[] = { SYSDB_ORIG_DN, NULL }; - struct ldb_message *user_msg; - const char *user_orig_dn; - struct ldb_message **msgs; - size_t count; - const char **groups; - int ret; - int i; - - tmpctx = talloc_new(memctx); - if (!tmpctx) { - return ENOMEM; - } - - ret = sysdb_search_user_by_name(tmpctx, be_ctx->sysdb, - be_ctx->domain, user, attrs, &user_msg); - if (ret != EOK) { - goto fail; - } - - DEBUG(9, ("Found user info for user [%s].\n", user)); - user_orig_dn = ldb_msg_find_attr_as_string(user_msg, SYSDB_ORIG_DN, NULL); - if (user_orig_dn == NULL) { - DEBUG(1, ("Original DN of user [%s] not available.\n", user)); - ret = EINVAL; - goto fail; - } - DEBUG(9, ("Found original DN [%s] for user [%s].\n", - user_orig_dn, user)); - - ret = sysdb_asq_search(tmpctx, be_ctx->sysdb, be_ctx->domain, - user_msg->dn, NULL, SYSDB_MEMBEROF, attrs, - &count, &msgs); - if (ret != EOK) { - DEBUG(1, ("sysdb_asq_search on user %s failed.\n", user)); - goto fail; - } - - if (count == 0) { - *user_dn = talloc_strdup(memctx, user_orig_dn); - if (*user_dn == NULL) { - ret = ENOMEM; - goto fail; - } - *groups_count = 0; - *_groups = NULL; - talloc_zfree(tmpctx); - return EOK; - } - - groups = talloc_array(tmpctx, const char *, count); - if (groups == NULL) { - DEBUG(1, ("talloc_groups failed.\n")); - ret = ENOMEM; - goto fail; - } - - for(i = 0; i < count; i++) { - if (msgs[i]->num_elements != 1) { - DEBUG(1, ("Unexpected number of elements.\n")); - ret = EINVAL; - goto fail; - } - - if (msgs[i]->elements[0].num_values != 1) { - DEBUG(1, ("Unexpected number of values.\n")); - ret = EINVAL; - goto fail; - } - - groups[i] = talloc_strndup(groups, - (const char *)msgs[i]->elements[0].values[0].data, - msgs[i]->elements[0].values[0].length); - if (groups[i] == NULL) { - DEBUG(1, ("talloc_strndup failed.\n")); - ret = ENOMEM; - goto fail; - } - - DEBUG(9, ("Found group [%s].\n", groups[i])); - } - - *user_dn = talloc_strdup(memctx, user_orig_dn); - if (*user_dn == NULL) { - ret = ENOMEM; - goto fail; - } - *groups_count = count; - *_groups = talloc_steal(memctx, groups); - - talloc_zfree(tmpctx); - return EOK; - -fail: - DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret))); - talloc_zfree(tmpctx); - return ret; -} - - -struct hbac_get_host_info_state { - struct hbac_ctx *hbac_ctx; - - char *host_filter; - const char **host_attrs; - - struct sysdb_attrs **host_reply_list; - size_t host_reply_count; - size_t current_item; - struct hbac_host_info **hbac_host_info; -}; - -static void hbac_get_host_memberof(struct tevent_req *req, bool offline); -static void hbac_get_host_memberof_done(struct tevent_req *subreq); - -static struct tevent_req *hbac_get_host_info_send(TALLOC_CTX *memctx, - struct hbac_ctx *hbac_ctx, - const char **hostnames) -{ - struct tevent_req *req = NULL; - struct tevent_req *subreq = NULL; - struct hbac_get_host_info_state *state; - struct sdap_handle *sdap_handle; - char *host; - int ret; - int i; - - if (hostnames == NULL) { - DEBUG(1, ("Missing hostnames.\n")); - return NULL; - } - - req = tevent_req_create(memctx, &state, struct hbac_get_host_info_state); - if (req == NULL) { - DEBUG(1, ("tevent_req_create failed.\n")); - return NULL; - } - - state->hbac_ctx = hbac_ctx; - - state->host_reply_list = NULL; - state->host_reply_count = 0; - state->current_item = 0; - state->hbac_host_info = NULL; - - state->host_filter = talloc_asprintf(state, "(&(objectclass=ipaHost)(|"); - if (state->host_filter == NULL) { - DEBUG(1, ("Failed to create filter.\n")); - ret = ENOMEM; - goto fail; - } - for (i = 0; hostnames[i] != NULL; i++) { - ret = sss_filter_sanitize(state->host_filter, hostnames[i], &host); - if (ret != EOK) { - goto fail; - } - - state->host_filter = talloc_asprintf_append(state->host_filter, - "(%s=%s)(%s=%s)", - IPA_HOST_FQDN, host, - IPA_HOST_SERVERHOSTNAME, host); - - if (state->host_filter == NULL) { - ret = ENOMEM; - goto fail; - } - talloc_zfree(host); - } - state->host_filter = talloc_asprintf_append(state->host_filter, "))"); - if (state->host_filter == NULL) { - ret = ENOMEM; - goto fail; - } - - state->host_attrs = talloc_array(state, const char *, 8); - if (state->host_attrs == NULL) { - DEBUG(1, ("Failed to allocate host attribute list.\n")); - ret = ENOMEM; - goto fail; - } - state->host_attrs[0] = IPA_MEMBEROF; - state->host_attrs[1] = IPA_HOST_SERVERHOSTNAME; - state->host_attrs[2] = IPA_HOST_FQDN; - state->host_attrs[3] = "objectClass"; - state->host_attrs[4] = SYSDB_ORIG_DN; - state->host_attrs[5] = SYSDB_ORIG_MEMBEROF; - state->host_attrs[6] = IPA_UNIQUE_ID; - state->host_attrs[7] = NULL; - - if (hbac_ctx_is_offline(state->hbac_ctx)) { - ret = hbac_sysdb_data_recv(state, hbac_ctx_sysdb(state->hbac_ctx), - hbac_ctx_be(state->hbac_ctx)->domain, - state->host_filter, HBAC_HOSTS_SUBDIR, - state->host_attrs, - &state->host_reply_count, - &state->host_reply_list); - if (ret != EOK) { - DEBUG(1, ("hbac_sysdb_data_recv failed.\n")); - goto fail; - } - hbac_get_host_memberof(req, true); - tevent_req_post(req, hbac_ctx_ev(state->hbac_ctx)); - return req; - } - - sdap_handle = sdap_id_op_handle(hbac_ctx_sdap_id_op(state->hbac_ctx)); - if (sdap_handle == NULL) { - DEBUG(1, ("Bug: sdap_id_op is disconnected.\n")); - ret = EIO; - goto fail; - } - subreq = sdap_get_generic_send(state, hbac_ctx_ev(state->hbac_ctx), - hbac_ctx_sdap_id_ctx(state->hbac_ctx)->opts, - sdap_handle, - state->hbac_ctx->hbac_search_base, - LDAP_SCOPE_SUB, - state->host_filter, - state->host_attrs, - NULL, 0, - dp_opt_get_int( - hbac_ctx_sdap_id_ctx(state->hbac_ctx)->opts->basic, - SDAP_ENUM_SEARCH_TIMEOUT)); - - if (subreq == NULL) { - DEBUG(1, ("sdap_get_generic_send failed.\n")); - ret = ENOMEM; - goto fail; - } - - tevent_req_set_callback(subreq, hbac_get_host_memberof_done, req); - - return req; - -fail: - tevent_req_error(req, ret); - tevent_req_post(req, hbac_ctx_ev(state->hbac_ctx)); - return req; -} - -static void hbac_get_host_memberof_done(struct tevent_req *subreq) -{ - struct tevent_req *req = tevent_req_callback_data(subreq, - struct tevent_req); - struct hbac_get_host_info_state *state = tevent_req_data(req, - struct hbac_get_host_info_state); - int ret; - - ret = hbac_sdap_data_recv(subreq, state, &state->host_reply_count, - &state->host_reply_list); - talloc_zfree(subreq); - if (ret != EOK) { - tevent_req_error(req, ret); - return; - } - - hbac_get_host_memberof(req, false); -} - -static bool hbac_is_known_host(size_t host_reply_count, - struct sysdb_attrs **host_reply_list, - const char *fqdn) -{ - int i; - const char *new_fqdn; - int ret; - - if (!host_reply_list || !fqdn) { - return false; - } - - for (i = 0; i < host_reply_count; i++) { - ret = sysdb_attrs_get_string(host_reply_list[i], IPA_HOST_FQDN, - &new_fqdn); - if (ret != 0) { - DEBUG(1, ("missing FQDN in new HBAC host record\n")); - continue; - } - - if(strcmp(new_fqdn, fqdn) == 0) { - return true; - } - } - - return false; -} - -static void hbac_get_host_memberof(struct tevent_req *req, bool offline) -{ - struct hbac_get_host_info_state *state = - tevent_req_data(req, struct hbac_get_host_info_state); - struct sysdb_ctx *sysdb; - struct sss_domain_info *domain; - bool in_transaction = false; - int ret; - int i; - const char *fqdn_attrs[] = { IPA_HOST_FQDN, NULL }; - const char *fqdn; - - size_t cached_count; - struct ldb_message **cached_entries = 0; - - if (offline) { - tevent_req_done(req); - return; - } - - sysdb = hbac_ctx_sysdb(state->hbac_ctx); - domain = hbac_ctx_be(state->hbac_ctx)->domain; - - ret = sysdb_transaction_start(sysdb); - if (ret != EOK) { - tevent_req_error(req, ret); - return; - } - in_transaction = true; - - ret = sysdb_search_custom(state, sysdb, domain, - state->host_filter, HBAC_HOSTS_SUBDIR, - fqdn_attrs, - &cached_count, - &cached_entries); - - if (ret == ENOENT) { - cached_count = 0; - ret = EOK; - } - - if (ret) { - DEBUG(1, ("sysdb_search_custom failed: [%d](%s)\n", ret, strerror(ret))); - goto fail; - } - - for (i = 0; i < cached_count; i++) { - fqdn = ldb_msg_find_attr_as_string(cached_entries[i], IPA_HOST_FQDN, NULL); - if (!fqdn) { - DEBUG(1, ("missing FQDN in cached HBAC host record\n")); - } else if (hbac_is_known_host(state->host_reply_count, - state->host_reply_list, fqdn)) { - continue; - } else { - DEBUG(9, ("deleting obsolete HBAC host record for %s\n", fqdn)); - } - - ret = sysdb_delete_entry(sysdb, cached_entries[i]->dn, true); - if (ret) { - DEBUG(1, ("sysdb_delete_entry failed: [%d](%s)\n", ret, strerror(ret))); - goto fail; - } - } - - talloc_zfree(cached_entries); - - ret = sysdb_transaction_commit(sysdb); - if (ret) { - DEBUG(1, ("sysdb_transaction_commit failed.\n")); - goto fail; - } - in_transaction = false; - - tevent_req_done(req); - return; - -fail: - talloc_zfree(cached_entries); - - if (in_transaction) { - sysdb_transaction_cancel(sysdb); - } - tevent_req_error(req, ret); - return; -} - -static int hbac_get_host_info_recv(struct tevent_req *req, TALLOC_CTX *memctx, - size_t *hbac_hosts_count, - struct sysdb_attrs ***hbac_hosts_list) -{ - size_t c; - struct hbac_get_host_info_state *state = tevent_req_data(req, - struct hbac_get_host_info_state); - - TEVENT_REQ_RETURN_ON_ERROR(req); - - *hbac_hosts_count = state->host_reply_count; - *hbac_hosts_list = talloc_steal(memctx, state->host_reply_list); - for (c = 0; c < state->host_reply_count; c++) { - talloc_steal(memctx, state->host_reply_list[c]); - } - - return EOK; -} - - -struct hbac_get_rules_state { - struct hbac_ctx *hbac_ctx; - - const char *host_dn; - const char **memberof; - char *hbac_filter; - const char **hbac_attrs; - - struct ldb_message *old_rules; - struct sysdb_attrs **hbac_reply_list; - size_t hbac_reply_count; - int current_item; -}; - -static void hbac_rule_get_done(struct tevent_req *subreq); - -static struct tevent_req *hbac_get_rules_send(TALLOC_CTX *memctx, - struct hbac_ctx *hbac_ctx, - const char *host_dn, - const char **memberof) -{ - struct tevent_req *req = NULL; - struct tevent_req *subreq = NULL; - struct hbac_get_rules_state *state; - struct sdap_handle *sdap_handle; - char *host_dn_clean; - int ret; - int i; - - if (host_dn == NULL) { - DEBUG(1, ("Missing host_dn.\n")); - return NULL; - } - - req = tevent_req_create(memctx, &state, struct hbac_get_rules_state); - if (req == NULL) { - DEBUG(1, ("tevent_req_create failed.\n")); - return NULL; - } - - state->hbac_ctx = hbac_ctx; - state->host_dn = host_dn; - state->memberof = memberof; - - state->old_rules = NULL; - state->hbac_reply_list = NULL; - state->hbac_reply_count = 0; - state->current_item = 0; - - state->hbac_attrs = talloc_array(state, const char *, 17); - if (state->hbac_attrs == NULL) { - DEBUG(1, ("Failed to allocate HBAC attribute list.\n")); - ret = ENOMEM; - goto fail; - } - state->hbac_attrs[0] = IPA_ACCESS_RULE_TYPE; - state->hbac_attrs[1] = IPA_MEMBER_USER; - state->hbac_attrs[2] = IPA_USER_CATEGORY; - state->hbac_attrs[3] = IPA_SERVICE_NAME; - state->hbac_attrs[4] = IPA_SOURCE_HOST; - state->hbac_attrs[5] = IPA_SOURCE_HOST_CATEGORY; - state->hbac_attrs[6] = IPA_EXTERNAL_HOST; - state->hbac_attrs[7] = IPA_UNIQUE_ID; - state->hbac_attrs[8] = IPA_ENABLED_FLAG; - state->hbac_attrs[9] = IPA_CN; - state->hbac_attrs[10] = OBJECTCLASS; - state->hbac_attrs[11] = IPA_MEMBER_HOST; - state->hbac_attrs[12] = IPA_HOST_CATEGORY; - state->hbac_attrs[13] = IPA_MEMBER_SERVICE; - state->hbac_attrs[14] = IPA_SERVICE_CATEGORY; - state->hbac_attrs[15] = SYSDB_ORIG_DN; - state->hbac_attrs[16] = NULL; - - ret = sss_filter_sanitize(state, host_dn, &host_dn_clean); - if (ret != EOK) { - goto fail; - } - - state->hbac_filter = talloc_asprintf(state, - "(&(objectclass=ipaHBACRule)" - "(%s=%s)(|(%s=%s)(%s=%s)", - IPA_ENABLED_FLAG, IPA_TRUE_VALUE, - IPA_HOST_CATEGORY, "all", - IPA_MEMBER_HOST, host_dn_clean); - if (state->hbac_filter == NULL) { - ret = ENOMEM; - goto fail; - } - talloc_zfree(host_dn_clean); - - for (i = 0; memberof[i] != NULL; i++) { - state->hbac_filter = talloc_asprintf_append(state->hbac_filter, - "(%s=%s)", - IPA_MEMBER_HOST, - memberof[i]); - if (state->hbac_filter == NULL) { - ret = ENOMEM; - goto fail; - } - } - state->hbac_filter = talloc_asprintf_append(state->hbac_filter, "))"); - if (state->hbac_filter == NULL) { - ret = ENOMEM; - goto fail; - } - - DEBUG(9, ("HBAC rule filter: [%s].\n", state->hbac_filter)); - - if (hbac_ctx_is_offline(state->hbac_ctx)) { - ret = hbac_sysdb_data_recv(state, hbac_ctx_sysdb(state->hbac_ctx), - hbac_ctx_be(state->hbac_ctx)->domain, - state->hbac_filter, HBAC_RULES_SUBDIR, - state->hbac_attrs, - &state->hbac_reply_count, - &state->hbac_reply_list); - if (ret) { - DEBUG(1, ("hbac_sysdb_data_recv failed.\n")); - goto fail; - } - tevent_req_done(req); - tevent_req_post(req, hbac_ctx_ev(state->hbac_ctx)); - return req; - } - - sdap_handle = sdap_id_op_handle(hbac_ctx_sdap_id_op(state->hbac_ctx)); - if (sdap_handle == NULL) { - DEBUG(1, ("Bug: sdap_id_op is disconnected.\n")); - ret = EIO; - goto fail; - } - subreq = sdap_get_generic_send(state, hbac_ctx_ev(state->hbac_ctx), - hbac_ctx_sdap_id_ctx(state->hbac_ctx)->opts, - sdap_handle, - state->hbac_ctx->hbac_search_base, - LDAP_SCOPE_SUB, - state->hbac_filter, - state->hbac_attrs, - NULL, 0, - dp_opt_get_int( - hbac_ctx_sdap_id_ctx(state->hbac_ctx)->opts->basic, - SDAP_ENUM_SEARCH_TIMEOUT)); - - if (subreq == NULL) { - DEBUG(1, ("sdap_get_generic_send failed.\n")); - ret = ENOMEM; - goto fail; - } - - tevent_req_set_callback(subreq, hbac_rule_get_done, req); - - return req; - -fail: - tevent_req_error(req, ret); - tevent_req_post(req, hbac_ctx_ev(state->hbac_ctx)); - return req; -} - -static void hbac_rule_get_done(struct tevent_req *subreq) -{ - struct tevent_req *req = tevent_req_callback_data(subreq, - struct tevent_req); - struct hbac_get_rules_state *state = tevent_req_data(req, - struct hbac_get_rules_state); - int ret; - - ret = hbac_sdap_data_recv(subreq, state, &state->hbac_reply_count, - &state->hbac_reply_list); - talloc_zfree(subreq); - if (ret != EOK) { - tevent_req_error(req, ret); - return; - } - - tevent_req_done(req); - return; -} - -static int hbac_get_rules_recv(struct tevent_req *req, TALLOC_CTX *memctx, - size_t *hbac_rule_count, - struct sysdb_attrs ***hbac_rule_list) -{ - struct hbac_get_rules_state *state = tevent_req_data(req, - struct hbac_get_rules_state); - - TEVENT_REQ_RETURN_ON_ERROR(req); - - *hbac_rule_count = state->hbac_reply_count; - *hbac_rule_list = talloc_steal(memctx, state->hbac_reply_list); - /* we do not need to steal each hbac_reply_list[i] - * as it belongs to hbac_reply_list memory block */ - return EOK; -} - -enum hbac_result { - HBAC_ALLOW = 1, - HBAC_DENY, - HBAC_NOT_APPLICABLE -}; - -enum check_result { - RULE_APPLICABLE = 0, - RULE_NOT_APPLICABLE, - RULE_ERROR -}; - -static errno_t get_service_data(const char *cn, size_t count, - struct sysdb_attrs **list, const char **dn, - struct ldb_message_element **mof) -{ - int ret; - int i; - int j; - struct ldb_message_element *el; - - for (i = 0; i < count; i++) { - ret = sysdb_attrs_get_el(list[i], IPA_CN, &el); - if (ret != EOK) { - DEBUG(1, ("sysdb_attrs_get_el failed.\n")); - return ENOENT; - } - if (el->num_values == 0) { - DEBUG(9, ("No cn found.\n")); - return ENOENT; - } else { - for (j = 0; j < el->num_values; j++) { - if (strlen(cn) == el->values[j].length && - strncmp(cn, (const char *) el->values[j].data, - el->values[j].length) == 0) { - - ret = sysdb_attrs_get_string(list[i], SYSDB_ORIG_DN, dn); - if (ret != EOK) { - DEBUG(1, ("sysdb_attrs_get_string failed.\n")); - return ret; - } - - ret = sysdb_attrs_get_el(list[i], SYSDB_ORIG_MEMBEROF, mof); - if (ret != EOK) { - DEBUG(1, ("sysdb_attrs_get_el failed.\n")); - return ret; - } - - return EOK; - } - } - } - } - - return ENOENT; -} - -enum check_result check_service(struct hbac_ctx *hbac_ctx, - struct sysdb_attrs *rule_attrs) -{ - int ret; - int i; - int g; - struct ldb_message_element *el; - const char *service_dn; - struct ldb_message_element *service_memberof; - - if (hbac_ctx->pd->service == NULL) { - DEBUG(1, ("No service in pam data, assuming error.\n")); - return RULE_ERROR; - } - - ret = sysdb_attrs_get_el(rule_attrs, IPA_SERVICE_CATEGORY, &el); - if (ret != EOK) { - DEBUG(1, ("sysdb_attrs_get_el failed.\n")); - return RULE_ERROR; - } - if (el->num_values == 0) { - DEBUG(9, ("Service category is not set.\n")); - } else { - for (i = 0; i < el->num_values; i++) { - if (strncasecmp("all", (const char *) el->values[i].data, - el->values[i].length) == 0) { - DEBUG(9, ("Service category is set to 'all', rule applies.\n")); - return RULE_APPLICABLE; - } - DEBUG(9, ("Unsupported service category [%.*s].\n", - el->values[i].length, - (char *) el->values[i].data)); - } - } - - ret = get_service_data(hbac_ctx->pd->service, hbac_ctx->hbac_services_count, - hbac_ctx->hbac_services_list, &service_dn, - &service_memberof); - if (ret != EOK) { - DEBUG(1, ("Cannot find original DN for service [%s].\n", - hbac_ctx->pd->service)); - return RULE_ERROR; - } - DEBUG(9, ("OriginalDN for service [%s]: [%s].\n", hbac_ctx->pd->service, - service_dn)); - - ret = sysdb_attrs_get_el(rule_attrs, IPA_MEMBER_SERVICE, &el); - if (ret != EOK) { - DEBUG(1, ("sysdb_attrs_get_el failed.\n")); - return RULE_ERROR; - } - if (el->num_values == 0) { - DEBUG(9, ("No service or service group specified, rule does not apply.\n")); - return RULE_NOT_APPLICABLE; - } - - for (i = 0; i < el->num_values; i++) { - if (strncmp(service_dn, (const char *) el->values[i].data, - el->values[i].length) == 0) { - DEBUG(9, ("Service [%s] found in the list of allowed " - "services.\n", hbac_ctx->pd->service)); - return RULE_APPLICABLE; - } - - for (g = 0; g < service_memberof->num_values; g++) { - if (service_memberof->values[g].length == el->values[i].length && - strncmp((const char *) service_memberof->values[g].data, - (const char *) el->values[i].data, - el->values[i].length) == 0) { - DEBUG(9, ("Service [%s] is a member of a group in the list of " - "allowed service groups.\n", hbac_ctx->pd->service)); - return RULE_APPLICABLE; - } - } - } - - DEBUG(9, ("Service [%s] was not found in the list of allowed services and " - "service groups.\n", hbac_ctx->pd->service)); - return RULE_NOT_APPLICABLE; -} - -enum check_result check_user(struct hbac_ctx *hbac_ctx, - struct sysdb_attrs *rule_attrs) -{ - int ret; - int i; - int g; - struct ldb_message_element *el; - - if (hbac_ctx->user_dn == NULL) { - DEBUG(1, ("No user DN available, this should never happen.\n")); - return RULE_ERROR; - } - - ret = sysdb_attrs_get_el(rule_attrs, IPA_USER_CATEGORY, &el); - if (ret != EOK) { - DEBUG(1, ("sysdb_attrs_get_el failed.\n")); - return RULE_ERROR; - } - if (el->num_values == 0) { - DEBUG(9, ("User category is not set.\n")); - } else { - for (i = 0; i < el->num_values; i++) { - if (strncasecmp("all", (const char *) el->values[i].data, - el->values[i].length) == 0) { - DEBUG(9, ("User category is set to 'all', rule applies.\n")); - return RULE_APPLICABLE; - } - DEBUG(9, ("Unsupported user category [%.*s].\n", - el->values[i].length, - (char *) el->values[i].data)); - } - } - - ret = sysdb_attrs_get_el(rule_attrs, IPA_MEMBER_USER, &el); - if (ret != EOK) { - DEBUG(1, ("sysdb_attrs_get_el failed.\n")); - return RULE_ERROR; - } - if (el->num_values == 0) { - DEBUG(9, ("No user specified, rule does not apply.\n")); - return RULE_NOT_APPLICABLE; - } else { - for (i = 0; i < el->num_values; i++) { - DEBUG(9, ("Searching matches for [%.*s].\n", el->values[i].length, - (const char *) el->values[i].data)); - DEBUG(9, ("Checking user [%s].\n", hbac_ctx->user_dn)); - if (strncmp(hbac_ctx->user_dn, (const char *) el->values[i].data, - el->values[i].length) == 0) { - DEBUG(9, ("User [%s] found, rule applies.\n", - hbac_ctx->user_dn)); - return RULE_APPLICABLE; - } - - for (g = 0; g < hbac_ctx->groups_count; g++) { - DEBUG(9, ("Checking group [%s].\n", hbac_ctx->groups[g])); - if (strncmp(hbac_ctx->groups[g], - (const char *) el->values[i].data, - el->values[i].length) == 0) { - DEBUG(9, ("Group [%s] found, rule applies.\n", - hbac_ctx->groups[g])); - return RULE_APPLICABLE; - } - } - } - DEBUG(9, ("No matching user found, rule does not apply.\n")); - return RULE_NOT_APPLICABLE; - } - - return RULE_ERROR; -} - -enum check_result check_remote_hosts(const char *rhost, - struct hbac_host_info *hhi, - struct sysdb_attrs *rule_attrs) + struct dp_option *ipa_options) { + char *base; int ret; - int i; - int m; - struct ldb_message_element *cat_el; - struct ldb_message_element *src_el; - struct ldb_message_element *ext_el; - - if (hhi == NULL && (rhost == NULL || *rhost == '\0')) { - DEBUG(1, ("No remote host information specified, assuming error.\n")); - return RULE_ERROR; - } - - ret = sysdb_attrs_get_el(rule_attrs, IPA_SOURCE_HOST_CATEGORY, &cat_el); - if (ret != EOK) { - DEBUG(1, ("sysdb_attrs_get_el failed.\n")); - return RULE_ERROR; - } - if (cat_el->num_values == 0) { - DEBUG(9, ("Source host category not set.\n")); - } else { - for(i = 0; i < cat_el->num_values; i++) { - if (strncasecmp("all", (const char *) cat_el->values[i].data, - cat_el->values[i].length) == 0) { - DEBUG(9, ("Source host category is set to 'all', " - "rule applies.\n")); - return RULE_APPLICABLE; - } - DEBUG(9, ("Unsupported source hosts category [%.*s].\n", - cat_el->values[i].length, - (char *) cat_el->values[i].data)); - } - } - - ret = sysdb_attrs_get_el(rule_attrs, IPA_SOURCE_HOST, &src_el); - if (ret != EOK) { - DEBUG(1, ("sysdb_attrs_get_el failed.\n")); - return RULE_ERROR; - } - ret = sysdb_attrs_get_el(rule_attrs, IPA_EXTERNAL_HOST, &ext_el); - if (ret != EOK) { - DEBUG(1, ("sysdb_attrs_get_el failed.\n")); - return RULE_ERROR; - } - - if (src_el->num_values == 0 && ext_el->num_values == 0) { - DEBUG(9, ("No remote host specified in rule, rule does not apply.\n")); - return RULE_NOT_APPLICABLE; - } else { - if (hhi != NULL) { - for (i = 0; i < src_el->num_values; i++) { - if (strncasecmp(hhi->dn, (const char *) src_el->values[i].data, - src_el->values[i].length) == 0) { - DEBUG(9, ("Source host [%s] found, rule applies.\n", - hhi->dn)); - return RULE_APPLICABLE; - } - for (m = 0; hhi->memberof[m] != NULL; m++) { - if (strncasecmp(hhi->memberof[m], - (const char *) src_el->values[i].data, - src_el->values[i].length) == 0) { - DEBUG(9, ("Source host group [%s] found, rule applies.\n", - hhi->memberof[m])); - return RULE_APPLICABLE; - } - } - } - } - - if (rhost != NULL && *rhost != '\0') { - for (i = 0; i < ext_el->num_values; i++) { - if (strncasecmp(rhost, (const char *) ext_el->values[i].data, - ext_el->values[i].length) == 0) { - DEBUG(9, ("External host [%s] found, rule applies.\n", - rhost)); - return RULE_APPLICABLE; - } - } - } - DEBUG(9, ("No matching remote host found.\n")); - return RULE_NOT_APPLICABLE; - } - - return RULE_ERROR; -} - -static errno_t check_if_rule_applies(struct hbac_ctx *hbac_ctx, - struct sysdb_attrs *rule_attrs, - enum hbac_result *result) { - int ret; - struct ldb_message_element *el; - enum hbac_result rule_type; - char *rule_name; - struct pam_data *pd = hbac_ctx->pd; - ret = sysdb_attrs_get_el(rule_attrs, IPA_CN, &el); - if (ret != EOK) { - DEBUG(1, ("sysdb_attrs_get_el failed.\n")); - return ret; - } - if (el->num_values == 0) { - DEBUG(4, ("rule has no name, assuming '(none)'.\n")); - rule_name = talloc_strdup(rule_attrs, "(none)"); - } else { - rule_name = talloc_strndup(rule_attrs, (const char*) el->values[0].data, - el->values[0].length); - } - if (rule_name == NULL) { - DEBUG(1, ("talloc_strdup failed.\n")); - return ENOMEM; + base = dp_opt_get_string(ipa_options, IPA_HBAC_SEARCH_BASE); + if (base != NULL) { + return talloc_strdup(mem_ctx, base); } - DEBUG(9, ("Processsing rule [%s].\n", rule_name)); - ret = sysdb_attrs_get_el(rule_attrs, IPA_ENABLED_FLAG, &el); - if (ret != EOK) { - DEBUG(1, ("Failed to find out if rule is enabled or not, " - "assuming it is enabled.\n")); - } else { - if (el->num_values == 0) { - DEBUG(1, ("Failed to find out if rule is enabled or not, " - "assuming it is enabled.\n")); - } else { - if (strncasecmp("false", (const char*) el->values[0].data, - el->values[0].length) == 0) { - DEBUG(7, ("Rule is disabled.\n")); - *result = HBAC_NOT_APPLICABLE; - return EOK; - } - } - } + DEBUG(9, ("ipa_hbac_search_base not available, trying base DN.\n")); - /* rule type */ - ret = sysdb_attrs_get_el(rule_attrs, IPA_ACCESS_RULE_TYPE, &el); + ret = domain_to_basedn(mem_ctx, + dp_opt_get_string(ipa_options, IPA_KRB5_REALM), + &base); if (ret != EOK) { - DEBUG(1, ("sysdb_attrs_get_el failed.\n")); - return ret; - } - if (el->num_values == 0) { - DEBUG(4, ("rule has no type, assuming 'deny'.\n")); - rule_type = HBAC_DENY; - } else if (el->num_values == 1) { - if (strncasecmp((const char *) el->values[0].data, "allow", - el->values[0].length) == 0) { - rule_type = HBAC_ALLOW; - } else { - rule_type = HBAC_DENY; - } - } else { - DEBUG(1, ("rule has an unsupported number of values [%d].\n", - el->num_values)); - return EINVAL; - } - - ret = check_service(hbac_ctx, rule_attrs); - if (ret != RULE_APPLICABLE) { - goto not_applicable; - } - - ret = check_user(hbac_ctx, rule_attrs); - if (ret != RULE_APPLICABLE) { - goto not_applicable; - } - - ret = check_remote_hosts(pd->rhost, hbac_ctx->remote_hhi, rule_attrs); - if (ret != RULE_APPLICABLE) { - goto not_applicable; + DEBUG(1, ("domain_to_basedn failed.\n")); + return NULL; } - *result = rule_type; - - return EOK; - -not_applicable: - if (ret == RULE_NOT_APPLICABLE) { - *result = HBAC_NOT_APPLICABLE; - } else { - *result = HBAC_DENY; - } - return EOK; + return base; } -static int evaluate_ipa_hbac_rules(struct hbac_ctx *hbac_ctx, - bool *access_allowed) +static void ipa_access_reply(struct hbac_ctx *hbac_ctx, int pam_status) { - bool allow_matched = false; - enum hbac_result result; - int ret; - int i; - - *access_allowed = false; - - for (i = 0; i < hbac_ctx->hbac_rule_count ; i++) { - - ret = check_if_rule_applies(hbac_ctx, hbac_ctx->hbac_rule_list[i], - &result); - if (ret != EOK) { - DEBUG(1, ("check_if_rule_applies failed.\n")); - return ret; - } + struct be_req *be_req = hbac_ctx->be_req; + struct pam_data *pd; + pd = talloc_get_type(be_req->req_data, struct pam_data); + pd->pam_status = pam_status; - switch (result) { - case HBAC_DENY: - DEBUG(3, ("Access denied by single rule.\n")); - return EOK; - break; - case HBAC_ALLOW: - allow_matched = true; - DEBUG(9, ("Current rule allows access.\n")); - break; - default: - DEBUG(9, ("Current rule does not apply.\n")); - } + /* destroy HBAC context now to release all used resources and LDAP connection */ + talloc_zfree(hbac_ctx); + if (pam_status == PAM_SUCCESS || pam_status == PAM_PERM_DENIED) { + be_req->fn(be_req, DP_ERR_OK, pam_status, NULL); + } else { + be_req->fn(be_req, DP_ERR_FATAL, pam_status, NULL); } +} - *access_allowed = allow_matched; +enum hbac_result { + HBAC_ALLOW = 1, + HBAC_DENY, + HBAC_NOT_APPLICABLE +}; - return EOK; -} +enum check_result { + RULE_APPLICABLE = 0, + RULE_NOT_APPLICABLE, + RULE_ERROR +}; static int hbac_retry(struct hbac_ctx *hbac_ctx); static void hbac_connect_done(struct tevent_req *subreq); static bool hbac_check_step_result(struct hbac_ctx *hbac_ctx, int ret); static int hbac_get_host_info_step(struct hbac_ctx *hbac_ctx); -static void hbac_get_host_info_done(struct tevent_req *req); -static void hbac_get_rules_done(struct tevent_req *req); -static void hbac_get_service_data_done(struct tevent_req *req); + +static void ipa_hbac_evaluate_rules(struct hbac_ctx *hbac_ctx); void ipa_access_handler(struct be_req *be_req) { struct pam_data *pd; struct hbac_ctx *hbac_ctx; + const char *deny_method; int pam_status = PAM_SYSTEM_ERR; struct ipa_access_ctx *ipa_access_ctx; - bool offline; int ret; pd = talloc_get_type(be_req->req_data, struct pam_data); @@ -1666,11 +109,13 @@ void ipa_access_handler(struct be_req *be_req) DEBUG(1, ("talloc failed.\n")); goto fail; } + hbac_ctx->be_req = be_req; hbac_ctx->pd = pd; ipa_access_ctx = talloc_get_type( be_req->be_ctx->bet_info[BET_ACCESS].pvt_bet_data, struct ipa_access_ctx); + hbac_ctx->access_ctx = ipa_access_ctx; hbac_ctx->sdap_ctx = ipa_access_ctx->sdap_ctx; hbac_ctx->ipa_options = ipa_access_ctx->ipa_options; hbac_ctx->tr_ctx = ipa_access_ctx->tr_ctx; @@ -1681,16 +126,12 @@ void ipa_access_handler(struct be_req *be_req) goto fail; } - offline = be_is_offline(be_req->be_ctx); - DEBUG(9, ("Connection status is [%s].\n", offline ? "offline" : "online")); - - if (!offline) { - hbac_ctx->sdap_op = sdap_id_op_create(hbac_ctx, - hbac_ctx_sdap_id_ctx(hbac_ctx)->conn_cache); - if (!hbac_ctx->sdap_op) { - DEBUG(1, ("sdap_id_op_create failed.\n")); - goto fail; - } + deny_method = dp_opt_get_string(hbac_ctx->ipa_options, + IPA_HBAC_DENY_METHOD); + if (strcasecmp(deny_method, "IGNORE") == 0) { + hbac_ctx->get_deny_rules = false; + } else { + hbac_ctx->get_deny_rules = true; } ret = hbac_retry(hbac_ctx); @@ -1713,18 +154,48 @@ static int hbac_retry(struct hbac_ctx *hbac_ctx) { struct tevent_req *subreq; int ret; + bool offline; + time_t now, refresh_interval; + struct ipa_access_ctx *access_ctx = hbac_ctx->access_ctx; - if (hbac_ctx_is_offline(hbac_ctx)) { - return hbac_get_host_info_step(hbac_ctx); - } + offline = be_is_offline(hbac_ctx->be_req->be_ctx); + DEBUG(9, ("Connection status is [%s].\n", offline ? "offline" : "online")); + + refresh_interval = dp_opt_get_int(hbac_ctx->ipa_options, + IPA_HBAC_REFRESH); - subreq = sdap_id_op_connect_send(hbac_ctx_sdap_id_op(hbac_ctx), hbac_ctx, &ret); - if (!subreq) { - DEBUG(1, ("sdap_id_op_connect_send failed: %d(%s).\n", ret, strerror(ret))); - return ret; + now = time(NULL); + if (now < access_ctx->last_update + refresh_interval) { + /* Simulate offline mode and just go to the cache */ + DEBUG(6, ("Performing cached HBAC evaluation\n")); + offline = true; } - tevent_req_set_callback(subreq, hbac_connect_done, hbac_ctx); + if (!offline) { + if (hbac_ctx->sdap_op == NULL) { + hbac_ctx->sdap_op = sdap_id_op_create(hbac_ctx, + hbac_ctx_sdap_id_ctx(hbac_ctx)->conn_cache); + if (hbac_ctx->sdap_op == NULL) { + DEBUG(1, ("sdap_id_op_create failed.\n")); + return EIO; + } + } + + subreq = sdap_id_op_connect_send(hbac_ctx_sdap_id_op(hbac_ctx), hbac_ctx, &ret); + if (!subreq) { + DEBUG(1, ("sdap_id_op_connect_send failed: %d(%s).\n", ret, strerror(ret))); + talloc_zfree(hbac_ctx->sdap_op); + return ret; + } + + tevent_req_set_callback(subreq, hbac_connect_done, hbac_ctx); + } else { + /* Evaluate the rules based on what we have in the + * sysdb + */ + ipa_hbac_evaluate_rules(hbac_ctx); + return EOK; + } return EOK; } @@ -1739,6 +210,9 @@ static void hbac_connect_done(struct tevent_req *subreq) if (dp_error == DP_ERR_OFFLINE) { /* switching to offline mode */ talloc_zfree(hbac_ctx->sdap_op); + + ipa_hbac_evaluate_rules(hbac_ctx); + return; } else if (ret != EOK) { goto fail; } @@ -1754,6 +228,24 @@ fail: ipa_access_reply(hbac_ctx, PAM_SYSTEM_ERR); } +static void hbac_clear_rule_data(struct hbac_ctx *hbac_ctx) +{ + hbac_ctx->host_count = 0; + talloc_zfree(hbac_ctx->hosts); + + hbac_ctx->hostgroup_count = 0; + talloc_zfree(hbac_ctx->hostgroups); + + hbac_ctx->service_count = 0; + talloc_zfree(hbac_ctx->services); + + hbac_ctx->servicegroup_count = 0; + talloc_zfree(hbac_ctx->servicegroups); + + hbac_ctx->rule_count = 0; + talloc_zfree(hbac_ctx->rules); +} + /* Check the step result code and continue, retry, get offline result or abort accordingly */ static bool hbac_check_step_result(struct hbac_ctx *hbac_ctx, int ret) { @@ -1774,6 +266,10 @@ static bool hbac_check_step_result(struct hbac_ctx *hbac_ctx, int ret) if (dp_error == DP_ERR_OFFLINE) { /* switching to offline mode */ talloc_zfree(hbac_ctx->sdap_op); + + /* Free any of the results we've gotten */ + hbac_clear_rule_data(hbac_ctx); + dp_error = DP_ERR_OK; } @@ -1790,173 +286,374 @@ static bool hbac_check_step_result(struct hbac_ctx *hbac_ctx, int ret) return false; } +static void hbac_get_service_info_step(struct tevent_req *req); +static void hbac_get_rule_info_step(struct tevent_req *req); +static void hbac_sysdb_save (struct tevent_req *req); + static int hbac_get_host_info_step(struct hbac_ctx *hbac_ctx) { - struct pam_data *pd = hbac_ctx->pd; - const char *hostlist[3]; - struct tevent_req *subreq; - - hostlist[0] = dp_opt_get_cstring(hbac_ctx->ipa_options, IPA_HOSTNAME); - if (hostlist[0] == NULL) { - DEBUG(1, ("ipa_hostname not available.\n")); - return EINVAL; - } - if (pd->rhost != NULL && *pd->rhost != '\0') { - hostlist[1] = pd->rhost; - hostlist[2] = NULL; - } else { - hostlist[1] = NULL; - pd->rhost = discard_const_p(char, hostlist[0]); - } - - subreq = hbac_get_host_info_send(hbac_ctx, hbac_ctx, hostlist); - if (!subreq) { - DEBUG(1, ("hbac_get_host_info_send failed.\n")); + struct tevent_req *req = + ipa_hbac_host_info_send(hbac_ctx, + hbac_ctx_ev(hbac_ctx), + hbac_ctx_sysdb(hbac_ctx), + hbac_ctx_be(hbac_ctx)->domain, + sdap_id_op_handle(hbac_ctx->sdap_op), + hbac_ctx_sdap_id_ctx(hbac_ctx)->opts, + hbac_ctx->hbac_search_base); + if (req == NULL) { + DEBUG(1, ("Could not get host info\n")); return ENOMEM; } + tevent_req_set_callback(req, hbac_get_service_info_step, hbac_ctx); - tevent_req_set_callback(subreq, hbac_get_host_info_done, hbac_ctx); return EOK; } -static void hbac_get_host_info_done(struct tevent_req *req) +static void hbac_get_service_info_step(struct tevent_req *req) { - struct hbac_ctx *hbac_ctx = tevent_req_callback_data(req, struct hbac_ctx); - int ret; - int pam_status = PAM_SYSTEM_ERR; - const char *ipa_hostname; - struct hbac_host_info *local_hhi = NULL; + errno_t ret; + struct hbac_ctx *hbac_ctx = + tevent_req_callback_data(req, struct hbac_ctx); - ret = hbac_get_host_info_recv(req, hbac_ctx, &hbac_ctx->hbac_hosts_count, - &hbac_ctx->hbac_hosts_list); + ret = ipa_hbac_host_info_recv(req, hbac_ctx, + &hbac_ctx->host_count, + &hbac_ctx->hosts, + &hbac_ctx->hostgroup_count, + &hbac_ctx->hostgroups); talloc_zfree(req); + if (!hbac_check_step_result(hbac_ctx, ret)) { + return; + } + /* Get services and service groups */ + req = ipa_hbac_service_info_send(hbac_ctx, + hbac_ctx_ev(hbac_ctx), + hbac_ctx_sysdb(hbac_ctx), + hbac_ctx_be(hbac_ctx)->domain, + sdap_id_op_handle(hbac_ctx->sdap_op), + hbac_ctx_sdap_id_ctx(hbac_ctx)->opts, + hbac_ctx->hbac_search_base); + if (req == NULL) { + DEBUG(1,("Could not get service info\n")); + goto fail; + } + tevent_req_set_callback(req, hbac_get_rule_info_step, hbac_ctx); + return; + +fail: + ipa_access_reply(hbac_ctx, PAM_SYSTEM_ERR); +} + +static void hbac_get_rule_info_step(struct tevent_req *req) +{ + errno_t ret; + size_t i; + const char *ipa_hostname; + const char *hostname; + struct hbac_ctx *hbac_ctx = + tevent_req_callback_data(req, struct hbac_ctx); + + ret = ipa_hbac_service_info_recv(req, hbac_ctx, + &hbac_ctx->service_count, + &hbac_ctx->services, + &hbac_ctx->servicegroup_count, + &hbac_ctx->servicegroups); + talloc_zfree(req); if (!hbac_check_step_result(hbac_ctx, ret)) { return; } + /* Get the ipa_host attrs */ + hbac_ctx->ipa_host = NULL; ipa_hostname = dp_opt_get_cstring(hbac_ctx->ipa_options, IPA_HOSTNAME); if (ipa_hostname == NULL) { DEBUG(1, ("Missing ipa_hostname, this should never happen.\n")); goto fail; } - ret = set_local_and_remote_host_info(hbac_ctx, hbac_ctx->hbac_hosts_count, - hbac_ctx->hbac_hosts_list, ipa_hostname, - hbac_ctx->pd->rhost, &local_hhi, - &hbac_ctx->remote_hhi); - if (ret != EOK) { - DEBUG(1, ("set_local_and_remote_host_info failed.\n")); - goto fail; - } + for (i = 0; i < hbac_ctx->host_count; i++) { + ret = sysdb_attrs_get_string(hbac_ctx->hosts[i], + IPA_HOST_FQDN, + &hostname); + if (ret != EOK) { + DEBUG(1, ("Could not locate IPA host\n")); + goto fail; + } - if (local_hhi == NULL) { - DEBUG(1, ("Missing host info for [%s].\n", ipa_hostname)); - pam_status = PAM_PERM_DENIED; + if (strcmp(hostname, ipa_hostname) == 0) { + hbac_ctx->ipa_host = hbac_ctx->hosts[i]; + break; + } + } + if (hbac_ctx->ipa_host == NULL) { + DEBUG(1, ("Could not locate IPA host\n")); goto fail; } - req = hbac_get_rules_send(hbac_ctx, hbac_ctx, local_hhi->dn, - local_hhi->memberof); + + + /* Get the list of applicable rules */ + req = ipa_hbac_rule_info_send(hbac_ctx, + hbac_ctx->get_deny_rules, + hbac_ctx_ev(hbac_ctx), + hbac_ctx_sysdb(hbac_ctx), + hbac_ctx_be(hbac_ctx)->domain, + sdap_id_op_handle(hbac_ctx->sdap_op), + hbac_ctx_sdap_id_ctx(hbac_ctx)->opts, + hbac_ctx->hbac_search_base, + hbac_ctx->ipa_host); if (req == NULL) { - DEBUG(1, ("hbac_get_rules_send failed.\n")); + DEBUG(1, ("Could not get rules\n")); goto fail; } - tevent_req_set_callback(req, hbac_get_rules_done, hbac_ctx); + tevent_req_set_callback(req, hbac_sysdb_save, hbac_ctx); return; fail: - ipa_access_reply(hbac_ctx, pam_status); + ipa_access_reply(hbac_ctx, PAM_SYSTEM_ERR); } -static void hbac_get_rules_done(struct tevent_req *req) +static void hbac_sysdb_save(struct tevent_req *req) { - struct hbac_ctx *hbac_ctx = tevent_req_callback_data(req, struct hbac_ctx); - int ret; - int pam_status = PAM_SYSTEM_ERR; - - hbac_ctx->hbac_rule_count = 0; - talloc_zfree(hbac_ctx->hbac_rule_list); + errno_t ret; + bool in_transaction = false; + struct hbac_ctx *hbac_ctx = + tevent_req_callback_data(req, struct hbac_ctx); + struct sss_domain_info *domain = hbac_ctx_be(hbac_ctx)->domain; + struct sysdb_ctx *sysdb = hbac_ctx_sysdb(hbac_ctx); + struct ldb_dn *base_dn; + struct be_ctx *be_ctx = hbac_ctx_be(hbac_ctx); + struct ipa_access_ctx *access_ctx = + talloc_get_type(be_ctx->bet_info[BET_ACCESS].pvt_bet_data, + struct ipa_access_ctx); + TALLOC_CTX *tmp_ctx; - ret = hbac_get_rules_recv(req, hbac_ctx, &hbac_ctx->hbac_rule_count, - &hbac_ctx->hbac_rule_list); + ret = ipa_hbac_rule_info_recv(req, hbac_ctx, + &hbac_ctx->rule_count, + &hbac_ctx->rules); talloc_zfree(req); + if (ret == ENOENT) { + /* No rules were found that apply to this + * host. + */ + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ipa_access_reply(hbac_ctx, PAM_SYSTEM_ERR); + return; + } + /* Delete any rules in the sysdb so offline logins + * are also denied. + */ + base_dn = sysdb_custom_subtree_dn(sysdb, tmp_ctx, + domain->name, + HBAC_RULES_SUBDIR); + if (base_dn == NULL) { + talloc_free(tmp_ctx); + ipa_access_reply(hbac_ctx, PAM_SYSTEM_ERR); + return; + } + + ret = sysdb_delete_recursive(tmp_ctx, sysdb, base_dn, true); + talloc_free(tmp_ctx); + if (ret != EOK) { + DEBUG(1, ("sysdb_delete_recursive failed.\n")); + ipa_access_reply(hbac_ctx, PAM_SYSTEM_ERR); + return; + } + + /* If no rules are found, we default to DENY */ + ipa_access_reply(hbac_ctx, PAM_PERM_DENIED); + return; + } if (!hbac_check_step_result(hbac_ctx, ret)) { return; } - req = hbac_get_service_data_send(hbac_ctx, hbac_ctx); - if (req == NULL) { - DEBUG(1, ("hbac_get_service_data_send failed.\n")); - goto failed; + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(0, ("Could not start transaction\n")); + goto fail; } + in_transaction = true; - tevent_req_set_callback(req, hbac_get_service_data_done, hbac_ctx); - return; + /* Save the hosts */ + ret = ipa_hbac_sysdb_save(sysdb, domain, + HBAC_HOSTS_SUBDIR, IPA_HOST_FQDN, + hbac_ctx->host_count, hbac_ctx->hosts, + HBAC_HOSTGROUPS_SUBDIR, IPA_CN, + hbac_ctx->hostgroup_count, + hbac_ctx->hostgroups); + if (ret != EOK) { + DEBUG(1, ("Error saving hosts: [%d][%s]\n", + ret, strerror(ret))); + goto fail; + } -failed: - ipa_access_reply(hbac_ctx, pam_status); -} + /* Save the services */ + ret = ipa_hbac_sysdb_save(sysdb, domain, + HBAC_SERVICES_SUBDIR, IPA_CN, + hbac_ctx->service_count, hbac_ctx->services, + HBAC_SERVICEGROUPS_SUBDIR, IPA_CN, + hbac_ctx->servicegroup_count, + hbac_ctx->servicegroups); + if (ret != EOK) { + DEBUG(1, ("Error saving services: [%d][%s]\n", + ret, strerror(ret))); + goto fail; + } + /* Save the rules */ + ret = ipa_hbac_sysdb_save(sysdb, domain, + HBAC_RULES_SUBDIR, IPA_UNIQUE_ID, + hbac_ctx->rule_count, + hbac_ctx->rules, + NULL, NULL, 0, NULL); + if (ret != EOK) { + DEBUG(1, ("Error saving rules: [%d][%s]\n", + ret, strerror(ret))); + goto fail; + } -static void hbac_get_service_data_done(struct tevent_req *req) -{ - struct hbac_ctx *hbac_ctx = tevent_req_callback_data(req, struct hbac_ctx); - struct pam_data *pd = hbac_ctx->pd; - int ret; - int pam_status = PAM_SYSTEM_ERR; - bool access_allowed = false; + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(0, ("Failed to commit transaction\n")); + goto fail; + } - hbac_ctx->hbac_services_count = 0; - talloc_zfree(hbac_ctx->hbac_services_list); + /* We don't need the rule data any longer, + * the rest of the processing relies on + * sysdb lookups. + */ + hbac_clear_rule_data(hbac_ctx); - ret = hbac_get_service_data_recv(req, hbac_ctx, - &hbac_ctx->hbac_services_count, - &hbac_ctx->hbac_services_list); - talloc_zfree(req); - if (!hbac_check_step_result(hbac_ctx, ret)) { - return; - } + access_ctx->last_update = time(NULL); - if (hbac_ctx->user_dn) { - talloc_free(discard_const_p(TALLOC_CTX, hbac_ctx->user_dn)); - hbac_ctx->user_dn = 0; - } + /* Now evaluate the request against the rules */ + ipa_hbac_evaluate_rules(hbac_ctx); + + return; - if (!hbac_ctx_is_offline(hbac_ctx)) { - ret = hbac_save_data_to_sysdb(hbac_ctx); +fail: + if (in_transaction) { + ret = sysdb_transaction_cancel(sysdb); if (ret != EOK) { - DEBUG(1, ("Failed to save data, " - "offline authentication might not work.\n")); - /* This is not a fatal error. */ + DEBUG(0, ("Could not cancel transaction\n")); } } + ipa_access_reply(hbac_ctx, PAM_SYSTEM_ERR); +} + +static errno_t hbac_get_cached_rules(TALLOC_CTX *mem_ctx, + struct hbac_ctx *hbac_ctx); - hbac_ctx->groups_count = 0; - talloc_zfree(hbac_ctx->groups); +void ipa_hbac_evaluate_rules(struct hbac_ctx *hbac_ctx) +{ + errno_t ret; + struct hbac_rule **hbac_rules; + struct hbac_eval_req *eval_req; + enum hbac_eval_result result; + struct hbac_info *info; - ret = hbac_get_user_info(hbac_ctx, hbac_ctx_be(hbac_ctx), - pd->user, &hbac_ctx->user_dn, - &hbac_ctx->groups_count, &hbac_ctx->groups); + /* Get HBAC rules from the sysdb */ + ret = hbac_get_cached_rules(hbac_ctx, hbac_ctx); if (ret != EOK) { - goto failed; + DEBUG(1, ("Could not retrieve rules from the cache\n")); + ipa_access_reply(hbac_ctx, PAM_SYSTEM_ERR); } - ret = evaluate_ipa_hbac_rules(hbac_ctx, &access_allowed); - if (ret != EOK) { - DEBUG(1, ("evaluate_ipa_hbac_rules failed.\n")); - goto failed; + ret = hbac_ctx_to_rules(hbac_ctx, hbac_ctx, + &hbac_rules, &eval_req); + if (ret == EPERM) { + DEBUG(1, ("DENY rules detected. Denying access to all users\n")); + ipa_access_reply(hbac_ctx, PAM_PERM_DENIED); + return; + } else if (ret != EOK) { + DEBUG(1, ("Could not construct HBAC rules\n")); + ipa_access_reply(hbac_ctx, PAM_SYSTEM_ERR); + return; } - if (access_allowed) { - pam_status = PAM_SUCCESS; - DEBUG(5, ("Access allowed.\n")); - } else { - pam_status = PAM_PERM_DENIED; - DEBUG(3, ("Access denied.\n")); + result = hbac_evaluate(hbac_rules, eval_req, &info); + if (result == HBAC_EVAL_ALLOW) { + DEBUG(3, ("Access granted by HBAC rule [%s]\n", + info->rule_name)); + hbac_free_info(info); + ipa_access_reply(hbac_ctx, PAM_SUCCESS); + return; + } else if (result == HBAC_EVAL_ERROR) { + DEBUG(1, ("Error [%s] occurred in rule [%s]\n", + hbac_error_string(info->code), + info->rule_name)); + hbac_free_info(info); + ipa_access_reply(hbac_ctx, PAM_SYSTEM_ERR); + return; + } else if (result == HBAC_EVAL_OOM) { + DEBUG(1, ("Insufficient memory\n")); + ipa_access_reply(hbac_ctx, PAM_SYSTEM_ERR); + return; + } + + DEBUG(3, ("Access denied by HBAC rules\n")); + hbac_free_info(info); + ipa_access_reply(hbac_ctx, PAM_PERM_DENIED); +} + +static errno_t hbac_get_cached_rules(TALLOC_CTX *mem_ctx, + struct hbac_ctx *hbac_ctx) +{ + errno_t ret; + struct sysdb_ctx *sysdb = hbac_ctx_sysdb(hbac_ctx); + struct sss_domain_info *domain = hbac_ctx_be(hbac_ctx)->domain; + size_t count; + struct ldb_message **msgs; + TALLOC_CTX *tmp_ctx; + char *filter; + const char *attrs[] = { OBJECTCLASS, + IPA_CN, + IPA_UNIQUE_ID, + IPA_ENABLED_FLAG, + IPA_ACCESS_RULE_TYPE, + IPA_MEMBER_USER, + IPA_USER_CATEGORY, + IPA_MEMBER_SERVICE, + IPA_SERVICE_CATEGORY, + IPA_SOURCE_HOST, + IPA_SOURCE_HOST_CATEGORY, + IPA_EXTERNAL_HOST, + IPA_MEMBER_HOST, + IPA_HOST_CATEGORY, + NULL }; + + tmp_ctx = talloc_new(hbac_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(mem_ctx, sysdb, domain, filter, + HBAC_RULES_SUBDIR, attrs, + &count, &msgs); + if (ret != EOK && ret != ENOENT) { + DEBUG(1, ("Error looking up HBAC rules")); + goto done; + } if (ret == ENOENT) { + count = 0; } -failed: - ipa_access_reply(hbac_ctx, pam_status); + ret = msgs2attrs_array(mem_ctx, count, msgs, &hbac_ctx->rules); + if (ret != EOK) { + DEBUG(1, ("Could not convert ldb message to sysdb_attrs\n")); + goto done; + } + hbac_ctx->rule_count = count; + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; } diff --git a/src/providers/ipa/ipa_access.h b/src/providers/ipa/ipa_access.h index 52b2f0707..2a6bdad50 100644 --- a/src/providers/ipa/ipa_access.h +++ b/src/providers/ipa/ipa_access.h @@ -43,26 +43,37 @@ struct ipa_access_ctx { struct sdap_id_ctx *sdap_ctx; struct dp_option *ipa_options; struct time_rules_ctx *tr_ctx; + time_t last_update; }; struct hbac_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 time_rules_ctx *tr_ctx; struct be_req *be_req; struct pam_data *pd; - struct sysdb_attrs **hbac_hosts_list; - size_t hbac_hosts_count; - struct hbac_host_info *remote_hhi; - struct sysdb_attrs **hbac_rule_list; - size_t hbac_rule_count; - const char *user_dn; - size_t groups_count; - const char **groups; + char *hbac_search_base; - struct sysdb_attrs **hbac_services_list; - size_t hbac_services_count; + + /* Hosts */ + size_t host_count; + struct sysdb_attrs **hosts; + size_t hostgroup_count; + struct sysdb_attrs **hostgroups; + struct sysdb_attrs *ipa_host; + + /* Rules */ + bool get_deny_rules; + size_t rule_count; + struct sysdb_attrs **rules; + + /* Services */ + size_t service_count; + struct sysdb_attrs **services; + size_t servicegroup_count; + struct sysdb_attrs **servicegroups; }; /* Get BE context associated with HBAC context */ diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c index c1581305d..115172250 100644 --- a/src/providers/ipa/ipa_common.c +++ b/src/providers/ipa/ipa_common.c @@ -36,7 +36,9 @@ struct dp_option ipa_basic_opts[] = { { "ipa_dyndns_update", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, { "ipa_dyndns_iface", DP_OPT_STRING, NULL_STRING, NULL_STRING}, { "ipa_hbac_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING}, - { "krb5_realm", 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_hbac_treat_deny_as", DP_OPT_STRING, { "DENY_ALL" }, NULL_STRING } }; struct dp_option ipa_def_ldap_opts[] = { diff --git a/src/providers/ipa/ipa_common.h b/src/providers/ipa/ipa_common.h index 922806234..f6e1d4e5b 100644 --- a/src/providers/ipa/ipa_common.h +++ b/src/providers/ipa/ipa_common.h @@ -50,6 +50,8 @@ enum ipa_basic_opt { IPA_DYNDNS_IFACE, IPA_HBAC_SEARCH_BASE, IPA_KRB5_REALM, + IPA_HBAC_REFRESH, + IPA_HBAC_DENY_METHOD, IPA_OPTS_BASIC /* opts counter */ }; diff --git a/src/providers/ipa/ipa_hbac.h b/src/providers/ipa/ipa_hbac.h new file mode 100644 index 000000000..7de49d1ff --- /dev/null +++ b/src/providers/ipa/ipa_hbac.h @@ -0,0 +1,177 @@ +/* + SSSD + + IPA Backend Module -- Access control + + Authors: + Sumit Bose + Stephen Gallagher + + 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 . +*/ + +#ifndef IPA_HBAC_H_ +#define IPA_HBAC_H_ + +#include +#include + +enum hbac_eval_result { + HBAC_EVAL_ERROR = -1, + HBAC_EVAL_ALLOW, + HBAC_EVAL_DENY, + HBAC_EVAL_OOM +}; + +#define HBAC_CATEGORY_NULL 0x0000 /* No service category specified */ +#define HBAC_CATEGORY_ALL 0x0001 /* Rule should apply to all */ + +/* Opaque type contained in hbac_evaluator.c */ +struct hbac_time_rules; + +struct hbac_rule_element { + uint32_t category; + const char **names; + const char **groups; +}; + +struct hbac_rule { + const char *name; + bool enabled; + + /* Services and service groups + * for which this rule applies + */ + struct hbac_rule_element *services; + + /* Users and groups for which this + * rule applies + */ + struct hbac_rule_element *users; + + /* Target hosts for which this rule apples */ + struct hbac_rule_element *targethosts; + + /* Source hosts for which this rule applies */ + struct hbac_rule_element *srchosts; + + /* For future use */ + struct hbac_time_rules *timerules; +}; + +struct hbac_request_element { + const char *name; + const char **groups; +}; + +struct hbac_eval_req { + /* This is a list of service DNs to check, + * it must consist of the actual service + * requested, as well as all parent groups + * containing that service. + */ + struct hbac_request_element *service; + + /* This is a list of user DNs to check, + * it must consist of the actual user + * requested, as well as all parent groups + * containing that user. + */ + struct hbac_request_element *user; + + /* This is a list of target hosts to check, + * it must consist of the actual target host + * requested, as well as all parent groups + * containing that target host. + */ + struct hbac_request_element *targethost; + + /* This is a list of source hosts to check, + * it must consist of the actual source host + * requested, as well as all parent groups + * containing that source host. + */ + struct hbac_request_element *srchost; + + /* For future use */ + time_t request_time; +}; + +enum hbac_error_code { + HBAC_ERROR_UNKNOWN = -1, + HBAC_SUCCESS, + HBAC_ERROR_NOT_IMPLEMENTED, + HBAC_ERROR_OUT_OF_MEMORY, + HBAC_ERROR_UNPARSEABLE_RULE +}; + +/* Extended information */ +struct hbac_info { + /* If the hbac_eval_result was HBAC_EVAL_ERROR, + * this will be an error code. + * Otherwise it will be HBAC_SUCCESS + */ + enum hbac_error_code code; + + /* Specify the name of the rule that matched or + * threw an error + */ + char *rule_name; +}; + + +/** + * @brief Evaluate an authorization request against a set of HBAC rules + * + * @param[in] rules A NULL-terminated list of rules to evaluate against + * @param[in] hbac_req A user authorization request + * @param[out] info Extended information (including the name of the + * rule that allowed access (or caused a parse error) + * @return + */ +enum hbac_eval_result hbac_evaluate(struct hbac_rule **rules, + struct hbac_eval_req *hbac_req, + struct hbac_info **info); + +const char *hbac_result_string(enum hbac_eval_result result); +const char *hbac_error_string(enum hbac_error_code code); + +void hbac_free_info(struct hbac_info *info); + + +#define HBAC_RULE_ELEMENT_USERS 0x01 +#define HBAC_RULE_ELEMENT_SERVICES 0x02 +#define HBAC_RULE_ELEMENT_TARGETHOSTS 0x04 +#define HBAC_RULE_ELEMENT_SOURCEHOSTS 0x08 + +/** + * @brief Evaluate whether an HBAC rule contains all necessary elements + * + * @param[in] rule An HBAC rule to evaluate + * @param[out] missing_attrs A list of attributes missing from the rule + * This is a bitmask that may contain one or more + * of HBAC_RULE_ELEMENT_USERS, + * HBAC_RULE_ELEMENT_SERVICES, + * HBAC_RULE_ELEMENT_TARGETHOSTS and + * HBAC_RULE_ELEMENT_SOURCEHOSTS + * + * @return True if the rule contains all mandatory attributes + * + * @note This function does not care if the rule is enabled or disabled + */ +bool hbac_rule_is_complete(struct hbac_rule *rule, uint32_t *missing_attrs); + +#endif /* IPA_HBAC_H_ */ diff --git a/src/providers/ipa/ipa_hbac.pc.in b/src/providers/ipa/ipa_hbac.pc.in new file mode 100644 index 000000000..9d799e850 --- /dev/null +++ b/src/providers/ipa/ipa_hbac.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: ipa_hbac +Description: FreeIPA HBAC Evaluator library +Version: @VERSION@ +Libs: -L$(libdir) -lipa_hbac +Cflags: +URL: http://fedorahosted.org/sssd/ diff --git a/src/providers/ipa/ipa_hbac_common.c b/src/providers/ipa/ipa_hbac_common.c new file mode 100644 index 000000000..4633f2f9e --- /dev/null +++ b/src/providers/ipa/ipa_hbac_common.c @@ -0,0 +1,883 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + 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 . +*/ + +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ipa/ipa_hbac.h" +#include "providers/ipa/ipa_common.h" + +errno_t +ipa_hbac_save_list(struct sysdb_ctx *sysdb, bool delete_subdir, + const char *subdir, struct sss_domain_info *domain, + 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(1, ("talloc_new failed.\n")); + return ENOMEM; + } + + if (delete_subdir) { + base_dn = sysdb_custom_subtree_dn(sysdb, tmp_ctx, domain->name, subdir); + if (base_dn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_delete_recursive(tmp_ctx, sysdb, base_dn, true); + if (ret != EOK) { + DEBUG(1, ("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(1, ("sysdb_attrs_get_el failed.\n")); + goto done; + } + if (el->num_values == 0) { + DEBUG(1, ("[%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(1, ("talloc_strndup failed.\n")); + ret = ENOMEM; + goto done; + } + DEBUG(9, ("Object name: [%s].\n", object_name)); + + ret = sysdb_store_custom(tmp_ctx, sysdb, domain, object_name, subdir, + list[c]); + if (ret != EOK) { + DEBUG(1, ("sysdb_store_custom failed.\n")); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ipa_hbac_sysdb_save(struct sysdb_ctx *sysdb, 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) +{ + int lret; + errno_t ret, sret; + bool in_transaction = false; + const char **orig_member_dns; + size_t i, j, member_count; + struct ldb_message **members; + TALLOC_CTX *tmp_ctx = NULL; + const char *member_dn; + const char *group_id; + struct ldb_message *msg; + char *member_filter; + + 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(sysdb); + if (ret != EOK) return ret; + in_transaction = true; + + /* First, save the specific entries */ + ret = ipa_hbac_save_list(sysdb, true, + primary_subdir, + domain, + attr_name, + primary_count, + primary); + if (ret != EOK) { + DEBUG(1, ("Could not save %s. [%d][%s]\n", + primary_subdir, ret, strerror(ret))); + goto done; + } + + /* Second, save the groups */ + if (group_count > 0) { + ret = ipa_hbac_save_list(sysdb, true, + group_subdir, + domain, + groupattr_name, + group_count, + groups); + if (ret != EOK) { + DEBUG(1, ("Could not save %s. [%d][%s]\n", + group_subdir, ret, strerror(ret))); + goto done; + } + + /* Third, save the memberships */ + for (i = 0; i < group_count; i++) { + if (!groups[i]) { + ret = EINVAL; + goto done; + } + + talloc_free(tmp_ctx); + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_get_string(groups[i], + groupattr_name, + &group_id); + if (ret != EOK) { + DEBUG(1, ("Could not determine group attribute name\n")); + goto done; + } + + msg = ldb_msg_new(tmp_ctx); + if (msg == NULL) { + ret = ENOMEM; + goto done; + } + + msg->dn = sysdb_custom_dn(sysdb, msg, domain->name, + group_id, group_subdir); + if (msg->dn == NULL) { + ret = ENOMEM; + goto done; + } + + lret = ldb_msg_add_empty(msg, SYSDB_MEMBER, LDB_FLAG_MOD_ADD, NULL); + if (lret != LDB_SUCCESS) { + ret = sysdb_error_to_errno(lret); + goto done; + } + + ret = sysdb_attrs_get_string_array(groups[i], + SYSDB_ORIG_MEMBER, + tmp_ctx, + &orig_member_dns); + if (ret != EOK) { + DEBUG(1, ("Could not determine original members\n")); + goto done; + } + + for (j = 0; orig_member_dns[j]; j++) { + member_filter = talloc_asprintf(tmp_ctx, "%s=%s", + SYSDB_ORIG_DN, + orig_member_dns[j]); + if (member_filter == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_custom(tmp_ctx, sysdb, domain, + member_filter, primary_subdir, + NULL, &member_count, &members); + talloc_zfree(member_filter); + if (ret != EOK && ret != ENOENT) { + goto done; + } else if (ret == ENOENT || member_count == 0) { + /* No member exists with this orig_dn. Skip it */ + DEBUG(6, ("[%s] does not exist\n", orig_member_dns[j])); + continue; + } else if (member_count > 1) { + /* This probably means corruption in the cache, but + * we'll try to proceed anyway. + */ + DEBUG(1, ("More than one result for DN [%s], skipping\n")); + continue; + } + + member_dn = ldb_dn_get_linearized(members[0]->dn); + if (!member_dn) { + ret = ENOMEM; + goto done; + } + lret = ldb_msg_add_fmt(msg, SYSDB_MEMBER, "%s", member_dn); + if (lret != LDB_SUCCESS) { + ret = sysdb_error_to_errno(lret); + goto done; + } + } + + lret = ldb_modify(sysdb_ctx_get_ldb(sysdb), msg); + if (lret != LDB_SUCCESS) { + ret = sysdb_error_to_errno(lret); + goto done; + } + } + talloc_zfree(tmp_ctx); + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) goto done; + in_transaction = false; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(0, ("Could not cancel sysdb transaction\n")); + } + } + + if (ret != EOK) { + DEBUG(3, ("Error [%d][%s]\n", ret, strerror(ret))); + } + return ret; +} + +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(1, ("sysdb_attrs_replace_name failed.\n")); + return ret; + } + } + + 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; + 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(1, ("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(1, ("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(4, ("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(7, ("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(7, ("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_sysdb(hbac_ctx), + hbac_ctx_be(hbac_ctx)->domain, + new_rule->name, + hbac_ctx->rules[idx], + &new_rule->users); + if (ret != EOK) { + DEBUG(1, ("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_sysdb(hbac_ctx), + hbac_ctx_be(hbac_ctx)->domain, + new_rule->name, + hbac_ctx->rules[idx], + &new_rule->services); + if (ret != EOK) { + DEBUG(1, ("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_sysdb(hbac_ctx), + hbac_ctx_be(hbac_ctx)->domain, + new_rule->name, + hbac_ctx->rules[idx], + &new_rule->targethosts); + if (ret != EOK) { + DEBUG(1, ("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_sysdb(hbac_ctx), + hbac_ctx_be(hbac_ctx)->domain, + new_rule->name, + hbac_ctx->rules[idx], + &new_rule->srchosts); + if (ret != EOK) { + DEBUG(1, ("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(5, ("Category is set to 'all'.\n")); + cats |= HBAC_CATEGORY_ALL; + continue; + } + DEBUG(9, ("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 sysdb_ctx *sysdb, + 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 sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *hostname, + struct hbac_request_element **svc_element); + +static errno_t +hbac_eval_host_element(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + 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 sysdb_ctx *sysdb = hbac_ctx_sysdb(hbac_ctx); + struct sss_domain_info *domain = hbac_ctx_be(hbac_ctx)->domain; + const char *rhost; + const char *thost; + + 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 */ + ret = hbac_eval_user_element(eval_req, sysdb, 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, sysdb, 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, sysdb, 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(1, ("Missing ipa_hostname, this should never happen.\n")); + ret = EINVAL; + goto done; + } + + ret = hbac_eval_host_element(eval_req, sysdb, 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 sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *username, + struct hbac_request_element **user_element) +{ + errno_t ret; + unsigned int i; + unsigned int num_groups = 0; + TALLOC_CTX *tmp_ctx; + const char *member_dn; + struct hbac_request_element *users; + struct ldb_message *msg; + struct ldb_message_element *el; + const char *attrs[] = { SYSDB_ORIG_MEMBEROF, NULL }; + + 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; + } + + users->name = username; + + /* Read the originalMemberOf attribute + * This will give us the list of both POSIX and + * non-POSIX groups that this user belongs to. + */ + ret = sysdb_search_user_by_name(tmp_ctx, sysdb, domain, + users->name, attrs, &msg); + if (ret != EOK) { + DEBUG(1, ("Could not determine user memberships for [%s]\n", + users->name)); + goto done; + } + + el = ldb_msg_find_element(msg, SYSDB_ORIG_MEMBEROF); + if (el == NULL || el->num_values == 0) { + DEBUG(7, ("No groups for [%s]\n", users->name)); + users->groups = talloc_array(users, const char *, 1); + if (users->groups == NULL) { + ret = ENOMEM; + goto done; + } + users->groups[0] = NULL; + goto done; + } + DEBUG(7, ("[%d] groups for [%s]\n", el->num_values, users->name)); + + users->groups = talloc_array(users, const char *, el->num_values + 1); + if (users->groups == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < el->num_values; i++) { + member_dn = (const char *)el->values[i].data; + + ret = get_ipa_groupname(users->groups, sysdb, member_dn, + &users->groups[num_groups]); + if (ret != EOK && ret != ENOENT) { + DEBUG(3, ("Parse error on [%s]\n", member_dn)); + goto done; + } else if (ret == EOK) { + DEBUG(7, ("Added group [%s] for user [%s]\n", + users->groups[num_groups], users->name)); + num_groups++; + continue; + } + /* Skip entries that are not groups */ + DEBUG(8, ("Skipping non-group memberOf [%s]\n", member_dn)); + } + users->groups[num_groups] = NULL; + + if (num_groups < el->num_values) { + /* 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 sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *hostname, + struct hbac_request_element **svc_element) +{ + errno_t ret; + size_t i, count; + TALLOC_CTX *tmp_ctx; + struct hbac_request_element *svc; + struct ldb_message **msgs; + const char *group_name; + struct ldb_dn *svc_dn; + const char *attrs[] = { IPA_CN, NULL }; + const char *service_filter; + + 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 = hostname; + + service_filter = talloc_asprintf(tmp_ctx, + "(objectClass=%s)", + IPA_HBAC_SERVICE_GROUP); + if (service_filter == NULL) { + ret = ENOMEM; + goto done; + } + + svc_dn = sysdb_custom_dn(sysdb, tmp_ctx, domain->name, + svc->name, HBAC_SERVICES_SUBDIR); + if (svc_dn == NULL) { + ret = ENOMEM; + goto done; + } + + /* Find the service groups */ + ret = sysdb_asq_search(tmp_ctx, sysdb, domain, svc_dn, + service_filter, SYSDB_MEMBEROF, + attrs, &count, &msgs); + if (ret != EOK && ret != ENOENT) { + DEBUG(1, ("Could not look up servicegroups\n")); + goto done; + } else if (ret == ENOENT) { + count = 0; + } + + svc->groups = talloc_array(svc, const char *, count + 1); + if (svc->groups == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < count; i++) { + group_name = ldb_msg_find_attr_as_string(msgs[i], IPA_CN, NULL); + if (group_name == NULL) { + DEBUG(1, ("Group with no name?\n")); + ret = EINVAL; + goto done; + } + svc->groups[i] = talloc_strdup(svc->groups, + group_name); + if (svc->groups[i] == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(6, ("Added service group [%s] to the eval request\n", + svc->groups[i])); + } + svc->groups[i] = NULL; + + *svc_element = talloc_steal(mem_ctx, svc); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +hbac_eval_host_element(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *hostname, + struct hbac_request_element **host_element) +{ + errno_t ret; + size_t i, count; + TALLOC_CTX *tmp_ctx; + struct hbac_request_element *host; + struct ldb_message **msgs; + const char *group_name; + struct ldb_dn *host_dn; + const char *attrs[] = { IPA_HOST_FQDN, NULL }; + const char *host_filter; + + 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. + */ + host->groups = talloc_array(host, const char *, 1); + if (host->groups == NULL) { + ret = ENOMEM; + goto done; + } + host->groups[0] = NULL; + ret = EOK; + goto done; + } + + host_filter = talloc_asprintf(tmp_ctx, + "(objectClass=%s)", + IPA_HOSTGROUP); + if (host_filter == NULL) { + ret = ENOMEM; + goto done; + } + + host_dn = sysdb_custom_dn(sysdb, tmp_ctx, domain->name, + host->name, HBAC_SERVICES_SUBDIR); + if (host_dn == NULL) { + ret = ENOMEM; + goto done; + } + + /* Find the host groups */ + ret = sysdb_asq_search(tmp_ctx, sysdb, domain, host_dn, + host_filter, SYSDB_MEMBEROF, + attrs, &count, &msgs); + if (ret != EOK && ret != ENOENT) { + DEBUG(1, ("Could not look up host groups\n")); + goto done; + } else if (ret == ENOENT) { + count = 0; + } + + host->groups = talloc_array(host, const char *, count + 1); + if (host->groups == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < count; i++) { + group_name = ldb_msg_find_attr_as_string(msgs[i], + IPA_HOST_FQDN, + NULL); + if (group_name == NULL) { + DEBUG(1, ("Group with no name?\n")); + ret = EINVAL; + goto done; + } + host->groups[i] = talloc_strdup(host->groups, + group_name); + if (host->groups[i] == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(6, ("Added host group [%s] to the eval request\n", + host->groups[i])); + } + host->groups[i] = NULL; + + ret = EOK; + +done: + if (ret == EOK) { + *host_element = talloc_steal(mem_ctx, host); + } + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/ipa/ipa_hbac_hosts.c b/src/providers/ipa/ipa_hbac_hosts.c new file mode 100644 index 000000000..4e753f374 --- /dev/null +++ b/src/providers/ipa/ipa_hbac_hosts.c @@ -0,0 +1,524 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + 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 . +*/ + +#include "util/util.h" +#include "db/sysdb.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ldap/sdap_async.h" + +struct ipa_hbac_host_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sss_domain_info *dom; + struct sdap_handle *sh; + struct sdap_options *opts; + const char *search_base; + const char **attrs; + + /* Return values */ + size_t host_count; + struct sysdb_attrs **hosts; + + size_t hostgroup_count; + struct sysdb_attrs **hostgroups; +}; + +static void +ipa_hbac_host_info_done(struct tevent_req *subreq); + +static void +ipa_hbac_hostgroup_info_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_hbac_host_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *search_base) +{ + errno_t ret; + struct ipa_hbac_host_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + char *host_filter; + + req = tevent_req_create(mem_ctx, &state, struct ipa_hbac_host_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->sysdb = sysdb; + state->dom = dom; + state->sh = sh; + state->opts = opts; + state->search_base = search_base; + + host_filter = talloc_asprintf(state, "(objectClass=%s)", IPA_HOST); + if (host_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + + state->attrs = talloc_array(state, const char *, 8); + if (state->attrs == NULL) { + DEBUG(1, ("Failed to allocate host attribute list.\n")); + ret = ENOMEM; + goto immediate; + } + state->attrs[0] = "objectClass"; + state->attrs[1] = IPA_HOST_SERVERHOSTNAME; + state->attrs[2] = IPA_HOST_FQDN; + state->attrs[3] = IPA_UNIQUE_ID; + state->attrs[4] = IPA_MEMBER; + state->attrs[5] = IPA_MEMBEROF; + state->attrs[6] = IPA_CN; + state->attrs[7] = NULL; + + subreq = sdap_get_generic_send(state, ev, opts, sh, search_base, + LDAP_SCOPE_SUB, host_filter, + state->attrs, NULL, 0, + dp_opt_get_int(opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT)); + if (subreq == NULL) { + DEBUG(1, ("Error requesting host info\n")); + ret = EIO; + goto immediate; + } + tevent_req_set_callback(subreq, ipa_hbac_host_info_done, req); + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static void +ipa_hbac_host_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_hbac_host_state *state = + tevent_req_data(req, struct ipa_hbac_host_state); + char *hostgroup_filter; + + ret = sdap_get_generic_recv(subreq, state, + &state->host_count, + &state->hosts); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (state->host_count == 0) { + tevent_req_error(req, ENOENT); + return; + } + + ret = replace_attribute_name(IPA_MEMBEROF, SYSDB_ORIG_MEMBEROF, + state->host_count, + state->hosts); + if (ret != EOK) { + DEBUG(1, ("Could not replace attribute names\n")); + tevent_req_error(req, ret); + return; + } + + hostgroup_filter = talloc_asprintf(state, "(objectClass=%s)", + IPA_HOSTGROUP); + if (hostgroup_filter == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + /* Look up host groups */ + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + state->search_base, LDAP_SCOPE_SUB, + hostgroup_filter, state->attrs, NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT)); + if (subreq == NULL) { + DEBUG(1, ("Error requesting host info\n")); + tevent_req_error(req, EIO); + return; + } + tevent_req_set_callback(subreq, ipa_hbac_hostgroup_info_done, req); +} + +static void +ipa_hbac_hostgroup_info_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct ipa_hbac_host_state *state = + tevent_req_data(req, struct ipa_hbac_host_state); + + ret = sdap_get_generic_recv(subreq, state, + &state->hostgroup_count, + &state->hostgroups); + talloc_zfree(subreq); + if (ret != EOK) goto done; + + ret = replace_attribute_name(IPA_MEMBER, SYSDB_ORIG_MEMBER, + state->hostgroup_count, + state->hostgroups); + if (ret != EOK) { + DEBUG(1, ("Could not replace attribute names\n")); + goto done; + } + + ret = replace_attribute_name(IPA_MEMBEROF, SYSDB_ORIG_MEMBEROF, + state->hostgroup_count, + state->hostgroups); + if (ret != EOK) { + DEBUG(1, ("Could not replace attribute names\n")); + goto done; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + DEBUG(3, ("Error [%d][%s]\n", ret, strerror(ret))); + tevent_req_error(req, ret); + } +} + +errno_t +ipa_hbac_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) +{ + size_t c; + struct ipa_hbac_host_state *state = + tevent_req_data(req, struct ipa_hbac_host_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *host_count = state->host_count; + *hosts = talloc_steal(mem_ctx, state->hosts); + for (c = 0; c < state->host_count; c++) { + /* Guarantee the memory heirarchy of the list */ + talloc_steal(state->hosts, state->hosts[c]); + } + + *hostgroup_count = state->hostgroup_count; + *hostgroups = talloc_steal(mem_ctx, state->hostgroups); + + return EOK; +} + +/* + * Functions to convert sysdb_attrs to the hbac_rule format + */ +static errno_t hbac_host_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + 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[] = { IPA_HOST_FQDN, 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(1, ("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(1, ("sysdb_attrs_get_el failed.\n")); + goto done; + } + if (ret == ENOENT || el->num_values == 0) { + el->num_values = 0; + DEBUG(4, ("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, sysdb, 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(1, ("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], + IPA_HOST_FQDN, + NULL); + if (name == NULL) { + DEBUG(1, ("Attribute 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(8, ("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, sysdb, 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(1, ("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(1, ("Attribute 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(8, ("Added hostgroup [%s] to rule [%s]\n", + name, rule_name)); + num_hostgroups++; + } else { /* ret == ENOENT */ + /* Neither a host nor a hostgroup? Skip it */ + DEBUG(1, ("[%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 sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **thosts) +{ + DEBUG(7, ("Processing target hosts for rule [%s]\n", rule_name)); + + return hbac_host_attrs_to_rule(mem_ctx, sysdb, 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 sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **source_hosts) +{ + errno_t ret; + size_t host_count; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + size_t idx; + struct ldb_message_element *el; + struct hbac_rule_element *shosts; + + DEBUG(7, ("Processing source hosts for rule [%s]\n", rule_name)); + + ret = hbac_host_attrs_to_rule(tmp_ctx, sysdb, 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].data); + if (shosts->names[idx] == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(8, ("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 000000000..7289a0422 --- /dev/null +++ b/src/providers/ipa/ipa_hbac_private.h @@ -0,0 +1,194 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + 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 . +*/ + +#ifndef IPA_HBAC_PRIVATE_H_ +#define IPA_HBAC_PRIVATE_H_ + +#include "providers/ipa/ipa_access.h" +#include "providers/ipa/ipa_hbac.h" + +#define IPA_HBAC_RULE "ipaHBACRule" + +#define IPA_HOST "ipaHost" +#define IPA_HOSTGROUP "ipaHostGroup" + +#define IPA_HBAC_SERVICE "ipaHBACService" +#define IPA_HBAC_SERVICE_GROUP "ipaHBACServiceGroup" + +#define IPA_HOST_SERVERHOSTNAME "serverHostName" +#define IPA_HOST_FQDN "fqdn" +#define IPA_UNIQUE_ID "ipauniqueid" + +#define IPA_MEMBER "member" +#define SYSDB_ORIG_MEMBER "orig_member" +#define HBAC_HOSTS_SUBDIR "hbac_hosts" +#define HBAC_HOSTGROUPS_SUBDIR "hbac_hostgroups" + +#define OBJECTCLASS "objectclass" +#define IPA_MEMBEROF "memberOf" +#define IPA_ACCESS_RULE_TYPE "accessRuleType" +#define IPA_HBAC_ALLOW "allow" +#define IPA_MEMBER_USER "memberUser" +#define IPA_USER_CATEGORY "userCategory" +#define IPA_SERVICE_NAME "serviceName" +#define IPA_SOURCE_HOST "sourceHost" +#define IPA_SOURCE_HOST_CATEGORY "sourceHostCategory" +#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_MEMBER_SERVICE "memberService" +#define IPA_SERVICE_CATEGORY "serviceCategory" +#define IPA_TRUE_VALUE "TRUE" + +#define IPA_HOST_BASE_TMPL "cn=computers,cn=accounts,%s" +#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 ipa_hbac_save_list(struct sysdb_ctx *sysdb, bool delete_subdir, + const char *subdir, struct sss_domain_info *domain, + const char *naming_attribute, size_t count, + struct sysdb_attrs **list); +errno_t +ipa_hbac_sysdb_save(struct sysdb_ctx *sysdb, 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 +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); + +/* From ipa_hbac_hosts.c */ +struct tevent_req * +ipa_hbac_host_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *search_base); + +errno_t +ipa_hbac_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); + +errno_t +hbac_thost_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + 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 sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **source_hosts); + +/* From ipa_hbac_services.c */ +struct tevent_req * +ipa_hbac_service_info_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *search_base); + +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 sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *rule_name, + struct sysdb_attrs *rule_attrs, + struct hbac_rule_element **services); + +/* From ipa_hbac_rules.c */ +struct tevent_req * +ipa_hbac_rule_info_send(TALLOC_CTX *mem_ctx, + bool get_deny_rules, + struct tevent_context *ev, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *search_base, + 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); + +/* From ipa_hbac_users.c */ +errno_t +hbac_user_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + 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 000000000..43e1e4263 --- /dev/null +++ b/src/providers/ipa/ipa_hbac_rules.c @@ -0,0 +1,231 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + 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 . +*/ + +#include "util/util.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ldap/sdap_async.h" + +struct ipa_hbac_rule_state { + struct sdap_options *opts; + + size_t rule_count; + struct sysdb_attrs **rules; +}; + +static void +ipa_hbac_rule_info_done(struct tevent_req *subreq); + +struct tevent_req * +ipa_hbac_rule_info_send(TALLOC_CTX *mem_ctx, + bool get_deny_rules, + struct tevent_context *ev, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *search_base, + struct sysdb_attrs *ipa_host) +{ + errno_t ret; + size_t i; + struct tevent_req *req = NULL; + struct tevent_req *subreq; + struct ipa_hbac_rule_state *state; + TALLOC_CTX *tmp_ctx; + const char *host_dn; + char *host_dn_clean; + char *host_group_clean; + char *rule_filter; + const char **memberof_list; + const char *rule_attrs[] = { OBJECTCLASS, + IPA_CN, + IPA_UNIQUE_ID, + IPA_ENABLED_FLAG, + IPA_ACCESS_RULE_TYPE, + IPA_MEMBER_USER, + IPA_USER_CATEGORY, + IPA_MEMBER_SERVICE, + IPA_SERVICE_CATEGORY, + IPA_SOURCE_HOST, + IPA_SOURCE_HOST_CATEGORY, + IPA_EXTERNAL_HOST, + IPA_MEMBER_HOST, + IPA_HOST_CATEGORY, + NULL }; + + if (ipa_host == NULL) { + DEBUG(1, ("Missing host\n")); + return NULL; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) return NULL; + + ret = sysdb_attrs_get_string(ipa_host, SYSDB_ORIG_DN, &host_dn); + if (ret != EOK) { + DEBUG(1, ("Could not identify IPA hostname\n")); + goto error; + } + + ret = sss_filter_sanitize(tmp_ctx, host_dn, &host_dn_clean); + if (ret != EOK) goto error; + + req = tevent_req_create(mem_ctx, &state, struct ipa_hbac_rule_state); + if (req == NULL) { + DEBUG(1, ("tevent_req_create failed.\n")); + return NULL; + } + + state->opts = opts; + + if (get_deny_rules) { + rule_filter = talloc_asprintf(tmp_ctx, + "(&(objectclass=%s)" + "(%s=%s)(|(%s=%s)(%s=%s)", + IPA_HBAC_RULE, + IPA_ENABLED_FLAG, IPA_TRUE_VALUE, + IPA_HOST_CATEGORY, "all", + IPA_MEMBER_HOST, host_dn_clean); + } else { + rule_filter = talloc_asprintf(tmp_ctx, + "(&(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, + tmp_ctx, &memberof_list); + if (ret != EOK && ret != ENOENT) { + DEBUG(1, ("Could not identify ")) + } if (ret == ENOENT) { + /* This host is not a member of any hostgroups */ + memberof_list = talloc_array(tmp_ctx, 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(tmp_ctx, + 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; + } + talloc_steal(state, rule_filter); + + subreq = sdap_get_generic_send(state, ev, opts, sh, search_base, + LDAP_SCOPE_SUB, rule_filter, rule_attrs, + NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT)); + if (subreq == NULL) { + DEBUG(1, ("sdap_get_generic_send failed.\n")); + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, ipa_hbac_rule_info_done, req); + + talloc_free(tmp_ctx); + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + talloc_free(tmp_ctx); + return req; + +error: + talloc_free(tmp_ctx); + return NULL; +} + +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); + + ret = sdap_get_generic_recv(subreq, state, + &state->rule_count, + &state->rules); + if (ret != EOK) { + DEBUG(3, ("Could not retrieve HBAC rules\n")); + tevent_req_error(req, ret); + return; + } else if (state->rule_count == 0) { + DEBUG(3, ("No rules apply to this host\n")); + tevent_req_error(req, ENOENT); + return; + } + + tevent_req_done(req); +} + +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_services.c b/src/providers/ipa/ipa_hbac_services.c new file mode 100644 index 000000000..df276b86c --- /dev/null +++ b/src/providers/ipa/ipa_hbac_services.c @@ -0,0 +1,451 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + 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 . +*/ + +#include "util/util.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ldap/sdap_async.h" + +struct ipa_hbac_service_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sss_domain_info *dom; + struct sdap_handle *sh; + struct sdap_options *opts; + const char *search_base; + const char **attrs; + + /* Return values */ + size_t service_count; + struct sysdb_attrs **services; + + size_t servicegroup_count; + struct sysdb_attrs **servicegroups; +}; + +static void +ipa_hbac_service_info_done(struct tevent_req *subreq); +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 sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct sdap_handle *sh, + struct sdap_options *opts, + const char *search_base) +{ + errno_t ret; + struct ipa_hbac_service_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + char *service_filter; + + req = tevent_req_create(mem_ctx, &state, struct ipa_hbac_service_state); + if (req == NULL) { + DEBUG(1, ("tevent_req_create failed.\n")); + return NULL; + } + + state->ev = ev; + state->sysdb = sysdb; + state->dom = dom; + state->sh = sh; + state->opts = opts; + state->search_base = search_base; + + service_filter = talloc_asprintf(state, "(objectClass=%s)", + IPA_HBAC_SERVICE); + if (service_filter == NULL) { + ret = ENOMEM; + goto immediate; + } + + state->attrs = talloc_array(state, const char *, 6); + if (state->attrs == NULL) { + DEBUG(1, ("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; + + subreq = sdap_get_generic_send(state, ev, opts, sh, search_base, + LDAP_SCOPE_SUB, service_filter, + state->attrs, NULL, 0, + dp_opt_get_int(opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT)); + if (subreq == NULL) { + DEBUG(1, ("Error requesting service info\n")); + ret = EIO; + goto immediate; + } + tevent_req_set_callback(subreq, ipa_hbac_service_info_done, req); + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +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->service_count = 0; + state->services = NULL; + ret = EOK; + goto done; + } + + ret = replace_attribute_name(IPA_MEMBEROF, SYSDB_ORIG_MEMBEROF, + state->service_count, + state->services); + if (ret != EOK) { + DEBUG(1, ("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; + } + + /* Look up service groups */ + subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, + state->search_base, LDAP_SCOPE_SUB, + servicegroup_filter, state->attrs, NULL, 0, + dp_opt_get_int(state->opts->basic, + SDAP_ENUM_SEARCH_TIMEOUT)); + if (subreq == NULL) { + DEBUG(1, ("Error requesting host info\n")); + ret = EIO; + goto done; + } + tevent_req_set_callback(subreq, ipa_hbac_servicegroup_info_done, req); + + return; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } +} + +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); + + ret = sdap_get_generic_recv(subreq, state, + &state->servicegroup_count, + &state->servicegroups); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + ret = replace_attribute_name(IPA_MEMBER, SYSDB_ORIG_MEMBER, + state->servicegroup_count, + state->servicegroups); + if (ret != EOK) { + DEBUG(1, ("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(1, ("Could not replace attribute names\n")); + goto done; + } + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + DEBUG(3, ("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 sysdb_ctx *sysdb, + 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(7, ("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(1, ("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(1, ("sysdb_attrs_get_el failed.\n")); + goto done; + } + if (ret == ENOENT || el->num_values == 0) { + el->num_values = 0; + DEBUG(4, ("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, sysdb, 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(1, ("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(1, ("Attribute 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(8, ("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, sysdb, 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(1, ("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(1, ("Attribute 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(8, ("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(1, ("[%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; +} diff --git a/src/providers/ipa/ipa_hbac_users.c b/src/providers/ipa/ipa_hbac_users.c new file mode 100644 index 000000000..9b7cadb2e --- /dev/null +++ b/src/providers/ipa/ipa_hbac_users.c @@ -0,0 +1,345 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + 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 . +*/ + +#include "util/util.h" +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ldap/sdap_async.h" + +struct hbac_update_groups_state { + struct hbac_ctx *hbac_ctx; + struct sysdb_ctx *sysdb; + struct sss_domain_info *domain; +}; + + +/* Returns EOK and populates groupname if + * the group_dn is actually a group. + * Returns ENOENT if group_dn does not point + * at a 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; + + 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)) { + ret = EINVAL; + goto done; + } + + if (ldb_dn_get_comp_num(dn) < 4) { + /* RDN, groups, accounts, and at least one DC= */ + ret = EINVAL; + 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 = EINVAL; + goto done; + } + + if (strcasecmp("cn", rdn_name) != 0) { + /* RDN has the wrong attribute name. + * It's not a group. + */ + ret = ENOENT; + 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" */ + ret = ENOENT; + 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" */ + ret = ENOENT; + 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 = ENOENT; + 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 = ENOENT; + 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; + } + + ret = EOK; + +done: + talloc_free(dn); + return ret; +} + +errno_t +hbac_user_attrs_to_rule(TALLOC_CTX *mem_ctx, + struct sysdb_ctx *sysdb, + 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; + char *filter; + char *member_dn; + const char *member_user; + const char *attrs[] = { SYSDB_NAME, NULL }; + size_t num_users = 0; + size_t num_groups = 0; + const char *name; + + 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(7, ("Processing users for rule [%s]\n", rule_name)); + + ret = hbac_get_category(rule_attrs, IPA_USER_CATEGORY, + &new_users->category); + if (ret != EOK) { + DEBUG(1, ("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(1, ("sysdb_attrs_get_el failed.\n")); + goto done; + } + if (ret == ENOENT || el->num_values == 0) { + el->num_values = 0; + DEBUG(4, ("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_user = (const char *)el->values[i].data; + ret = sss_filter_sanitize(tmp_ctx, member_user, &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 user */ + ret = sysdb_search_users(tmp_ctx, sysdb, domain, + filter, attrs, &count, &msgs); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && count == 0) { + ret = ENOENT; + } + + if (ret == EOK) { + if (count > 1) { + DEBUG(1, ("Original DN matched multiple users. Skipping \n")); + talloc_zfree(member_dn); + continue; + } + + /* Original DN matched a single user. Get the username */ + name = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(1, ("Attribute is missing!\n")); + ret = EFAULT; + goto done; + } + + new_users->names[num_users] = talloc_strdup(new_users->names, + name); + if (new_users->names[num_users] == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(8, ("Added user [%s] to rule [%s]\n", + name, rule_name)); + num_users++; + } else { + /* Check if it is a group instead */ + ret = sysdb_search_groups(tmp_ctx, sysdb, domain, + filter, attrs, &count, &msgs); + if (ret != EOK && ret != ENOENT) goto done; + if (ret == EOK && count == 0) { + ret = ENOENT; + } + + if (ret == EOK) { + if (count > 1) { + DEBUG(1, ("Original DN matched multiple 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], SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(1, ("Attribute is missing!\n")); + ret = EFAULT; + goto done; + } + + new_users->groups[num_groups] = + talloc_strdup(new_users->groups, name); + if (new_users->groups[num_groups] == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(8, ("Added POSIX group [%s] to rule [%s]\n", + 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, sysdb, member_user, + &new_users->groups[num_groups]); + if (ret == EOK) { + DEBUG(8, ("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(1, ("[%s] does not map to either a user or group. " + "Skipping\n", member_dn)); + } + } + } + talloc_zfree(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/ldap/ldap_common.c b/src/providers/ldap/ldap_common.c index d2477bcf2..31b8139ae 100644 --- a/src/providers/ldap/ldap_common.c +++ b/src/providers/ldap/ldap_common.c @@ -1045,3 +1045,32 @@ bool sdap_is_secure_uri(const char *uri) } return false; } + +errno_t msgs2attrs_array(TALLOC_CTX *mem_ctx, size_t count, + struct ldb_message **msgs, + struct sysdb_attrs ***attrs) +{ + int i; + struct sysdb_attrs **a; + + a = talloc_array(mem_ctx, struct sysdb_attrs *, count); + if (a == NULL) { + DEBUG(1, ("talloc_array failed.\n")); + return ENOMEM; + } + + for (i = 0; i < count; i++) { + a[i] = talloc(a, struct sysdb_attrs); + if (a[i] == NULL) { + DEBUG(1, ("talloc_array failed.\n")); + talloc_free(a); + return ENOMEM; + } + a[i]->num = msgs[i]->num_elements; + a[i]->a = talloc_steal(a[i], msgs[i]->elements); + } + + *attrs = a; + + return EOK; +} diff --git a/src/providers/ldap/ldap_common.h b/src/providers/ldap/ldap_common.h index 9146da5a9..70ffd1485 100644 --- a/src/providers/ldap/ldap_common.h +++ b/src/providers/ldap/ldap_common.h @@ -162,4 +162,8 @@ errno_t list_missing_attrs(TALLOC_CTX *mem_ctx, bool sdap_is_secure_uri(const char *uri); +errno_t msgs2attrs_array(TALLOC_CTX *mem_ctx, size_t count, + struct ldb_message **msgs, + struct sysdb_attrs ***attrs); + #endif /* _LDAP_COMMON_H_ */ diff --git a/src/tests/ipa_hbac-tests.c b/src/tests/ipa_hbac-tests.c new file mode 100644 index 000000000..330e49e7c --- /dev/null +++ b/src/tests/ipa_hbac-tests.c @@ -0,0 +1,850 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + 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 . +*/ +#include +#include +#include +#include +#include +#include + +#include "tests/common.h" +#include "providers/ipa/ipa_hbac.h" + +#define HBAC_TEST_USER "testuser" +#define HBAC_TEST_INVALID_USER "nosuchuser" + +#define HBAC_TEST_GROUP1 "testgroup1" +#define HBAC_TEST_GROUP2 "testgroup2" +#define HBAC_TEST_INVALID_GROUP "nosuchgroup" + +#define HBAC_TEST_SERVICE "testservice" +#define HBAC_TEST_INVALID_SERVICE "nosuchservice" + +#define HBAC_TEST_SERVICEGROUP1 "login_services" +#define HBAC_TEST_SERVICEGROUP2 "all_services" +#define HBAC_TEST_INVALID_SERVICEGROUP "nosuchservicegroup" + +#define HBAC_TEST_SRCHOST "client.example.com" +#define HBAC_TEST_INVALID_SRCHOST "nosuchsrchost" + +#define HBAC_TEST_SRCHOSTGROUP1 "site_hosts" +#define HBAC_TEST_SRCHOSTGROUP2 "corp_hosts" +#define HBAC_TEST_INVALID_SRCHOSTGROUP "nosuchsrchostgroup" + + +/* These don't make sense for a user/group/service but they do the job and + * every one is from a different codepage */ +/* Latin Extended A - "Czech" */ +const uint8_t user_utf8_lowcase[] = { 0xC4, 0x8D, 'e', 'c', 'h', 0x0 }; +const uint8_t user_utf8_upcase[] = { 0xC4, 0x8C, 'e', 'c', 'h', 0x0 }; +const uint8_t user_utf8_lowcase_neg[] = { 0xC4, 0x8E, 'e', 'c', 'h', 0x0 }; +/* Latin 1 Supplement - "Munchen" */ +const uint8_t service_utf8_lowcase[] = { 'm', 0xC3, 0xBC, 'n', 'c', 'h', 'e', 'n', 0x0 }; +const uint8_t service_utf8_upcase[] = { 'M', 0xC3, 0x9C, 'N', 'C', 'H', 'E', 'N', 0x0 }; +/* Greek - "AlphaBetaGamma" */ +const uint8_t srchost_utf8_lowcase[] = { 0xCE, 0xB1, 0xCE, 0xB2, 0xCE, 0xB3, 0x0 }; +const uint8_t srchost_utf8_upcase[] = { 0xCE, 0x91, 0xCE, 0x92, 0xCE, 0x93, 0x0 }; +/* Turkish "capital I" and "dotless i" */ +const uint8_t user_lowcase_tr[] = { 0xC4, 0xB1, 0x0 }; +const uint8_t user_upcase_tr[] = { 0x49, 0x0 }; + +static void get_allow_all_rule(TALLOC_CTX *mem_ctx, + struct hbac_rule **allow_rule) +{ + struct hbac_rule *rule; + /* Create a rule that ALLOWs all services, users and + * remote hosts. + */ + rule = talloc_zero(mem_ctx, struct hbac_rule); + fail_if (rule == NULL); + + rule->enabled = true; + + rule->services = talloc_zero(rule, struct hbac_rule_element); + fail_if (rule->services == NULL); + rule->services->category = HBAC_CATEGORY_ALL; + rule->services->names = NULL; + rule->services->groups = NULL; + + rule->users = talloc_zero(rule, struct hbac_rule_element); + fail_if (rule->users == NULL); + rule->users->category = HBAC_CATEGORY_ALL; + rule->users->names = NULL; + rule->users->groups = NULL; + + rule->targethosts = talloc_zero(rule, struct hbac_rule_element); + fail_if (rule->targethosts == NULL); + rule->targethosts->category = HBAC_CATEGORY_ALL; + rule->targethosts->names = NULL; + rule->targethosts->groups = NULL; + + rule->srchosts = talloc_zero(rule, struct hbac_rule_element); + fail_if (rule->srchosts == NULL); + rule->srchosts->category = HBAC_CATEGORY_ALL; + rule->srchosts->names = NULL; + rule->srchosts->groups = NULL; + + *allow_rule = rule; +} + +static void get_test_user(TALLOC_CTX *mem_ctx, + struct hbac_request_element **user) +{ + struct hbac_request_element *new_user; + + new_user = talloc_zero(mem_ctx, struct hbac_request_element); + fail_if (new_user == NULL); + + new_user->name = talloc_strdup(new_user, HBAC_TEST_USER); + fail_if(new_user->name == NULL); + + new_user->groups = talloc_array(new_user, const char *, 3); + fail_if(new_user->groups == NULL); + + new_user->groups[0] = talloc_strdup(new_user->groups, HBAC_TEST_GROUP1); + fail_if(new_user->groups[0] == NULL); + + new_user->groups[1] = talloc_strdup(new_user->groups, HBAC_TEST_GROUP2); + fail_if(new_user->groups[1] == NULL); + + new_user->groups[2] = NULL; + + *user = new_user; +} + +static void get_test_service(TALLOC_CTX *mem_ctx, + struct hbac_request_element **service) +{ + struct hbac_request_element *new_service; + + new_service = talloc_zero(mem_ctx, struct hbac_request_element); + fail_if (new_service == NULL); + + new_service->name = talloc_strdup(new_service, HBAC_TEST_SERVICE); + fail_if(new_service->name == NULL); + + new_service->groups = talloc_array(new_service, const char *, 3); + fail_if(new_service->groups == NULL); + + new_service->groups[0] = talloc_strdup(new_service->groups, HBAC_TEST_SERVICEGROUP1); + fail_if(new_service->groups[0] == NULL); + + new_service->groups[1] = talloc_strdup(new_service->groups, HBAC_TEST_SERVICEGROUP2); + fail_if(new_service->groups[1] == NULL); + + new_service->groups[2] = NULL; + + *service = new_service; +} + +static void get_test_srchost(TALLOC_CTX *mem_ctx, + struct hbac_request_element **srchost) +{ + struct hbac_request_element *new_srchost; + + new_srchost = talloc_zero(mem_ctx, struct hbac_request_element); + fail_if (new_srchost == NULL); + + new_srchost->name = talloc_strdup(new_srchost, "client.example.com"); + fail_if(new_srchost->name == NULL); + + new_srchost->groups = talloc_array(new_srchost, const char *, 3); + fail_if(new_srchost->groups == NULL); + + new_srchost->groups[0] = talloc_strdup(new_srchost->groups, "site_hosts"); + fail_if(new_srchost->groups[0] == NULL); + + new_srchost->groups[1] = talloc_strdup(new_srchost->groups, "corp_hosts"); + fail_if(new_srchost->groups[1] == NULL); + + new_srchost->groups[2] = NULL; + + *srchost = new_srchost; +} + +START_TEST(ipa_hbac_test_allow_all) +{ + enum hbac_eval_result result; + TALLOC_CTX *test_ctx; + struct hbac_rule **rules; + struct hbac_eval_req *eval_req; + struct hbac_info *info; + bool is_valid; + uint32_t missing_attrs; + + test_ctx = talloc_new(global_talloc_context); + + /* Create a request */ + eval_req = talloc_zero(test_ctx, struct hbac_eval_req); + fail_if (eval_req == NULL); + + get_test_user(eval_req, &eval_req->user); + get_test_service(eval_req, &eval_req->service); + get_test_srchost(eval_req, &eval_req->srchost); + + /* Create the rules to evaluate against */ + rules = talloc_array(test_ctx, struct hbac_rule *, 2); + fail_if (rules == NULL); + + get_allow_all_rule(rules, &rules[0]); + rules[0]->name = talloc_strdup(rules[0], "Allow All"); + fail_if(rules[0]->name == NULL); + rules[1] = NULL; + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rules[0], &missing_attrs); + fail_unless(is_valid); + fail_unless(missing_attrs == 0); + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_ALLOW, + "Expected [%s], got [%s]; " + "Error: [%s]", + hbac_result_string(HBAC_EVAL_ALLOW), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + talloc_free(test_ctx); +} +END_TEST + +START_TEST(ipa_hbac_test_allow_user) +{ + enum hbac_eval_result result; + TALLOC_CTX *test_ctx; + struct hbac_rule **rules; + struct hbac_eval_req *eval_req; + struct hbac_info *info; + bool is_valid; + uint32_t missing_attrs; + + test_ctx = talloc_new(global_talloc_context); + + /* Create a request */ + eval_req = talloc_zero(test_ctx, struct hbac_eval_req); + fail_if (eval_req == NULL); + + get_test_user(eval_req, &eval_req->user); + get_test_service(eval_req, &eval_req->service); + get_test_srchost(eval_req, &eval_req->srchost); + + /* Create the rules to evaluate against */ + rules = talloc_array(test_ctx, struct hbac_rule *, 2); + fail_if (rules == NULL); + + get_allow_all_rule(rules, &rules[0]); + + /* Modify the rule to allow only a specific user */ + rules[0]->name = talloc_strdup(rules[0], "Allow user"); + fail_if(rules[0]->name == NULL); + rules[0]->users->category = HBAC_CATEGORY_NULL; + + rules[0]->users->names = talloc_array(rules[0], const char *, 2); + fail_if(rules[0]->users->names == NULL); + + rules[0]->users->names[0] = HBAC_TEST_USER; + rules[0]->users->names[1] = NULL; + + rules[1] = NULL; + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rules[0], &missing_attrs); + fail_unless(is_valid); + fail_unless(missing_attrs == 0); + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_ALLOW, + "Expected [%s], got [%s]; " + "Error: [%s]", + hbac_result_string(HBAC_EVAL_ALLOW), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + /* Negative test */ + rules[0]->users->names[0] = HBAC_TEST_INVALID_USER; + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rules[0], &missing_attrs); + fail_unless(is_valid); + fail_unless(missing_attrs == 0); + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_DENY, + "Expected [%s], got [%s]; " + "Error: [%s]", + hbac_result_string(HBAC_EVAL_DENY), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + talloc_free(test_ctx); +} +END_TEST + +START_TEST(ipa_hbac_test_allow_utf8) +{ + enum hbac_eval_result result; + TALLOC_CTX *test_ctx; + struct hbac_rule **rules; + struct hbac_eval_req *eval_req; + struct hbac_info *info; + bool is_valid; + uint32_t missing_attrs; + + test_ctx = talloc_new(global_talloc_context); + + /* Create a request */ + eval_req = talloc_zero(test_ctx, struct hbac_eval_req); + fail_if (eval_req == NULL); + + get_test_user(eval_req, &eval_req->user); + get_test_service(eval_req, &eval_req->service); + get_test_srchost(eval_req, &eval_req->srchost); + + /* Override the with UTF8 values */ + eval_req->user->name = (const char *) &user_utf8_lowcase; + eval_req->srchost->name = (const char *) &srchost_utf8_lowcase; + eval_req->service->name = (const char *) &service_utf8_lowcase; + + /* Create the rules to evaluate against */ + rules = talloc_array(test_ctx, struct hbac_rule *, 2); + fail_if (rules == NULL); + + get_allow_all_rule(rules, &rules[0]); + + rules[0]->name = talloc_strdup(rules[0], "Allow user"); + fail_if(rules[0]->name == NULL); + rules[0]->users->category = HBAC_CATEGORY_NULL; + + /* Modify the rule to allow only a specific user */ + rules[0]->users->names = talloc_array(rules[0], const char *, 2); + fail_if(rules[0]->users->names == NULL); + + rules[0]->users->names[0] = (const char *) &user_utf8_upcase; + rules[0]->users->names[1] = NULL; + + /* Modify the rule to allow only a specific service */ + rules[0]->services->category = HBAC_CATEGORY_NULL; + + rules[0]->services->names = talloc_array(rules[0], const char *, 2); + fail_if(rules[0]->services->names == NULL); + + rules[0]->services->names[0] = (const char *) &service_utf8_upcase; + rules[0]->services->names[1] = NULL; + + /* Modify the rule to allow only a specific service */ + rules[0]->srchosts->category = HBAC_CATEGORY_NULL; + + rules[0]->srchosts->names = talloc_array(rules[0], const char *, 2); + fail_if(rules[0]->services->names == NULL); + + rules[0]->srchosts->names[0] = (const char *) &srchost_utf8_upcase; + rules[0]->services->names[1] = NULL; + + rules[1] = NULL; + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rules[0], &missing_attrs); + fail_unless(is_valid); + fail_unless(missing_attrs == 0); + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_ALLOW, + "Expected [%s], got [%s]; " + "Error: [%s]", + hbac_result_string(HBAC_EVAL_ALLOW), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + /* Negative test - a different letter */ + rules[0]->users->names[0] = (const char *) &user_utf8_lowcase_neg; + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_DENY, + "Expected [%s], got [%s]; " + "Error: [%s]", + hbac_result_string(HBAC_EVAL_DENY), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + /* Negative test - Turkish dotless i. We cannot know that capital I + * casefolds into dotless i unless we know the language is Turkish */ + eval_req->user->name = (const char *) &user_lowcase_tr; + rules[0]->users->names[0] = (const char *) &user_upcase_tr; + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rules[0], &missing_attrs); + fail_unless(is_valid); + fail_unless(missing_attrs == 0); + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_DENY, + "Expected [%s], got [%s]; " + "Error: [%s]", + hbac_result_string(HBAC_EVAL_DENY), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + talloc_free(test_ctx); +} +END_TEST + +START_TEST(ipa_hbac_test_allow_group) +{ + enum hbac_eval_result result; + TALLOC_CTX *test_ctx; + struct hbac_rule **rules; + struct hbac_eval_req *eval_req; + struct hbac_info *info; + bool is_valid; + uint32_t missing_attrs; + + test_ctx = talloc_new(global_talloc_context); + + /* Create a request */ + eval_req = talloc_zero(test_ctx, struct hbac_eval_req); + fail_if (eval_req == NULL); + + get_test_user(eval_req, &eval_req->user); + get_test_service(eval_req, &eval_req->service); + get_test_srchost(eval_req, &eval_req->srchost); + + /* Create the rules to evaluate against */ + rules = talloc_array(test_ctx, struct hbac_rule *, 2); + fail_if (rules == NULL); + + get_allow_all_rule(rules, &rules[0]); + + /* Modify the rule to allow only a group of users */ + rules[0]->name = talloc_strdup(rules[0], "Allow group"); + fail_if(rules[0]->name == NULL); + rules[0]->users->category = HBAC_CATEGORY_NULL; + + rules[0]->users->names = NULL; + rules[0]->users->groups = talloc_array(rules[0], const char *, 2); + fail_if(rules[0]->users->groups == NULL); + + rules[0]->users->groups[0] = HBAC_TEST_GROUP1; + rules[0]->users->groups[1] = NULL; + + rules[1] = NULL; + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rules[0], &missing_attrs); + fail_unless(is_valid); + fail_unless(missing_attrs == 0); + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_ALLOW, + "Expected [%s], got [%s]; " + "Error: [%s]", + hbac_result_string(HBAC_EVAL_ALLOW), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + /* Negative test */ + rules[0]->users->groups[0] = HBAC_TEST_INVALID_GROUP; + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rules[0], &missing_attrs); + fail_unless(is_valid); + fail_unless(missing_attrs == 0); + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_DENY, + "Expected [%s], got [%s]; " + "Error: [%s]", + hbac_result_string(HBAC_EVAL_DENY), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + talloc_free(test_ctx); +} +END_TEST + +START_TEST(ipa_hbac_test_allow_svc) +{ + enum hbac_eval_result result; + TALLOC_CTX *test_ctx; + struct hbac_rule **rules; + struct hbac_eval_req *eval_req; + struct hbac_info *info; + bool is_valid; + uint32_t missing_attrs; + + test_ctx = talloc_new(global_talloc_context); + + /* Create a request */ + eval_req = talloc_zero(test_ctx, struct hbac_eval_req); + fail_if (eval_req == NULL); + + get_test_user(eval_req, &eval_req->user); + get_test_service(eval_req, &eval_req->service); + get_test_srchost(eval_req, &eval_req->srchost); + + /* Create the rules to evaluate against */ + rules = talloc_array(test_ctx, struct hbac_rule *, 2); + fail_if (rules == NULL); + + get_allow_all_rule(rules, &rules[0]); + + /* Modify the rule to allow only a specific service */ + rules[0]->name = talloc_strdup(rules[0], "Allow service"); + fail_if(rules[0]->name == NULL); + rules[0]->services->category = HBAC_CATEGORY_NULL; + + rules[0]->services->names = talloc_array(rules[0], const char *, 2); + fail_if(rules[0]->services->names == NULL); + + rules[0]->services->names[0] = HBAC_TEST_SERVICE; + rules[0]->services->names[1] = NULL; + + rules[1] = NULL; + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rules[0], &missing_attrs); + fail_unless(is_valid); + fail_unless(missing_attrs == 0); + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_ALLOW, + "Expected [%s], got [%s]; " + "Error: [%s]", + hbac_result_string(HBAC_EVAL_ALLOW), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + /* Negative test */ + rules[0]->services->names[0] = HBAC_TEST_INVALID_SERVICE; + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rules[0], &missing_attrs); + fail_unless(is_valid); + fail_unless(missing_attrs == 0); + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_DENY, + "Expected [%s], got [%s]; " + "Error: [%s]", + hbac_result_string(HBAC_EVAL_DENY), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + talloc_free(test_ctx); +} +END_TEST + +START_TEST(ipa_hbac_test_allow_svcgroup) +{ + enum hbac_eval_result result; + TALLOC_CTX *test_ctx; + struct hbac_rule **rules; + struct hbac_eval_req *eval_req; + struct hbac_info *info; + bool is_valid; + uint32_t missing_attrs; + + test_ctx = talloc_new(global_talloc_context); + + /* Create a request */ + eval_req = talloc_zero(test_ctx, struct hbac_eval_req); + fail_if (eval_req == NULL); + + get_test_user(eval_req, &eval_req->user); + get_test_service(eval_req, &eval_req->service); + get_test_srchost(eval_req, &eval_req->srchost); + + /* Create the rules to evaluate against */ + rules = talloc_array(test_ctx, struct hbac_rule *, 2); + fail_if (rules == NULL); + + get_allow_all_rule(rules, &rules[0]); + + /* Modify the rule to allow only a group of users */ + rules[0]->name = talloc_strdup(rules[0], "Allow servicegroup"); + fail_if(rules[0]->name == NULL); + rules[0]->services->category = HBAC_CATEGORY_NULL; + + rules[0]->services->names = NULL; + rules[0]->services->groups = talloc_array(rules[0], const char *, 2); + fail_if(rules[0]->services->groups == NULL); + + rules[0]->services->groups[0] = HBAC_TEST_SERVICEGROUP1; + rules[0]->services->groups[1] = NULL; + + rules[1] = NULL; + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rules[0], &missing_attrs); + fail_unless(is_valid); + fail_unless(missing_attrs == 0); + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_ALLOW, + "Expected [%s], got [%s]; " + "Error: [%s]", + hbac_result_string(HBAC_EVAL_ALLOW), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + /* Negative test */ + rules[0]->services->groups[0] = HBAC_TEST_INVALID_SERVICEGROUP; + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rules[0], &missing_attrs); + fail_unless(is_valid); + fail_unless(missing_attrs == 0); + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_DENY, + "Expected [%s], got [%s]; " + "Error: [%s]", + hbac_result_string(HBAC_EVAL_DENY), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + talloc_free(test_ctx); +} +END_TEST + +START_TEST(ipa_hbac_test_allow_srchost) +{ + enum hbac_eval_result result; + TALLOC_CTX *test_ctx; + struct hbac_rule **rules; + struct hbac_eval_req *eval_req; + struct hbac_info *info; + bool is_valid; + uint32_t missing_attrs; + + test_ctx = talloc_new(global_talloc_context); + + /* Create a request */ + eval_req = talloc_zero(test_ctx, struct hbac_eval_req); + fail_if (eval_req == NULL); + + get_test_user(eval_req, &eval_req->user); + get_test_service(eval_req, &eval_req->service); + get_test_srchost(eval_req, &eval_req->srchost); + + /* Create the rules to evaluate against */ + rules = talloc_array(test_ctx, struct hbac_rule *, 2); + fail_if (rules == NULL); + + get_allow_all_rule(rules, &rules[0]); + + /* Modify the rule to allow only a specific service */ + rules[0]->name = talloc_strdup(rules[0], "Allow srchost"); + fail_if(rules[0]->name == NULL); + rules[0]->srchosts->category = HBAC_CATEGORY_NULL; + + rules[0]->srchosts->names = talloc_array(rules[0], const char *, 2); + fail_if(rules[0]->srchosts->names == NULL); + + rules[0]->srchosts->names[0] = HBAC_TEST_SRCHOST; + rules[0]->srchosts->names[1] = NULL; + + rules[1] = NULL; + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rules[0], &missing_attrs); + fail_unless(is_valid); + fail_unless(missing_attrs == 0); + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_ALLOW, + "Expected [%s], got [%s]; " + "Error: [%s]", + hbac_result_string(HBAC_EVAL_ALLOW), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + /* Negative test */ + rules[0]->srchosts->names[0] = HBAC_TEST_INVALID_SRCHOST; + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rules[0], &missing_attrs); + fail_unless(is_valid); + fail_unless(missing_attrs == 0); + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_DENY, + "Expected [%s], got [%s]; " + "Error: [%s](%s)", + hbac_result_string(HBAC_EVAL_DENY), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + talloc_free(test_ctx); +} +END_TEST + +START_TEST(ipa_hbac_test_allow_srchostgroup) +{ + enum hbac_eval_result result; + TALLOC_CTX *test_ctx; + struct hbac_rule **rules; + struct hbac_eval_req *eval_req; + struct hbac_info *info; + bool is_valid; + uint32_t missing_attrs; + + test_ctx = talloc_new(global_talloc_context); + + /* Create a request */ + eval_req = talloc_zero(test_ctx, struct hbac_eval_req); + fail_if (eval_req == NULL); + + get_test_user(eval_req, &eval_req->user); + get_test_service(eval_req, &eval_req->service); + get_test_srchost(eval_req, &eval_req->srchost); + + /* Create the rules to evaluate against */ + rules = talloc_array(test_ctx, struct hbac_rule *, 2); + fail_if (rules == NULL); + + get_allow_all_rule(rules, &rules[0]); + + /* Modify the rule to allow only a group of users */ + rules[0]->name = talloc_strdup(rules[0], "Allow srchostgroup"); + fail_if(rules[0]->name == NULL); + rules[0]->srchosts->category = HBAC_CATEGORY_NULL; + + rules[0]->srchosts->names = NULL; + rules[0]->srchosts->groups = talloc_array(rules[0], const char *, 2); + fail_if(rules[0]->srchosts->groups == NULL); + + rules[0]->srchosts->groups[0] = HBAC_TEST_SRCHOSTGROUP1; + rules[0]->srchosts->groups[1] = NULL; + + rules[1] = NULL; + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rules[0], &missing_attrs); + fail_unless(is_valid); + fail_unless(missing_attrs == 0); + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_ALLOW, + "Expected [%s], got [%s]; " + "Error: [%s]", + hbac_result_string(HBAC_EVAL_ALLOW), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + /* Negative test */ + rules[0]->srchosts->groups[0] = HBAC_TEST_INVALID_SRCHOSTGROUP; + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rules[0], &missing_attrs); + fail_unless(is_valid); + fail_unless(missing_attrs == 0); + + /* Evaluate the rules */ + result = hbac_evaluate(rules, eval_req, &info); + fail_unless(result == HBAC_EVAL_DENY, + "Expected [%s], got [%s]; " + "Error: [%s]", + hbac_result_string(HBAC_EVAL_DENY), + hbac_result_string(result), + info ? hbac_error_string(info->code):"Unknown"); + + talloc_free(test_ctx); +} +END_TEST + +START_TEST(ipa_hbac_test_incomplete) +{ + TALLOC_CTX *test_ctx; + struct hbac_rule *rule; + bool is_valid; + uint32_t missing_attrs; + + test_ctx = talloc_new(global_talloc_context); + + rule = talloc_zero(test_ctx, struct hbac_rule); + + /* Validate this rule */ + is_valid = hbac_rule_is_complete(rule, &missing_attrs); + fail_if(is_valid); + fail_unless(missing_attrs | HBAC_RULE_ELEMENT_USERS); + fail_unless(missing_attrs | HBAC_RULE_ELEMENT_SERVICES); + fail_unless(missing_attrs | HBAC_RULE_ELEMENT_TARGETHOSTS); + fail_unless(missing_attrs | HBAC_RULE_ELEMENT_SOURCEHOSTS); + + talloc_free(test_ctx); +} +END_TEST + +Suite *hbac_test_suite (void) +{ + Suite *s = suite_create ("HBAC"); + + TCase *tc_hbac = tcase_create("HBAC_rules"); + tcase_add_checked_fixture(tc_hbac, + leak_check_setup, + leak_check_teardown); + + tcase_add_test(tc_hbac, ipa_hbac_test_allow_all); + tcase_add_test(tc_hbac, ipa_hbac_test_allow_user); + tcase_add_test(tc_hbac, ipa_hbac_test_allow_group); + tcase_add_test(tc_hbac, ipa_hbac_test_allow_svc); + tcase_add_test(tc_hbac, ipa_hbac_test_allow_svcgroup); + tcase_add_test(tc_hbac, ipa_hbac_test_allow_srchost); + tcase_add_test(tc_hbac, ipa_hbac_test_allow_srchostgroup); + tcase_add_test(tc_hbac, ipa_hbac_test_allow_utf8); + tcase_add_test(tc_hbac, ipa_hbac_test_incomplete); + + suite_add_tcase(s, tc_hbac); + return s; +} + +int main(int argc, const char *argv[]) +{ + int number_failed; + + tests_set_cwd(); + + Suite *s = hbac_test_suite(); + SRunner *sr = srunner_create(s); + + /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */ + srunner_run_all(sr, CK_ENV); + number_failed = srunner_ntests_failed (sr); + srunner_free (sr); + + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} -- cgit