/* Authors: Pavel Březina 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 "providers/dp_backend.h" #include "providers/ldap/ldap_common.h" #include "providers/ldap/sdap.h" #include "providers/ldap/sdap_async.h" #include "providers/ldap/sdap_sudo.h" #include "providers/ldap/sdap_sudo_cache.h" #include "providers/ldap/sdap_sudo_timer.h" #include "db/sysdb_sudo.h" static void sdap_sudo_shutdown(struct be_req *req) { sdap_handler_done(req, DP_ERR_OK, EOK, NULL); } struct bet_ops sdap_sudo_ops = { .handler = sdap_sudo_handler, .finalize = sdap_sudo_shutdown }; int sdap_sudo_setup_tasks(struct sdap_id_ctx *id_ctx); int sdap_sudo_init(struct be_ctx *be_ctx, struct sdap_id_ctx *id_ctx, struct bet_ops **ops, void **pvt_data) { int ret; DEBUG(SSSDBG_TRACE_INTERNAL, ("Initializing sudo LDAP back end\n")); *ops = &sdap_sudo_ops; *pvt_data = id_ctx; ret = ldap_get_sudo_options(id_ctx, be_ctx->cdb, be_ctx->conf_path, id_ctx->opts); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Cannot get SUDO options [%d]: %s\n", ret, strerror(ret))); return ret; } ret = sdap_sudo_setup_tasks(id_ctx); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("SUDO setup failed [%d]: %s\n", ret, strerror(ret))); return ret; } return EOK; } int sdap_sudo_setup_tasks(struct sdap_id_ctx *id_ctx) { struct sdap_sudo_refresh_ctx *refresh_ctx = NULL; struct timeval tv; int ret = EOK; bool refreshed = false; bool refresh_enabled = dp_opt_get_bool(id_ctx->opts->basic, SDAP_SUDO_REFRESH_ENABLED); /* set up periodical update of sudo rules */ if (refresh_enabled) { refresh_ctx = sdap_sudo_refresh_ctx_init(id_ctx, id_ctx->be, id_ctx, id_ctx->opts, tevent_timeval_zero()); if (refresh_ctx == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("sdap_sudo_refresh_ctx_init() failed!\n")); return ENOMEM; } /* If this is the first startup, we need to kick off * an refresh immediately, to close a window where * clients requesting sudo information won't get an * immediate reply with no entries */ ret = sysdb_sudo_get_refreshed(id_ctx->be->sysdb, &refreshed); if (ret != EOK) { return ret; } if (refreshed) { /* At least one update has previously run, * so clients will get cached data. We will delay * starting to enumerate by 10s so we don't slow * down the startup process if this is happening * during system boot. */ tv = tevent_timeval_current_ofs(10, 0); DEBUG(SSSDBG_FUNC_DATA, ("Delaying first refresh of SUDO rules " "for 10 seconds\n")); } else { /* This is our first startup. Schedule the * update to start immediately once we * enter the mainloop. */ tv = tevent_timeval_current(); } ret = sdap_sudo_refresh_set_timer(refresh_ctx, tv); if (ret != EOK) { talloc_free(refresh_ctx); return ret; } } return EOK; } struct sdap_sudo_load_sudoers_state { struct tevent_context *ev; struct sdap_sudo_ctx *sudo_ctx; struct sdap_options *opts; struct sdap_handle *sh; struct sysdb_attrs **ldap_rules; /* search result will be stored here */ size_t ldap_rules_count; /* search result will be stored here */ const char **attrs; const char *filter; size_t base_iter; struct sdap_search_base **search_bases; int timeout; }; struct sdap_sudo_refresh_state { struct be_ctx *be_ctx; struct be_sudo_req *sudo_req; struct sdap_options *opts; struct sdap_id_op *sdap_op; struct sdap_id_conn_cache *sdap_conn_cache; int dp_error; int error; }; static int sdap_sudo_connect(struct tevent_req *req); static void sdap_sudo_connect_done(struct tevent_req *subreq); static struct tevent_req * sdap_sudo_load_sudoers_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct be_sudo_req *sudo_req, struct sdap_options *opts, struct sdap_handle *sh); static errno_t sdap_sudo_load_sudoers_next_base(struct tevent_req *req); static void sdap_sudo_load_sudoers_process(struct tevent_req *subreq); static int sdap_sudo_load_sudoers_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, size_t *rules_count, struct sysdb_attrs ***rules); static void sdap_sudo_load_sudoers_done(struct tevent_req *req); static int sdap_sudo_purge_sudoers(struct sysdb_ctx *sysdb_ctx, struct sss_domain_info *domain, struct be_sudo_req *sudo_req); static int sdap_sudo_store_sudoers(struct sysdb_ctx *sysdb_ctx, struct sdap_options *opts, size_t rules_count, struct sysdb_attrs **rules); static const char *sdap_sudo_build_filter(TALLOC_CTX *mem_ctx, struct sdap_attr_map *map, struct be_sudo_req *sudo_req); static const char *sdap_sudo_build_user_filter(TALLOC_CTX *mem_ctx, struct sdap_attr_map *map, const char *username, uid_t uid, char **groups); static void sdap_sudo_reply(struct tevent_req *req) { struct be_req *be_req = NULL; int dp_error; int error; int ret; be_req = tevent_req_callback_data(req, struct be_req); ret = sdap_sudo_refresh_recv(req, &dp_error, &error); talloc_zfree(req); if (ret != EOK) { sdap_handler_done(be_req, DP_ERR_FATAL, ret, strerror(ret)); return; } sdap_handler_done(be_req, dp_error, error, strerror(error)); } void sdap_sudo_handler(struct be_req *be_req) { struct tevent_req *req = NULL; struct be_sudo_req *sudo_req = NULL; struct sdap_id_ctx *id_ctx = NULL; int ret = EOK; id_ctx = talloc_get_type(be_req->be_ctx->bet_info[BET_SUDO].pvt_bet_data, struct sdap_id_ctx); sudo_req = talloc_get_type(be_req->req_data, struct be_sudo_req); /* get user info */ if (sudo_req->username != NULL) { ret = sysdb_get_sudo_user_info(sudo_req, sudo_req->username, id_ctx->be->sysdb, &sudo_req->uid, &sudo_req->groups); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to get uid and groups of %s\n", sudo_req->username)); goto fail; } } else { sudo_req->uid = 0; sudo_req->groups = NULL; } req = sdap_sudo_refresh_send(be_req, id_ctx->be, sudo_req, id_ctx->opts, id_ctx->conn_cache); if (req == NULL) { ret = ENOMEM; goto fail; } tevent_req_set_callback(req, sdap_sudo_reply, be_req); return; fail: sdap_handler_done(be_req, DP_ERR_FATAL, ret, NULL); } struct tevent_req *sdap_sudo_refresh_send(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx, struct be_sudo_req *sudo_req, struct sdap_options *opts, struct sdap_id_conn_cache *conn_cache) { struct tevent_req *req = NULL; struct sdap_sudo_refresh_state *state = NULL; int ret; req = tevent_req_create(mem_ctx, &state, struct sdap_sudo_refresh_state); if (!req) { return NULL; } state->be_ctx = be_ctx; state->sudo_req = sudo_req; state->opts = opts; state->sdap_op = NULL; state->sdap_conn_cache = conn_cache; state->dp_error = DP_ERR_OK; state->error = EOK; switch (sudo_req->type) { case BE_REQ_SUDO_ALL: DEBUG(SSSDBG_TRACE_FUNC, ("Requested refresh for: \n")); break; case BE_REQ_SUDO_DEFAULTS: DEBUG(SSSDBG_TRACE_FUNC, ("Requested refresh of cn=defaults\n")); break; case BE_REQ_SUDO_USER: DEBUG(SSSDBG_TRACE_FUNC, ("Requested refresh for: %s\n", sudo_req->username)); break; default: DEBUG(SSSDBG_CRIT_FAILURE, ("Invalid request type %d\n", sudo_req->type)); ret = EINVAL; goto immediately; } ret = sdap_sudo_connect(req); if (ret == EAGAIN) { /* asynchronous processing */ return req; } immediately: if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } tevent_req_post(req, be_ctx->ev); return req; } int sdap_sudo_refresh_recv(struct tevent_req *req, int *dp_error, int *error) { struct sdap_sudo_refresh_state *state = NULL; state = tevent_req_data(req, struct sdap_sudo_refresh_state); TEVENT_REQ_RETURN_ON_ERROR(req); *dp_error = state->dp_error; *error = state->error; return EOK; } int sdap_sudo_connect(struct tevent_req *req) { struct tevent_req *subreq = NULL; struct sdap_sudo_refresh_state *state = NULL; int ret; state = tevent_req_data(req, struct sdap_sudo_refresh_state); if (be_is_offline(state->be_ctx)) { state->dp_error = DP_ERR_OFFLINE; state->error = EAGAIN; return EOK; } if (state->sdap_op == NULL) { state->sdap_op = sdap_id_op_create(state, state->sdap_conn_cache); if (state->sdap_op == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("sdap_id_op_create() failed\n")); state->dp_error = DP_ERR_FATAL; state->error = EIO; return EIO; } } subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret); if (subreq == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("sdap_id_op_connect_send() failed: %d(%s)\n", ret, strerror(ret))); talloc_zfree(state->sdap_op); state->dp_error = DP_ERR_FATAL; state->error = ret; return ret; } tevent_req_set_callback(subreq, sdap_sudo_connect_done, req); return EAGAIN; } void sdap_sudo_connect_done(struct tevent_req *subreq) { struct tevent_req *req = NULL; /* req from sdap_sudo_refresh_send() */ struct sdap_sudo_refresh_state *state = NULL; int dp_error; int ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_sudo_refresh_state); ret = sdap_id_op_connect_recv(subreq, &dp_error); talloc_zfree(subreq); if (dp_error == DP_ERR_OFFLINE) { talloc_zfree(state->sdap_op); state->dp_error = DP_ERR_OFFLINE; state->error = EAGAIN; tevent_req_done(req); return; } else if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("SUDO LDAP connection failed - %s\n", strerror(ret))); goto fail; } DEBUG(SSSDBG_TRACE_FUNC, ("SUDO LDAP connection successful\n")); subreq = sdap_sudo_load_sudoers_send(state, state->be_ctx->ev, state->sudo_req, state->opts, sdap_id_op_handle(state->sdap_op)); if (subreq == NULL) { ret = EFAULT; goto fail; } tevent_req_set_callback(subreq, sdap_sudo_load_sudoers_done, req); return; fail: state->dp_error = DP_ERR_FATAL; state->error = ret; tevent_req_error(req, ret); } struct tevent_req * sdap_sudo_load_sudoers_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct be_sudo_req *sudo_req, struct sdap_options *opts, struct sdap_handle *sh) { struct tevent_req *req = NULL; struct sdap_sudo_load_sudoers_state *state = NULL; int ret; req = tevent_req_create(mem_ctx, &state, struct sdap_sudo_load_sudoers_state); if (!req) { return NULL; } state->ev = ev; state->opts = opts; state->sh = sh; state->base_iter = 0; state->search_bases = opts->sudo_search_bases; state->timeout = dp_opt_get_int(opts->basic, SDAP_SEARCH_TIMEOUT); state->ldap_rules = NULL; state->ldap_rules_count = 0; if (!state->search_bases) { DEBUG(SSSDBG_CRIT_FAILURE, ("SUDOERS lookup request without a search base\n")); ret = EINVAL; goto done; } /* create filter */ state->filter = sdap_sudo_build_filter(state, opts->sudorule_map, sudo_req); if (state->filter == NULL) { goto fail; } /* create attrs from map */ ret = build_attrs_from_map(state, opts->sudorule_map, SDAP_OPTS_SUDO, &state->attrs); if (ret != EOK) { goto fail; } /* begin search */ ret = sdap_sudo_load_sudoers_next_base(req); done: if (ret != EOK) { tevent_req_error(req, ret); tevent_req_post(req, ev); } return req; fail: talloc_zfree(req); return NULL; } static errno_t sdap_sudo_load_sudoers_next_base(struct tevent_req *req) { struct tevent_req *subreq = NULL; struct sdap_sudo_load_sudoers_state *state = NULL; struct sdap_search_base *search_base = NULL; char *filter = NULL; state = tevent_req_data(req, struct sdap_sudo_load_sudoers_state); search_base = state->search_bases[state->base_iter]; if (search_base == NULL) { /* should not happen */ DEBUG(SSSDBG_CRIT_FAILURE, ("search_base is null\n")); return EFAULT; } /* create filter */ filter = sdap_get_id_specific_filter(state, state->filter, search_base->filter); if (filter == NULL) { return ENOMEM; } /* send request */ DEBUG(SSSDBG_TRACE_FUNC, ("Searching for sudo rules with base [%s]\n", search_base->basedn)); subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh, search_base->basedn, search_base->scope, filter, state->attrs, state->opts->sudorule_map, SDAP_OPTS_SUDO, state->timeout, true); if (subreq == NULL) { return ENOMEM; } tevent_req_set_callback(subreq, sdap_sudo_load_sudoers_process, req); return EOK; } static void sdap_sudo_load_sudoers_process(struct tevent_req *subreq) { struct tevent_req *req = NULL; struct sdap_sudo_load_sudoers_state *state = NULL; struct sdap_search_base *search_base = NULL; struct sysdb_attrs **attrs = NULL; size_t count; int ret; int i; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_sudo_load_sudoers_state); search_base = state->search_bases[state->base_iter]; DEBUG(SSSDBG_TRACE_FUNC, ("Receiving sudo rules with base [%s]\n", search_base->basedn)); ret = sdap_get_generic_recv(subreq, state, &count, &attrs); talloc_zfree(subreq); if (ret) { tevent_req_error(req, ret); return; } /* add rules to result */ if (count > 0) { state->ldap_rules = talloc_realloc(state, state->ldap_rules, struct sysdb_attrs *, state->ldap_rules_count + count); if (state->ldap_rules == NULL) { tevent_req_error(req, ENOMEM); return; } for (i = 0; i < count; i++) { state->ldap_rules[state->ldap_rules_count + i] = talloc_steal( state->ldap_rules, attrs[i]); } state->ldap_rules_count += count; } /* go to next base */ state->base_iter++; if (state->search_bases[state->base_iter]) { ret = sdap_sudo_load_sudoers_next_base(req); if (ret != EOK) { tevent_req_error(req, ret); } return; } /* we are done */ tevent_req_done(req); } int sdap_sudo_load_sudoers_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, size_t *rules_count, struct sysdb_attrs ***rules) { struct sdap_sudo_load_sudoers_state *state = NULL; state = tevent_req_data(req, struct sdap_sudo_load_sudoers_state); TEVENT_REQ_RETURN_ON_ERROR(req); *rules_count = state->ldap_rules_count; *rules = talloc_steal(mem_ctx, state->ldap_rules); return EOK; } void sdap_sudo_load_sudoers_done(struct tevent_req *subreq) { struct tevent_req *req = NULL; /* req from sdap_sudo_refresh_send() */ struct sdap_sudo_refresh_state *state = NULL; struct sysdb_attrs **rules = NULL; size_t rules_count; int ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_sudo_refresh_state); ret = sdap_sudo_load_sudoers_recv(subreq, state, &rules_count, &rules); talloc_zfree(subreq); if (ret != EOK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, ("Received %d rules\n", rules_count)); /* purge cache */ ret = sdap_sudo_purge_sudoers(state->be_ctx->sysdb, state->be_ctx->domain, state->sudo_req); if (ret != EOK) { goto done; } /* store rules */ ret = sdap_sudo_store_sudoers(state->be_ctx->sysdb, state->opts, rules_count, rules); if (ret != EOK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, ("Sudoers is successfuly stored in cache\n")); ret = EOK; done: state->error = ret; if (ret == EOK) { state->dp_error = DP_ERR_OK; tevent_req_done(req); } else { state->dp_error = DP_ERR_FATAL; tevent_req_error(req, ret); } } int sdap_sudo_purge_sudoers(struct sysdb_ctx *sysdb_ctx, struct sss_domain_info *domain, struct be_sudo_req *sudo_req) { TALLOC_CTX *tmp_ctx; char *filter = NULL; char **sudouser = NULL; int ret = EOK; errno_t sret; bool in_transaction = false; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_new() failed\n")); return ENOMEM; } ret = sysdb_transaction_start(sysdb_ctx); if (ret != EOK) { goto done; } in_transaction = true; switch (sudo_req->type) { case BE_REQ_SUDO_ALL: DEBUG(SSSDBG_TRACE_FUNC, ("Purging SUDOers cache of all rules\n")); ret = sysdb_sudo_purge_all(sysdb_ctx); break; case BE_REQ_SUDO_DEFAULTS: DEBUG(SSSDBG_TRACE_FUNC, ("Purging SUDOers cache of default options\n")); ret = sysdb_sudo_purge_byname(sysdb_ctx, SDAP_SUDO_DEFAULTS); break; case BE_REQ_SUDO_USER: DEBUG(SSSDBG_TRACE_FUNC, ("Purging SUDOers cache of user's [%s] rules\n", sudo_req->username)); /* netgroups */ ret = sysdb_get_sudo_filter(tmp_ctx, NULL, 0, NULL, SYSDB_SUDO_FILTER_NGRS, &filter); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to create filter to purge " "SUDOers cache [%d]: %s\n", ret, strerror(ret))); goto done; } ret = sysdb_sudo_purge_byfilter(sysdb_ctx, filter); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to purge SUDOers cache " "(netgroups) [%d]: %s\n", ret, strerror(ret))); goto done; } /* user, uid, groups */ sudouser = sysdb_sudo_build_sudouser(tmp_ctx, sudo_req->username, sudo_req->uid, sudo_req->groups, true); if (sudouser == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to create sudoUser to purge " "SUDOers cache [%d]: %s\n", ret, strerror(ret))); goto done; } ret = sysdb_sudo_purge_bysudouser(sysdb_ctx, sudouser); break; default: DEBUG(SSSDBG_CRIT_FAILURE, ("Invalid request type %d\n", sudo_req->type)); return EINVAL; } if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to purge SUDOers cache [%d]: %s\n", ret, strerror(ret))); goto done; } ret = sysdb_transaction_commit(sysdb_ctx); if (ret == EOK) { in_transaction = false; } done: if (in_transaction) { sret = sysdb_transaction_cancel(sysdb_ctx); if (sret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Could not cancel transaction\n")); } } talloc_free(tmp_ctx); return ret; } int sdap_sudo_store_sudoers(struct sysdb_ctx *sysdb_ctx, struct sdap_options *opts, size_t rules_count, struct sysdb_attrs **rules) { errno_t ret; /* Empty sudoers? Done. */ if (rules_count == 0 || rules == NULL) { return EOK; } ret = sdap_save_native_sudorule_list(sysdb_ctx, opts->sudorule_map, rules, rules_count); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("failed to save sudo rules [%d]: %s\n", ret, strerror(ret))); return ret; } return EOK; } const char *sdap_sudo_build_filter(TALLOC_CTX *mem_ctx, struct sdap_attr_map *map, struct be_sudo_req *sudo_req) { switch (sudo_req->type) { case BE_REQ_SUDO_ALL: return talloc_asprintf(mem_ctx, SDAP_SUDO_FILTER_ALL, map[SDAP_OC_SUDORULE].name); break; case BE_REQ_SUDO_DEFAULTS: return talloc_asprintf(mem_ctx, SDAP_SUDO_FILTER_DEFAULTS, map[SDAP_OC_SUDORULE].name, map[SDAP_AT_SUDO_NAME].name, SDAP_SUDO_DEFAULTS); /* FIXME: add option for this */ break; case BE_REQ_SUDO_USER: return sdap_sudo_build_user_filter(mem_ctx, map, sudo_req->username, sudo_req->uid, sudo_req->groups); break; default: DEBUG(SSSDBG_CRIT_FAILURE, ("Invalid request type %d\n", sudo_req->type)); return NULL; } } /* alway update cn=defaults and sudoUser=ALL */ const char *sdap_sudo_build_user_filter(TALLOC_CTX *mem_ctx, struct sdap_attr_map *map, const char *username, uid_t uid, char **groups) { char *filter = NULL; char *output = NULL; char *sanitized = NULL; char **group = NULL; int ret; /* user name */ ret = sss_filter_sanitize(filter, username, &sanitized); if (ret != EOK) { goto fail; } filter = talloc_asprintf_append(filter, SDAP_SUDO_FILTER_USERNAME, map[SDAP_AT_SUDO_USER].name, sanitized); if (filter == NULL) { goto fail; } /* user uid */ filter = talloc_asprintf_append(filter, SDAP_SUDO_FILTER_UID, map[SDAP_AT_SUDO_USER].name, uid); if (filter == NULL) { goto fail; } /* groups */ if (groups != NULL) { for (group = groups; *group != NULL; group++) { ret = sss_filter_sanitize(filter, *group, &sanitized); if (ret != EOK) { goto fail; } filter = talloc_asprintf_append(filter, SDAP_SUDO_FILTER_GROUP, map[SDAP_AT_SUDO_USER].name, sanitized); if (filter == NULL) { goto fail; } } } /* netgroups */ /* * FIXME: load only netgroups user is member of * FIXME: add option to disable this filter */ filter = talloc_asprintf_append(filter, SDAP_SUDO_FILTER_NETGROUP, map[SDAP_AT_SUDO_USER].name, "*"); if (filter == NULL) { goto fail; } output = talloc_asprintf(mem_ctx, SDAP_SUDO_FILTER_USER, map[SDAP_OC_SUDORULE].name, map[SDAP_AT_SUDO_NAME].name, SDAP_SUDO_DEFAULTS, /* FIXME: add option for this */ map[SDAP_AT_SUDO_USER].name, filter); talloc_free(filter); return output; fail: talloc_free(filter); return NULL; }