diff options
Diffstat (limited to 'src/lib/ipa_hbac/hbac_evaluator.c')
-rw-r--r-- | src/lib/ipa_hbac/hbac_evaluator.c | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/src/lib/ipa_hbac/hbac_evaluator.c b/src/lib/ipa_hbac/hbac_evaluator.c new file mode 100644 index 000000000..ce13bd535 --- /dev/null +++ b/src/lib/ipa_hbac/hbac_evaluator.c @@ -0,0 +1,520 @@ +/* + SSSD + + IPA Backend Module -- Access control + + Authors: + Sumit Bose <sbose@redhat.com> + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2011 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" /* for HAVE_FUNCTION_ATTRIBUTE_FORMAT in "ipa_hbac.h" */ + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include "ipa_hbac.h" +#include "sss_utf8.h" + +#ifndef HAVE_ERRNO_T +#define HAVE_ERRNO_T +typedef int errno_t; +#endif + +#ifndef EOK +#define EOK 0 +#endif + +/* HBAC logging system */ + +/* debug macro */ +#define HBAC_DEBUG(level, format, ...) do { \ + if (hbac_debug_fn != NULL) { \ + hbac_debug_fn(__FILE__, __LINE__, __FUNCTION__, \ + level, format, ##__VA_ARGS__); \ + } \ +} while (0) + +/* static pointer to external logging function */ +static hbac_debug_fn_t hbac_debug_fn = NULL; + +/* setup function for external logging function */ +void hbac_enable_debug(hbac_debug_fn_t external_debug_fn) +{ + hbac_debug_fn = external_debug_fn; +} + +/* auxiliary function for hbac_request_element logging */ +static void hbac_request_element_debug_print(struct hbac_request_element *el, + const char *label); + +/* auxiliary function for hbac_eval_req logging */ +static void hbac_req_debug_print(struct hbac_eval_req *req); + +/* auxiliary function for hbac_rule_element logging */ +static void hbac_rule_element_debug_print(struct hbac_rule_element *el, + const char *label); + +/* auxiliary function for hbac_rule logging */ +static void hbac_rule_debug_print(struct hbac_rule *rule); + + +/* 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) +{ + uint32_t i; + + enum hbac_error_code ret; + enum hbac_eval_result result = HBAC_EVAL_DENY; + enum hbac_eval_result_int intermediate_result; + + HBAC_DEBUG(HBAC_DBG_INFO, "[< hbac_evaluate()\n"); + hbac_req_debug_print(hbac_req); + + if (info) { + *info = malloc(sizeof(struct hbac_info)); + if (!*info) { + HBAC_DEBUG(HBAC_DBG_ERROR, "Out of memory.\n"); + return HBAC_EVAL_OOM; + } + (*info)->code = HBAC_ERROR_UNKNOWN; + (*info)->rule_name = NULL; + } + + for (i = 0; rules[i]; i++) { + hbac_rule_debug_print(rules[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 */ + HBAC_DEBUG(HBAC_DBG_INFO, "The rule [%s] did not match.\n", + rules[i]->name); + continue; + } else if (intermediate_result == HBAC_EVAL_MATCHED) { + HBAC_DEBUG(HBAC_DBG_INFO, "ALLOWED by rule [%s].\n", rules[i]->name); + result = HBAC_EVAL_ALLOW; + if (info) { + (*info)->code = HBAC_SUCCESS; + (*info)->rule_name = strdup(rules[i]->name); + if (!(*info)->rule_name) { + HBAC_DEBUG(HBAC_DBG_ERROR, "Out of memory.\n"); + result = HBAC_EVAL_ERROR; + (*info)->code = HBAC_ERROR_OUT_OF_MEMORY; + } + } + break; + } else { + /* An error occurred processing this rule */ + HBAC_DEBUG(HBAC_DBG_ERROR, + "Error %d occurred during evaluating of rule [%s].\n", + ret, rules[i]->name); + result = HBAC_EVAL_ERROR; + if (info) { + (*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: + + HBAC_DEBUG(HBAC_DBG_INFO, "hbac_evaluate() >]\n"); + 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) { + HBAC_DEBUG(HBAC_DBG_INFO, "Rule [%s] is not enabled\n", rule->name); + return HBAC_EVAL_UNMATCHED; + } + + /* Make sure we have all elements */ + if (!rule->users + || !rule->services + || !rule->targethosts + || !rule->srchosts) { + HBAC_DEBUG(HBAC_DBG_INFO, + "Rule [%s] cannot be parsed, some elements are empty\n", + rule->name); + *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) { + HBAC_DEBUG(HBAC_DBG_ERROR, + "Cannot parse user elements of rule [%s]\n", rule->name); + *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) { + HBAC_DEBUG(HBAC_DBG_ERROR, + "Cannot parse service elements of rule [%s]\n", rule->name); + *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) { + HBAC_DEBUG(HBAC_DBG_ERROR, + "Cannot parse targethost elements of rule [%s]\n", + rule->name); + *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) { + HBAC_DEBUG(HBAC_DBG_ERROR, + "Cannot parse srchost elements of rule [%s]\n", + rule->name); + *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 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. */ + ret = sss_utf8_case_eq(rule_name, req_name); + if (ret != EOK && ret != ENOMATCH) { + return ret; + } else if (ret == EOK) { + *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. */ + ret = sss_utf8_case_eq(rule_name, req_name); + if (ret != EOK && ret != ENOMATCH) { + return ret; + } else if (ret == EOK) { + *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); +} + +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"; + } +} + +static void hbac_request_element_debug_print(struct hbac_request_element *el, + const char *label) +{ + int i; + + if (el) { + if (el->name) { + HBAC_DEBUG(HBAC_DBG_TRACE, "\t\t%s [%s]\n", label, el->name); + } + + if (el->groups) { + if (el->groups[0]) { + HBAC_DEBUG(HBAC_DBG_TRACE, "\t\t%s_group:\n", label); + for (i = 0; el->groups[i]; i++) { + HBAC_DEBUG(HBAC_DBG_TRACE, "\t\t\t[%s]\n", el->groups[i]); + } + } else { + HBAC_DEBUG(HBAC_DBG_TRACE, "\t\t%s_group (none)\n", label); + } + } + } else { + HBAC_DEBUG(HBAC_DBG_TRACE, "\t%s (none)\n", label); + } +} + +static void hbac_req_debug_print(struct hbac_eval_req *req) +{ + HBAC_DEBUG(HBAC_DBG_TRACE, "\tREQUEST:\n"); + if (req) { + struct tm *local_time = NULL; + size_t ret; + const size_t buff_size = 100; + char time_buff[buff_size]; + + hbac_request_element_debug_print(req->service, "service"); + hbac_request_element_debug_print(req->user, "user"); + hbac_request_element_debug_print(req->targethost, "targethost"); + hbac_request_element_debug_print(req->srchost, "srchost"); + + local_time = localtime(&req->request_time); + if (local_time == NULL) { + return; + } + + ret = strftime(time_buff, buff_size, "%Y-%m-%d %H:%M:%S", local_time); + if (ret <= 0) { + return; + } + + HBAC_DEBUG(HBAC_DBG_TRACE, "\t\trequest time %s\n", time_buff); + } else { + HBAC_DEBUG(HBAC_DBG_TRACE, "\tRequest is EMPTY.\n"); + } +} + +static void hbac_rule_element_debug_print(struct hbac_rule_element *el, + const char *label) +{ + int i; + + if (el) { + HBAC_DEBUG(HBAC_DBG_TRACE, "\t\tcategory [%#x] [%s]\n", el->category, + (el->category == HBAC_CATEGORY_ALL) ? "ALL" : "NONE"); + + if (el->names) { + if (el->names[0]) { + HBAC_DEBUG(HBAC_DBG_TRACE, "\t\t%s_names:\n", label); + for (i = 0; el->names[i]; i++) { + HBAC_DEBUG(HBAC_DBG_TRACE, "\t\t\t[%s]\n", el->names[i]); + } + } else { + HBAC_DEBUG(HBAC_DBG_TRACE, "\t\t%s_names (none)\n", label); + } + } + + if (el->groups) { + if (el->groups[0]) { + HBAC_DEBUG(HBAC_DBG_TRACE, "\t\t%s_groups:\n", label); + for (i = 0; el->groups[i]; i++) { + HBAC_DEBUG(HBAC_DBG_TRACE, "\t\t\t[%s]\n", el->groups[i]); + } + } else { + HBAC_DEBUG(HBAC_DBG_TRACE, "\t\t%s_groups (none)\n", label); + } + } + } +} + +static void hbac_rule_debug_print(struct hbac_rule *rule) +{ + if (rule) { + HBAC_DEBUG(HBAC_DBG_TRACE, "\tRULE [%s] [%s]:\n", + rule->name, (rule->enabled) ? "ENABLED" : "DISABLED"); + if (rule->services) { + HBAC_DEBUG(HBAC_DBG_TRACE, "\tservices:\n"); + hbac_rule_element_debug_print(rule->services, "services"); + } else { + HBAC_DEBUG(HBAC_DBG_TRACE, "\tservices (none)\n"); + } + + if (rule->users) { + HBAC_DEBUG(HBAC_DBG_TRACE, "\tusers:\n"); + hbac_rule_element_debug_print(rule->users, "users"); + } else { + HBAC_DEBUG(HBAC_DBG_TRACE, "\tusers (none)\n"); + } + + if (rule->targethosts) { + HBAC_DEBUG(HBAC_DBG_TRACE, "\ttargethosts:\n"); + hbac_rule_element_debug_print(rule->targethosts, "targethosts"); + } else { + HBAC_DEBUG(HBAC_DBG_TRACE, "\ttargethosts (none)\n"); + } + + if (rule->srchosts) { + HBAC_DEBUG(HBAC_DBG_TRACE, "\tsrchosts:\n"); + hbac_rule_element_debug_print(rule->srchosts, "srchosts"); + } else { + HBAC_DEBUG(HBAC_DBG_TRACE, "\tsrchosts (none)\n"); + } + } +} |