/* Authors: Pavel Březina Jakub Hrozek 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 "config.h" #include #include #include #include "util/util.h" #include "db/sysdb_sudo.h" #include "responder/sudo/sudosrv_private.h" static errno_t sudosrv_get_user(struct sudo_dom_ctx *dctx); errno_t sudosrv_get_sudorules(struct sudo_dom_ctx *dctx) { errno_t ret; dctx->check_provider = true; ret = sudosrv_get_user(dctx); if (ret == EAGAIN) { DEBUG(SSSDBG_TRACE_INTERNAL, ("Looking up the user info from Data Provider\n")); return EAGAIN; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Error looking up user information [%d]: %s\n", ret, strerror(ret))); return ret; } /* OK, got the user from cache. Try to get the rules. */ ret = sudosrv_get_rules(dctx->cmd_ctx); if (ret == EAGAIN) { DEBUG(SSSDBG_TRACE_INTERNAL, ("Looking up the sudo rules from Data Provider\n")); return EAGAIN; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Error looking up sudo rules [%d]: %s\n", ret, strerror(ret))); return ret; } return EOK; } static void sudosrv_dp_send_acct_req_done(struct tevent_req *req); static void sudosrv_check_user_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr); static errno_t sudosrv_get_user(struct sudo_dom_ctx *dctx) { TALLOC_CTX *tmp_ctx = NULL; struct sss_domain_info *dom = dctx->domain; struct sudo_cmd_ctx *cmd_ctx = dctx->cmd_ctx; struct cli_ctx *cli_ctx = dctx->cmd_ctx->cli_ctx; struct ldb_result *user; time_t cache_expire = 0; struct tevent_req *dpreq; struct dp_callback_ctx *cb_ctx; const char *original_name = NULL; char *name = NULL; uid_t uid = 0; errno_t ret; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_new() failed\n")); return ENOMEM; } while (dom) { /* if it is a domainless search, skip domains that require fully * qualified names instead */ while (dom && cmd_ctx->check_next && dom->fqnames) { dom = get_next_domain(dom, false); } if (!dom) break; /* make sure to update the dctx if we changed domain */ dctx->domain = dom; talloc_free(name); name = sss_get_cased_name(tmp_ctx, cmd_ctx->username, dom->case_sensitive); if (name == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("Out of memory\n")); ret = ENOMEM; goto done; } DEBUG(SSSDBG_FUNC_DATA, ("Requesting info about [%s@%s]\n", name, dom->name)); ret = sysdb_getpwnam(dctx, dctx->domain->sysdb, dctx->domain, name, &user); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Failed to make request to our cache!\n")); ret = EIO; goto done; } if (user->count > 1) { DEBUG(SSSDBG_CRIT_FAILURE, ("getpwnam call returned more than one result !?!\n")); ret = EIO; goto done; } if (user->count == 0 && !dctx->check_provider) { /* if a multidomain search, try with next */ if (cmd_ctx->check_next) { dctx->check_provider = true; dom = get_next_domain(dom, false); if (dom) continue; } DEBUG(SSSDBG_MINOR_FAILURE, ("No results for getpwnam call\n")); ret = ENOENT; goto done; } /* One result found, check cache expiry */ if (user->count == 1) { cache_expire = ldb_msg_find_attr_as_uint64(user->msgs[0], SYSDB_CACHE_EXPIRE, 0); } /* If cache miss and we haven't checked DP yet OR the entry is * outdated, go to DP */ if ((user->count == 0 || cache_expire < time(NULL)) && dctx->check_provider) { dpreq = sss_dp_get_account_send(cli_ctx, cli_ctx->rctx, dom, false, SSS_DP_INITGROUPS, cmd_ctx->username, 0, NULL); if (!dpreq) { DEBUG(SSSDBG_CRIT_FAILURE, ("Out of memory sending data provider request\n")); ret = ENOMEM; goto done; } cb_ctx = talloc_zero(cli_ctx, struct dp_callback_ctx); if(!cb_ctx) { talloc_zfree(dpreq); ret = ENOMEM; goto done; } cb_ctx->callback = sudosrv_check_user_dp_callback; cb_ctx->ptr = dctx; cb_ctx->cctx = cli_ctx; cb_ctx->mem_ctx = cli_ctx; tevent_req_set_callback(dpreq, sudosrv_dp_send_acct_req_done, cb_ctx); /* tell caller we are in an async call */ ret = EAGAIN; goto done; } /* check uid */ uid = ldb_msg_find_attr_as_int(user->msgs[0], SYSDB_UIDNUM, 0); if (uid != cmd_ctx->uid) { /* if a multidomain search, try with next */ if (cmd_ctx->check_next) { dctx->check_provider = true; dom = get_next_domain(dom, false); if (dom) continue; } DEBUG(SSSDBG_MINOR_FAILURE, ("UID does not match\n")); ret = ENOENT; goto done; } /* user is stored in cache, remember cased and original name */ original_name = ldb_msg_find_attr_as_string(user->msgs[0], SYSDB_NAME, NULL); if (original_name == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("A user with no name?\n")); ret = EFAULT; goto done; } cmd_ctx->cased_username = talloc_move(cmd_ctx, &name); cmd_ctx->orig_username = talloc_strdup(cmd_ctx, original_name); if (cmd_ctx->orig_username == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("Out of memory\n")); ret = ENOMEM; goto done; } /* and set domain */ cmd_ctx->domain = dom; DEBUG(SSSDBG_TRACE_FUNC, ("Returning info for user [%s@%s]\n", cmd_ctx->username, dctx->domain->name)); ret = EOK; goto done; } ret = ENOENT; done: talloc_free(tmp_ctx); return ret; } static void sudosrv_dp_send_acct_req_done(struct tevent_req *req) { struct dp_callback_ctx *cb_ctx = tevent_req_callback_data(req, struct dp_callback_ctx); errno_t ret; dbus_uint16_t err_maj; dbus_uint32_t err_min; char *err_msg; ret = sss_dp_get_account_recv(cb_ctx->mem_ctx, req, &err_maj, &err_min, &err_msg); talloc_zfree(req); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("Fatal error, killing connection!\n")); talloc_free(cb_ctx->cctx); return; } cb_ctx->callback(err_maj, err_min, err_msg, cb_ctx->ptr); } static void sudosrv_check_user_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr) { errno_t ret; struct sudo_dom_ctx *dctx = talloc_get_type(ptr, struct sudo_dom_ctx); if (err_maj) { DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to get information from Data Provider\n" "Error: %u, %u, %s\n", (unsigned int)err_maj, (unsigned int)err_min, err_msg)); } DEBUG(SSSDBG_TRACE_INTERNAL, ("Data Provider returned, check the cache again\n")); dctx->check_provider = false; ret = sudosrv_get_user(dctx); if (ret == EAGAIN) { goto done; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Could not look up the user [%d]: %s\n", ret, strerror(ret))); sudosrv_cmd_done(dctx->cmd_ctx, ret); return; } DEBUG(SSSDBG_TRACE_INTERNAL, ("Looking up sudo rules..\n")); ret = sudosrv_get_rules(dctx->cmd_ctx); if (ret == EAGAIN) { goto done; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Error getting sudo rules [%d]: %s\n", ret, strerror(ret))); sudosrv_cmd_done(dctx->cmd_ctx, EIO); return; } done: sudosrv_cmd_done(dctx->cmd_ctx, ret); } static errno_t sudosrv_get_sudorules_from_cache(TALLOC_CTX *mem_ctx, struct sudo_cmd_ctx *cmd_ctx, struct sysdb_attrs ***_rules, uint32_t *_num_rules); static void sudosrv_get_sudorules_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr); static void sudosrv_dp_req_done(struct tevent_req *req); static errno_t sudosrv_get_sudorules_query_cache(TALLOC_CTX *mem_ctx, struct sysdb_ctx *sysdb, struct sss_domain_info *domain, const char **attrs, unsigned int flags, const char *username, uid_t uid, char **groupnames, struct sysdb_attrs ***_rules, uint32_t *_count); errno_t sudosrv_get_rules(struct sudo_cmd_ctx *cmd_ctx) { TALLOC_CTX *tmp_ctx = NULL; struct tevent_req *dpreq = NULL; struct dp_callback_ctx *cb_ctx = NULL; char **groupnames = NULL; uint32_t expired_rules_num = 0; struct sysdb_attrs **expired_rules = NULL; errno_t ret; unsigned int flags = SYSDB_SUDO_FILTER_NONE; const char *attrs[] = { SYSDB_NAME, NULL }; if (cmd_ctx->domain == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("Domain is not set!\n")); return EFAULT; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_new() failed\n")); return ENOMEM; } switch (cmd_ctx->type) { case SSS_SUDO_DEFAULTS: DEBUG(SSSDBG_TRACE_FUNC, ("Retrieving default options " "for [%s] from [%s]\n", cmd_ctx->orig_username, cmd_ctx->domain->name)); break; case SSS_SUDO_USER: DEBUG(SSSDBG_TRACE_FUNC, ("Retrieving rules " "for [%s] from [%s]\n", cmd_ctx->orig_username, cmd_ctx->domain->name)); break; } /* Fetch all expired rules: * sudo asks sssd twice - for defaults and for rules. If we refresh all * expired rules for this user and defaults at once we will save one * provider call */ ret = sysdb_get_sudo_user_info(tmp_ctx, cmd_ctx->domain->sysdb, cmd_ctx->domain, cmd_ctx->orig_username, NULL, &groupnames); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to retrieve user info [%d]: %s\n", ret, strerror(ret))); goto done; } flags = SYSDB_SUDO_FILTER_INCLUDE_ALL | SYSDB_SUDO_FILTER_INCLUDE_DFL | SYSDB_SUDO_FILTER_ONLY_EXPIRED | SYSDB_SUDO_FILTER_USERINFO; ret = sudosrv_get_sudorules_query_cache(tmp_ctx, cmd_ctx->domain->sysdb, cmd_ctx->domain, attrs, flags, cmd_ctx->orig_username, cmd_ctx->uid, groupnames, &expired_rules, &expired_rules_num); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to retrieve expired sudo rules " "[%d]: %s\n", ret, strerror(ret))); goto done; } cmd_ctx->expired_rules_num = expired_rules_num; if (expired_rules_num > 0) { /* refresh expired rules then continue */ DEBUG(SSSDBG_TRACE_INTERNAL, ("Refreshing %d expired rules\n", expired_rules_num)); dpreq = sss_dp_get_sudoers_send(tmp_ctx, cmd_ctx->cli_ctx->rctx, cmd_ctx->domain, false, SSS_DP_SUDO_REFRESH_RULES, cmd_ctx->orig_username, expired_rules_num, expired_rules); if (dpreq == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("Cannot issue DP request.\n")); ret = EIO; goto done; } cb_ctx = talloc_zero(tmp_ctx, struct dp_callback_ctx); if (!cb_ctx) { talloc_zfree(dpreq); ret = ENOMEM; goto done; } cb_ctx->callback = sudosrv_get_sudorules_dp_callback; cb_ctx->ptr = cmd_ctx; cb_ctx->cctx = cmd_ctx->cli_ctx; cb_ctx->mem_ctx = cmd_ctx; tevent_req_set_callback(dpreq, sudosrv_dp_req_done, cb_ctx); ret = EAGAIN; } else { /* nothing is expired return what we have in the cache */ DEBUG(SSSDBG_TRACE_INTERNAL, ("About to get sudo rules from cache\n")); ret = sudosrv_get_sudorules_from_cache(cmd_ctx, cmd_ctx, &cmd_ctx->rules, &cmd_ctx->num_rules); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Failed to make a request to our cache [%d]: %s\n", ret, strerror(ret))); goto done; } } if (dpreq != NULL) { talloc_steal(cmd_ctx->cli_ctx, dpreq); } if (cb_ctx != NULL) { talloc_steal(cmd_ctx, cb_ctx); } done: talloc_free(tmp_ctx); return ret; } static void sudosrv_dp_req_done(struct tevent_req *req) { struct dp_callback_ctx *cb_ctx = tevent_req_callback_data(req, struct dp_callback_ctx); struct cli_ctx *cli_ctx; errno_t ret; dbus_uint16_t err_maj; dbus_uint32_t err_min; char *err_msg; cli_ctx = talloc_get_type(cb_ctx->cctx, struct cli_ctx); ret = sss_dp_get_sudoers_recv(cb_ctx->mem_ctx, req, &err_maj, &err_min, &err_msg); talloc_free(req); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("Fatal error, killing connection!\n")); talloc_free(cli_ctx); return; } cb_ctx->callback(err_maj, err_min, err_msg, cb_ctx->ptr); } static void sudosrv_dp_oob_req_done(struct tevent_req *req) { DEBUG(SSSDBG_TRACE_FUNC, ("Out of band refresh finished\n")); talloc_free(req); } static void sudosrv_get_sudorules_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr) { struct sudo_cmd_ctx *cmd_ctx = talloc_get_type(ptr, struct sudo_cmd_ctx); struct tevent_req *dpreq = NULL; errno_t ret; if (err_maj) { DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to get information from Data Provider\n" "Error: %u, %u, %s\n" "Will try to return what we have in cache\n", (unsigned int)err_maj, (unsigned int)err_min, err_msg)); } DEBUG(SSSDBG_TRACE_INTERNAL, ("About to get sudo rules from cache\n")); ret = sudosrv_get_sudorules_from_cache(cmd_ctx, cmd_ctx, &cmd_ctx->rules, &cmd_ctx->num_rules); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Failed to make a request to our cache [%d]: %s\n", ret, strerror(ret))); sudosrv_cmd_done(cmd_ctx, EIO); return; } if (cmd_ctx->expired_rules_num > 0 && err_min == ENOENT) { DEBUG(SSSDBG_TRACE_INTERNAL, ("Some expired rules were removed from the server, " "scheduling full refresh out of band\n")); dpreq = sss_dp_get_sudoers_send(cmd_ctx->cli_ctx->rctx, cmd_ctx->cli_ctx->rctx, cmd_ctx->domain, false, SSS_DP_SUDO_FULL_REFRESH, cmd_ctx->orig_username, 0, NULL); if (dpreq == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("Cannot issue DP request.\n")); } else { tevent_req_set_callback(dpreq, sudosrv_dp_oob_req_done, NULL); } } sudosrv_cmd_done(cmd_ctx, ret); } static errno_t sudosrv_get_sudorules_from_cache(TALLOC_CTX *mem_ctx, struct sudo_cmd_ctx *cmd_ctx, struct sysdb_attrs ***_rules, uint32_t *_num_rules) { TALLOC_CTX *tmp_ctx; errno_t ret; char **groupnames = NULL; const char *debug_name = NULL; unsigned int flags = SYSDB_SUDO_FILTER_NONE; struct sysdb_attrs **rules = NULL; uint32_t num_rules = 0; const char *attrs[] = { SYSDB_OBJECTCLASS, SYSDB_SUDO_CACHE_AT_CN, SYSDB_SUDO_CACHE_AT_USER, SYSDB_SUDO_CACHE_AT_HOST, SYSDB_SUDO_CACHE_AT_COMMAND, SYSDB_SUDO_CACHE_AT_OPTION, SYSDB_SUDO_CACHE_AT_RUNASUSER, SYSDB_SUDO_CACHE_AT_RUNASGROUP, SYSDB_SUDO_CACHE_AT_NOTBEFORE, SYSDB_SUDO_CACHE_AT_NOTAFTER, SYSDB_SUDO_CACHE_AT_ORDER, NULL }; if (cmd_ctx->domain == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("Domain is not set!\n")); return EFAULT; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_new() failed\n")); return ENOMEM; } switch (cmd_ctx->type) { case SSS_SUDO_USER: debug_name = cmd_ctx->cased_username; ret = sysdb_get_sudo_user_info(tmp_ctx, cmd_ctx->domain->sysdb, cmd_ctx->domain, cmd_ctx->orig_username, NULL, &groupnames); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to retrieve user info [%d]: %s\n", ret, strerror(ret))); goto done; } flags = SYSDB_SUDO_FILTER_USERINFO | SYSDB_SUDO_FILTER_INCLUDE_ALL; break; case SSS_SUDO_DEFAULTS: debug_name = ""; flags = SYSDB_SUDO_FILTER_INCLUDE_DFL; break; } ret = sudosrv_get_sudorules_query_cache(tmp_ctx, cmd_ctx->domain->sysdb, cmd_ctx->domain, attrs, flags, cmd_ctx->orig_username, cmd_ctx->uid, groupnames, &rules, &num_rules); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to retrieve sudo rules [%d]: %s\n", ret, strerror(ret))); goto done; } DEBUG(SSSDBG_TRACE_FUNC, ("Returning %d rules for [%s@%s]\n", num_rules, debug_name, cmd_ctx->domain->name)); if (_rules != NULL) { *_rules = talloc_steal(mem_ctx, rules); } if (_num_rules != NULL) { *_num_rules = num_rules; } ret = EOK; done: talloc_free(tmp_ctx); return ret; } static errno_t sort_sudo_rules(struct sysdb_attrs **rules, size_t count); static errno_t sudosrv_get_sudorules_query_cache(TALLOC_CTX *mem_ctx, struct sysdb_ctx *sysdb, struct sss_domain_info *domain, const char **attrs, unsigned int flags, const char *username, uid_t uid, char **groupnames, struct sysdb_attrs ***_rules, uint32_t *_count) { TALLOC_CTX *tmp_ctx; char *filter; errno_t ret; size_t count; struct sysdb_attrs **rules; struct ldb_message **msgs; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) return ENOMEM; ret = sysdb_get_sudo_filter(tmp_ctx, username, uid, groupnames, flags, &filter); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("Could not construct the search filter [%d]: %s\n", ret, strerror(ret))); goto done; } DEBUG(SSSDBG_FUNC_DATA, ("Searching sysdb with [%s]\n", filter)); if (IS_SUBDOMAIN(domain)) { /* rules are stored inside parent domain tree */ domain = domain->parent; } ret = sysdb_search_custom(tmp_ctx, sysdb, domain, filter, SUDORULE_SUBDIR, attrs, &count, &msgs); if (ret != EOK && ret != ENOENT) { DEBUG(SSSDBG_CRIT_FAILURE, ("Error looking up SUDO rules")); goto done; } if (ret == ENOENT) { *_rules = NULL; *_count = 0; ret = EOK; goto done; } ret = sysdb_msg2attrs(tmp_ctx, count, msgs, &rules); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("Could not convert ldb message to sysdb_attrs\n")); goto done; } ret = sort_sudo_rules(rules, count); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Could not sort rules by sudoOrder\n")); goto done; } *_rules = talloc_steal(mem_ctx, rules); *_count = (uint32_t)count; ret = EOK; done: talloc_free(tmp_ctx); return ret; } static int sudo_order_cmp_fn(const void *a, const void *b) { struct sysdb_attrs *r1, *r2; uint32_t o1, o2; int ret; r1 = * (struct sysdb_attrs * const *) a; r2 = * (struct sysdb_attrs * const *) b; if (!r1 || !r2) { DEBUG(SSSDBG_CRIT_FAILURE, ("BUG: Wrong data?\n")); return 0; } ret = sysdb_attrs_get_uint32_t(r1, SYSDB_SUDO_CACHE_AT_ORDER, &o1); if (ret == ENOENT) { /* man sudoers-ldap: If the sudoOrder attribute is not present, * a value of 0 is assumed */ o1 = 0; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Cannot get sudoOrder value\n")); return 0; } ret = sysdb_attrs_get_uint32_t(r2, SYSDB_SUDO_CACHE_AT_ORDER, &o2); if (ret == ENOENT) { /* man sudoers-ldap: If the sudoOrder attribute is not present, * a value of 0 is assumed */ o2 = 0; } else if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Cannot get sudoOrder value\n")); return 0; } if (o1 > o2) { return 1; } else if (o1 < o2) { return -1; } return 0; } static errno_t sort_sudo_rules(struct sysdb_attrs **rules, size_t count) { qsort(rules, count, sizeof(struct sysdb_attrs *), sudo_order_cmp_fn); return EOK; }