From 68abbe716bed7c8d6790d9bec168ef44469306a1 Mon Sep 17 00:00:00 2001 From: Pavel Březina Date: Wed, 16 Dec 2015 14:42:04 +0100 Subject: SUDO: make sudo sysdb interface more reusable Reviewed-by: Sumit Bose --- src/db/sysdb.c | 98 ++++++++++++++++++ src/db/sysdb.h | 7 ++ src/db/sysdb_sudo.c | 286 +++++++++++++++++++++++++++++++++++++++++----------- src/db/sysdb_sudo.h | 17 ++-- 4 files changed, 339 insertions(+), 69 deletions(-) (limited to 'src/db') diff --git a/src/db/sysdb.c b/src/db/sysdb.c index a71364d7c..d4366a3c7 100644 --- a/src/db/sysdb.c +++ b/src/db/sysdb.c @@ -2013,3 +2013,101 @@ errno_t sysdb_msg2attrs(TALLOC_CTX *mem_ctx, size_t count, return EOK; } + +int sysdb_compare_usn(const char *a, const char *b) +{ + size_t len_a; + size_t len_b; + + if (a == NULL) { + return -1; + } + + if (b == NULL) { + return 1; + } + + len_a = strlen(a); + len_b = strlen(b); + + /* trim leading zeros */ + while (len_a > 0 && *a == '0') { + a++; + len_a--; + } + + while (len_b > 0 && *b == '0') { + b++; + len_b--; + } + + /* less digits means lower number */ + if (len_a < len_b) { + return -1; + } + + /* more digits means bigger number */ + if (len_a > len_b) { + return 1; + } + + /* now we can compare digits since alphabetical order is the same + * as numeric order */ + return strcmp(a, b); +} + +errno_t sysdb_get_highest_usn(TALLOC_CTX *mem_ctx, + struct sysdb_attrs **attrs, + size_t num_attrs, + char **_usn) +{ + const char *highest = NULL; + const char *current = NULL; + char *usn; + errno_t ret; + size_t i; + + if (num_attrs == 0 || attrs == NULL) { + goto done; + } + + for (i = 0; i < num_attrs; i++) { + ret = sysdb_attrs_get_string(attrs[i], SYSDB_USN, ¤t); + if (ret == ENOENT) { + /* USN value is not present, assuming zero. */ + current = "0"; + } else if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to retrieve USN value " + "[%d]: %s\n", ret, sss_strerror(ret)); + + return ret; + } + + if (current == NULL) { + continue; + } + + if (highest == NULL) { + highest = current; + continue; + } + + if (sysdb_compare_usn(current, highest) > 0 ) { + highest = current; + } + } + +done: + if (highest == NULL) { + usn = talloc_strdup(mem_ctx, "0"); + } else { + usn = talloc_strdup(mem_ctx, highest); + } + + if (usn == NULL) { + return ENOMEM; + } + + *_usn = usn; + return EOK; +} diff --git a/src/db/sysdb.h b/src/db/sysdb.h index ad1bf75b7..2e797fd7f 100644 --- a/src/db/sysdb.h +++ b/src/db/sysdb.h @@ -386,6 +386,13 @@ errno_t sysdb_msg2attrs(TALLOC_CTX *mem_ctx, size_t count, struct ldb_message **msgs, struct sysdb_attrs ***attrs); +int sysdb_compare_usn(const char *a, const char *b); + +errno_t sysdb_get_highest_usn(TALLOC_CTX *mem_ctx, + struct sysdb_attrs **attrs, + size_t num_attrs, + char **_usn); + /* convert an ldb error into an errno error */ int sysdb_error_to_errno(int ldberr); diff --git a/src/db/sysdb_sudo.c b/src/db/sysdb_sudo.c index 784ac8af3..76116abac 100644 --- a/src/db/sysdb_sudo.c +++ b/src/db/sysdb_sudo.c @@ -27,6 +27,8 @@ #include "db/sysdb_private.h" #include "db/sysdb_sudo.h" +#define SUDO_ALL_FILTER "(" SYSDB_OBJECTCLASS "=" SYSDB_SUDO_CACHE_OC ")" + #define NULL_CHECK(val, rval, label) do { \ if (!val) { \ rval = ENOMEM; \ @@ -427,41 +429,6 @@ done: return ret; } -errno_t -sysdb_save_sudorule(struct sss_domain_info *domain, - const char *rule_name, - struct sysdb_attrs *attrs) -{ - errno_t ret; - - DEBUG(SSSDBG_TRACE_FUNC, "Adding sudo rule %s\n", rule_name); - - ret = sysdb_attrs_add_string(attrs, SYSDB_OBJECTCLASS, - SYSDB_SUDO_CACHE_OC); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Could not set rule object class [%d]: %s\n", - ret, strerror(ret)); - return ret; - } - - ret = sysdb_attrs_add_string(attrs, SYSDB_NAME, rule_name); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Could not set name attribute [%d]: %s\n", - ret, strerror(ret)); - return ret; - } - - ret = sysdb_store_custom(domain, rule_name, - SUDORULE_SUBDIR, attrs); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "sysdb_store_custom failed [%d]: %s\n", - ret, strerror(ret)); - return ret; - } - - return EOK; -} - static errno_t sysdb_sudo_set_refresh_time(struct sss_domain_info *domain, const char *attr_name, time_t value) @@ -615,6 +582,26 @@ errno_t sysdb_sudo_get_last_full_refresh(struct sss_domain_info *domain, /* ==================== Purge functions ==================== */ +static const char * +sysdb_sudo_get_rule_name(struct sysdb_attrs *rule) +{ + const char *name; + errno_t ret; + + ret = sysdb_attrs_get_string(rule, SYSDB_SUDO_CACHE_AT_CN, &name); + if (ret == ERANGE) { + DEBUG(SSSDBG_MINOR_FAILURE, "Warning: found rule that contains none " + "or multiple CN values. It will be skipped.\n"); + return NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to obtain rule name [%d]: %s\n", + ret, strerror(ret)); + return NULL; + } + + return name; +} + static errno_t sysdb_sudo_purge_all(struct sss_domain_info *domain) { struct ldb_dn *base_dn = NULL; @@ -627,6 +614,8 @@ static errno_t sysdb_sudo_purge_all(struct sss_domain_info *domain) base_dn = sysdb_custom_subtree_dn(tmp_ctx, domain, SUDORULE_SUBDIR); NULL_CHECK(base_dn, ret, done); + DEBUG(SSSDBG_TRACE_FUNC, "Deleting all cached sudo rules\n"); + ret = sysdb_delete_recursive(domain->sysdb, base_dn, true); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_delete_recursive failed.\n"); @@ -639,42 +628,74 @@ done: return ret; } -errno_t sysdb_sudo_purge_byname(struct sss_domain_info *domain, - const char *name) +static errno_t +sysdb_sudo_purge_byname(struct sss_domain_info *domain, + const char *name) { DEBUG(SSSDBG_TRACE_INTERNAL, "Deleting sudo rule %s\n", name); return sysdb_delete_custom(domain, name, SUDORULE_SUBDIR); } -errno_t sysdb_sudo_purge_byfilter(struct sss_domain_info *domain, - const char *filter) +static errno_t +sysdb_sudo_purge_byrules(struct sss_domain_info *dom, + struct sysdb_attrs **rules, + size_t num_rules) +{ + const char *name; + errno_t ret; + size_t i; + + DEBUG(SSSDBG_TRACE_FUNC, "About to remove rules from sudo cache\n"); + + if (num_rules == 0 || rules == NULL) { + return EOK; + } + + for (i = 0; i < num_rules; i++) { + name = sysdb_sudo_get_rule_name(rules[i]); + if (name == NULL) { + continue; + } + + ret = sysdb_sudo_purge_byname(dom, name); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to delete rule " + "%s [%d]: %s\n", name, ret, sss_strerror(ret)); + continue; + } + } + + return EOK; +} + +static errno_t +sysdb_sudo_purge_byfilter(struct sss_domain_info *domain, + const char *filter) { TALLOC_CTX *tmp_ctx; - size_t count; + struct sysdb_attrs **rules; struct ldb_message **msgs; - const char *name; - int i; + size_t count; errno_t ret; - errno_t sret; - bool in_transaction = false; const char *attrs[] = { SYSDB_OBJECTCLASS, SYSDB_NAME, SYSDB_SUDO_CACHE_AT_CN, NULL }; - /* just purge all if there's no filter */ - if (!filter) { + if (filter == NULL || strcmp(filter, SUDO_ALL_FILTER) == 0) { return sysdb_sudo_purge_all(domain); } tmp_ctx = talloc_new(NULL); - NULL_CHECK(tmp_ctx, ret, done); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } - /* match entries based on the filter and remove them one by one */ ret = sysdb_search_custom(tmp_ctx, domain, filter, SUDORULE_SUBDIR, attrs, &count, &msgs); - if (ret == ENOENT) { + if (ret == ENOENT || count == 0) { DEBUG(SSSDBG_TRACE_FUNC, "No rules matched\n"); ret = EOK; goto done; @@ -683,24 +704,165 @@ errno_t sysdb_sudo_purge_byfilter(struct sss_domain_info *domain, goto done; } + ret = sysdb_msg2attrs(tmp_ctx, count, msgs, &rules); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to convert ldb message to " + "sysdb attrs [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_sudo_purge_byrules(domain, rules, count); + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sysdb_sudo_purge(struct sss_domain_info *domain, + const char *delete_filter, + struct sysdb_attrs **rules, + size_t num_rules) +{ + bool in_transaction = false; + errno_t sret; + errno_t ret; + ret = sysdb_transaction_start(domain->sysdb); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); - goto done; + return ret; } in_transaction = true; - for (i = 0; i < count; i++) { - name = ldb_msg_find_attr_as_string(msgs[i], SYSDB_NAME, NULL); - if (name == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "A rule without a name?\n"); - /* skip this one but still delete other entries */ - continue; + if (delete_filter) { + ret = sysdb_sudo_purge_byfilter(domain, delete_filter); + } else { + ret = sysdb_sudo_purge_byrules(domain, rules, num_rules); + } + + if (ret != EOK) { + goto done; + } + + ret = sysdb_transaction_commit(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(domain->sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not cancel transaction\n"); } + } - ret = sysdb_sudo_purge_byname(domain, name); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Could not delete rule %s\n", name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to purge sudo cache [%d]: %s\n", + ret, sss_strerror(ret)); + } + + return ret; +} + +static errno_t +sysdb_sudo_add_sss_attrs(struct sysdb_attrs *rule, + const char *name, + int cache_timeout, + time_t now) +{ + time_t expire; + errno_t ret; + + ret = sysdb_attrs_add_string(rule, SYSDB_OBJECTCLASS, SYSDB_SUDO_CACHE_OC); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to add %s attribute [%d]: %s\n", + SYSDB_OBJECTCLASS, ret, strerror(ret)); + return ret; + } + + ret = sysdb_attrs_add_string(rule, SYSDB_NAME, name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to add %s attribute [%d]: %s\n", + SYSDB_OBJECTCLASS, ret, strerror(ret)); + return ret; + } + + expire = cache_timeout > 0 ? now + cache_timeout : 0; + ret = sysdb_attrs_add_time_t(rule, SYSDB_CACHE_EXPIRE, expire); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to add %s attribute [%d]: %s\n", + SYSDB_CACHE_EXPIRE, ret, strerror(ret)); + return ret; + } + + return EOK; +} + +static errno_t +sysdb_sudo_store_rule(struct sss_domain_info *domain, + struct sysdb_attrs *rule, + int cache_timeout, + time_t now) +{ + const char *name; + errno_t ret; + + name = sysdb_sudo_get_rule_name(rule); + if (name == NULL) { + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Adding sudo rule %s\n", name); + + ret = sysdb_sudo_add_sss_attrs(rule, name, cache_timeout, now); + if (ret != EOK) { + return ret; + } + + ret = sysdb_store_custom(domain, name, SUDORULE_SUBDIR, rule); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to store rule %s [%d]: %s\n", + name, ret, strerror(ret)); + return ret; + } + + return EOK; +} + +errno_t +sysdb_sudo_store(struct sss_domain_info *domain, + struct sysdb_attrs **rules, + size_t num_rules) +{ + bool in_transaction = false; + errno_t sret; + errno_t ret; + time_t now; + size_t i; + + if (num_rules == 0 || rules == NULL) { + return EOK; + } + + ret = sysdb_transaction_start(domain->sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + return ret; + } + in_transaction = true; + + now = time(NULL); + for (i = 0; i < num_rules; i++) { + ret = sysdb_sudo_store_rule(domain, rules[i], + domain->sudo_timeout, now); + if (ret == EINVAL) { + /* Multiple CNs are error on server side, we can just ignore this + * rule and save the others. Loud debug message is in logs. */ + continue; + } else if (ret != EOK) { goto done; } } @@ -720,6 +882,10 @@ done: } } - talloc_free(tmp_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to store sudo rules [%d]: %s\n", + ret, sss_strerror(ret)); + } + return ret; } diff --git a/src/db/sysdb_sudo.h b/src/db/sysdb_sudo.h index fc896c385..6dd9ea7bb 100644 --- a/src/db/sysdb_sudo.h +++ b/src/db/sysdb_sudo.h @@ -78,20 +78,19 @@ sysdb_get_sudo_user_info(TALLOC_CTX *mem_ctx, const char *username, uid_t *_uid, char ***groupnames); -errno_t -sysdb_save_sudorule(struct sss_domain_info *domain, - const char *rule_name, - struct sysdb_attrs *attrs); - errno_t sysdb_sudo_set_last_full_refresh(struct sss_domain_info *domain, time_t value); errno_t sysdb_sudo_get_last_full_refresh(struct sss_domain_info *domain, time_t *value); -errno_t sysdb_sudo_purge_byname(struct sss_domain_info *domain, - const char *name); +errno_t sysdb_sudo_purge(struct sss_domain_info *domain, + const char *delete_filter, + struct sysdb_attrs **rules, + size_t num_rules); -errno_t sysdb_sudo_purge_byfilter(struct sss_domain_info *domain, - const char *filter); +errno_t +sysdb_sudo_store(struct sss_domain_info *domain, + struct sysdb_attrs **rules, + size_t num_rules); #endif /* _SYSDB_SUDO_H_ */ -- cgit