diff options
Diffstat (limited to '')
-rw-r--r-- | src/responder/sudo/sudosrv.c | 223 | ||||
-rw-r--r-- | src/responder/sudo/sudosrv_cmd.c | 303 | ||||
-rw-r--r-- | src/responder/sudo/sudosrv_dp.c | 265 | ||||
-rw-r--r-- | src/responder/sudo/sudosrv_get_sudorules.c | 887 | ||||
-rw-r--r-- | src/responder/sudo/sudosrv_private.h | 112 | ||||
-rw-r--r-- | src/responder/sudo/sudosrv_query.c | 307 |
6 files changed, 2097 insertions, 0 deletions
diff --git a/src/responder/sudo/sudosrv.c b/src/responder/sudo/sudosrv.c new file mode 100644 index 0000000..8568e6e --- /dev/null +++ b/src/responder/sudo/sudosrv.c @@ -0,0 +1,223 @@ +/* + Authors: + Pavel Březina <pbrezina@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 <popt.h> + +#include "util/util.h" +#include "confdb/confdb.h" +#include "responder/common/responder.h" +#include "responder/sudo/sudosrv_private.h" +#include "providers/data_provider.h" +#include "responder/common/negcache.h" +#include "sss_iface/sss_iface_async.h" + +int sudo_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb, + int pipe_fd) +{ + struct resp_ctx *rctx; + struct sss_cmd_table *sudo_cmds; + struct sudo_ctx *sudo_ctx; + int ret; + + sudo_cmds = get_sudo_cmds(); + ret = sss_process_init(mem_ctx, ev, cdb, + sudo_cmds, + SSS_SUDO_SOCKET_NAME, pipe_fd, /* custom permissions on socket */ + NULL, -1, /* No private socket */ + CONFDB_SUDO_CONF_ENTRY, + SSS_BUS_SUDO, SSS_SUDO_SBUS_SERVICE_NAME, + sss_connection_setup, + &rctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "sss_process_init() failed\n"); + return ret; + } + + sudo_ctx = talloc_zero(rctx, struct sudo_ctx); + if (!sudo_ctx) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing sudo_ctx\n"); + ret = ENOMEM; + goto fail; + } + + sudo_ctx->rctx = rctx; + sudo_ctx->rctx->pvt_ctx = sudo_ctx; + + sss_ncache_prepopulate(sudo_ctx->rctx->ncache, sudo_ctx->rctx->cdb, rctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "failed to set ncache for sudo's filter_users\n"); + goto fail; + } + + /* Get sudo_timed option */ + ret = confdb_get_bool(sudo_ctx->rctx->cdb, + CONFDB_SUDO_CONF_ENTRY, CONFDB_SUDO_TIMED, + CONFDB_DEFAULT_SUDO_TIMED, + &sudo_ctx->timed); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto fail; + } + + /* Get sudo_inverse_order option */ + ret = confdb_get_bool(sudo_ctx->rctx->cdb, + CONFDB_SUDO_CONF_ENTRY, CONFDB_SUDO_INVERSE_ORDER, + CONFDB_DEFAULT_SUDO_INVERSE_ORDER, + &sudo_ctx->inverse_order); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto fail; + } + + /* Get sudo_inverse_order option */ + ret = confdb_get_int(sudo_ctx->rctx->cdb, + CONFDB_SUDO_CONF_ENTRY, CONFDB_SUDO_THRESHOLD, + CONFDB_DEFAULT_SUDO_THRESHOLD, + &sudo_ctx->threshold); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto fail; + } + + ret = schedule_get_domains_task(rctx, rctx->ev, rctx, NULL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "schedule_get_domains_tasks failed.\n"); + goto fail; + } + + /* The responder is initialized. Now tell it to the monitor. */ + ret = sss_monitor_service_init(rctx, rctx->ev, SSS_BUS_SUDO, + SSS_SUDO_SBUS_SERVICE_NAME, + SSS_SUDO_SBUS_SERVICE_VERSION, + MT_SVC_SERVICE, + &rctx->last_request_time, &rctx->mon_conn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up message bus\n"); + goto fail; + } + + ret = sss_resp_register_service_iface(rctx); + if (ret != EOK) { + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "SUDO Initialization complete\n"); + + return EOK; + +fail: + talloc_free(rctx); + return ret; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *opt_logger = NULL; + struct main_context *main_ctx; + int ret; + int pipe_fd = -1; + uid_t uid = 0; + gid_t gid = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_LOGGER_OPTS + SSSD_SERVER_OPTS(uid, gid) + SSSD_RESPONDER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + umask(DFL_RSP_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc. */ + debug_log_file = "sssd_sudo"; + DEBUG_INIT(debug_level, opt_logger); + + if (!is_socket_activated()) { + /* Create pipe file descriptors here with right ownerschip */ + ret = create_pipe_fd(SSS_SUDO_SOCKET_NAME, &pipe_fd, SSS_DFL_UMASK); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "create_pipe_fd failed [%d]: %s.\n", + ret, sss_strerror(ret)); + return 4; + } + + ret = chown(SSS_SUDO_SOCKET_NAME, uid, 0); + if (ret != 0) { + ret = errno; + close(pipe_fd); + DEBUG(SSSDBG_FATAL_FAILURE, + "create_pipe_fd failed [%d]: %s.\n", + ret, sss_strerror(ret)); + return 5; + } + } + + ret = server_setup("sudo", true, 0, uid, gid, CONFDB_SUDO_CONF_ENTRY, + &main_ctx, true); + if (ret != EOK) { + return 2; + } + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_OP_FAILURE, "Could not set up to exit " + "when parent process does\n"); + } + + ret = sudo_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx, pipe_fd); + if (ret != EOK) { + return 3; + } + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} diff --git a/src/responder/sudo/sudosrv_cmd.c b/src/responder/sudo/sudosrv_cmd.c new file mode 100644 index 0000000..63b548f --- /dev/null +++ b/src/responder/sudo/sudosrv_cmd.c @@ -0,0 +1,303 @@ +/* + Authors: + Pavel Březina <pbrezina@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 <stdint.h> +#include <errno.h> +#include <talloc.h> + +#include "util/util.h" +#include "responder/common/responder.h" +#include "responder/common/responder_packet.h" +#include "responder/sudo/sudosrv_private.h" +#include "db/sysdb_sudo.h" +#include "sss_client/sss_cli.h" +#include "responder/common/negcache.h" + +static errno_t sudosrv_cmd_send_reply(struct sudo_cmd_ctx *cmd_ctx, + uint8_t *response_body, + size_t response_len) +{ + errno_t ret; + uint8_t *packet_body = NULL; + size_t packet_len = 0; + struct cli_ctx *cli_ctx = cmd_ctx->cli_ctx; + struct cli_protocol *pctx; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + ret = sss_packet_new(pctx->creq, 0, + sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to create a new packet [%d]; %s\n", + ret, strerror(ret)); + goto done; + } + + ret = sss_packet_grow(pctx->creq->out, response_len); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to create response: %s\n", strerror(ret)); + goto done; + } + sss_packet_get_body(pctx->creq->out, &packet_body, &packet_len); + memcpy(packet_body, response_body, response_len); + + sss_packet_set_error(pctx->creq->out, EOK); + sss_cmd_done(cmd_ctx->cli_ctx, cmd_ctx); + + ret = EOK; + +done: + talloc_zfree(tmp_ctx); + return ret; +} + +static errno_t sudosrv_cmd_send_error(TALLOC_CTX *mem_ctx, + struct sudo_cmd_ctx *cmd_ctx, + uint32_t error) +{ + uint8_t *response_body = NULL; + size_t response_len = 0; + int ret = EOK; + + if (error == EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Everything is fine but we are " + "returning error?\n"); + return EFAULT; + } + + ret = sudosrv_build_response(mem_ctx, error, 0, NULL, + &response_body, &response_len); + if (ret != EOK) { + return ret; + } + + return sudosrv_cmd_send_reply(cmd_ctx, response_body, response_len); +} + +errno_t sudosrv_cmd_reply(struct sudo_cmd_ctx *cmd_ctx, int ret) +{ + uint8_t *response_body = NULL; + size_t response_len = 0; + uint32_t num_rules = cmd_ctx->num_rules; + struct sysdb_attrs **rules = cmd_ctx->rules; + + switch (ret) { + case EOK: + /* + * Parent of cmd_ctx->rules is in-memory cache, we must not talloc_free it! + */ + if (cmd_ctx->sudo_ctx->timed) { + /* filter rules by time */ + + DEBUG(SSSDBG_TRACE_FUNC, "Applying time restrictions on" + "%u rules\n", cmd_ctx->num_rules); + + ret = sysdb_sudo_filter_rules_by_time(cmd_ctx, cmd_ctx->num_rules, + cmd_ctx->rules, 0, + &num_rules, &rules); + if (ret != EOK) { + return EFAULT; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Got %u rules after time filter\n", + num_rules); + } + + /* send result */ + ret = sudosrv_build_response(cmd_ctx, SSS_SUDO_ERROR_OK, + num_rules, rules, + &response_body, &response_len); + if (ret != EOK) { + return EFAULT; + } + + ret = sudosrv_cmd_send_reply(cmd_ctx, response_body, response_len); + break; + + case EAGAIN: + /* async processing, just return here */ + return EOK; + + case EFAULT: + /* very bad error */ + return EFAULT; + + + /* case ENOENT: + * - means user not found + * - send error ENOENT + */ + + default: + /* send error */ + ret = sudosrv_cmd_send_error(cmd_ctx, cmd_ctx, ret); + break; + } + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Fatal error, killing connection!\n"); + talloc_free(cmd_ctx->cli_ctx); + return EFAULT; + } + + return EOK; +} + +static void sudosrv_cmd_done(struct tevent_req *req); + +static int sudosrv_cmd(enum sss_sudo_type type, struct cli_ctx *cli_ctx) +{ + struct tevent_req *req = NULL; + struct sudo_cmd_ctx *cmd_ctx = NULL; + uint8_t *query_body = NULL; + size_t query_len = 0; + struct cli_protocol *pctx; + uint32_t protocol; + errno_t ret; + + /* create cmd_ctx */ + + cmd_ctx = talloc_zero(cli_ctx, struct sudo_cmd_ctx); + if (cmd_ctx == NULL) { + /* kill the connection here as we have no context for reply */ + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory?\n"); + return ENOMEM; + } + + cmd_ctx->cli_ctx = cli_ctx; + cmd_ctx->type = type; + cmd_ctx->sudo_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sudo_ctx); + if (cmd_ctx->sudo_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "sudo_ctx not set, killing connection!\n"); + return EFAULT; + } + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + protocol = pctx->cli_protocol_version->version; + + /* if protocol is invalid return */ + switch (protocol) { + case 0: + DEBUG(SSSDBG_FATAL_FAILURE, "Protocol [%d] is not secure. " + "SSSD does not allow to use this protocol.\n", protocol); + ret = EFAULT; + goto done; + break; + case SSS_SUDO_PROTOCOL_VERSION: + DEBUG(SSSDBG_TRACE_INTERNAL, "Using protocol version [%d]\n", + protocol); + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, "Invalid protocol version [%d]!\n", + protocol); + ret = EFAULT; + goto done; + } + + /* parse query */ + sss_packet_get_body(pctx->creq->in, &query_body, &query_len); + if (query_len <= 0 || query_body == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Query is empty\n"); + ret = EINVAL; + goto done; + } + + ret = sudosrv_parse_query(cmd_ctx, query_body, query_len, + &cmd_ctx->rawname, &cmd_ctx->uid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse sudo query [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + req = sudosrv_get_rules_send(cmd_ctx, cli_ctx->ev, cmd_ctx->sudo_ctx, + cmd_ctx->type, cmd_ctx->uid, + cmd_ctx->rawname); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, sudosrv_cmd_done, cmd_ctx); + + ret = EAGAIN; + +done: + return sudosrv_cmd_reply(cmd_ctx, ret); +} + +static void sudosrv_cmd_done(struct tevent_req *req) +{ + struct sudo_cmd_ctx *cmd_ctx; + errno_t ret; + + cmd_ctx = tevent_req_callback_data(req, struct sudo_cmd_ctx); + + ret = sudosrv_get_rules_recv(cmd_ctx, req, &cmd_ctx->rules, + &cmd_ctx->num_rules); + talloc_zfree(req); + if (ret != EOK) { + DEBUG((ret == ENOENT) ? SSSDBG_MINOR_FAILURE : SSSDBG_OP_FAILURE, + "Unable to obtain cached rules [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + sudosrv_cmd_reply(cmd_ctx, ret); +} + +static int sudosrv_cmd_get_sudorules(struct cli_ctx *cli_ctx) +{ + return sudosrv_cmd(SSS_SUDO_USER, cli_ctx); +} + +static int sudosrv_cmd_get_defaults(struct cli_ctx *cli_ctx) +{ + return sudosrv_cmd(SSS_SUDO_DEFAULTS, cli_ctx); +} + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version sudo_cli_protocol_version[] = { + {1, "2012-05-14", "require uid and domain"}, + {0, NULL, NULL} + }; + + return sudo_cli_protocol_version; +} + +struct sss_cmd_table *get_sudo_cmds(void) { + static struct sss_cmd_table sudo_cmds[] = { + {SSS_GET_VERSION, sss_cmd_get_version}, + {SSS_SUDO_GET_SUDORULES, sudosrv_cmd_get_sudorules}, + {SSS_SUDO_GET_DEFAULTS, sudosrv_cmd_get_defaults}, + {SSS_CLI_NULL, NULL} + }; + + return sudo_cmds; +} diff --git a/src/responder/sudo/sudosrv_dp.c b/src/responder/sudo/sudosrv_dp.c new file mode 100644 index 0000000..465c0f2 --- /dev/null +++ b/src/responder/sudo/sudosrv_dp.c @@ -0,0 +1,265 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + Jakub Hrozek <jhrozek@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 <talloc.h> +#include <tevent.h> +#include <dbus/dbus.h> + +#include "util/util.h" +#include "providers/data_provider.h" +#include "providers/data_provider_req.h" +#include "responder/common/responder.h" +#include "responder/sudo/sudosrv_private.h" +#include "db/sysdb.h" +#include "sss_iface/sss_iface_async.h" + +static DBusMessage * +sss_dp_get_sudoers_msg(TALLOC_CTX *mem_ctx, + const char *bus_name, + struct sss_domain_info *dom, + bool fast_reply, + enum sss_dp_sudo_type type, + const char *name, + uint32_t num_rules, + struct sysdb_attrs **rules) +{ + DBusMessage *msg; + DBusMessageIter iter; + DBusMessageIter array_iter; + dbus_bool_t dbret; + errno_t ret; + uint32_t be_type = 0; + uint32_t dp_flags = 0; + const char *rule_name = NULL; + uint32_t i; + + switch (type) { + case SSS_DP_SUDO_REFRESH_RULES: + be_type = BE_REQ_SUDO_RULES; + break; + case SSS_DP_SUDO_FULL_REFRESH: + be_type = BE_REQ_SUDO_FULL; + break; + } + + if (fast_reply) { + dp_flags |= DP_FAST_REPLY; + } + + msg = dbus_message_new_method_call(bus_name, + SSS_BUS_PATH, + "sssd.dataprovider", + "sudoHandler"); + if (msg == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory?!\n"); + return NULL; + } + + /* create the message */ + DEBUG(SSSDBG_TRACE_FUNC, + "Creating SUDOers request for [%s][%u][%s][%u]\n", + dom->name, be_type, name, num_rules); + + dbus_message_iter_init_append(msg, &iter); + + dbret = dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &dp_flags); + if (dbret == FALSE) { + goto fail; + } + + /* BE TYPE */ + dbret = dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &be_type); + if (dbret == FALSE) { + goto fail; + } + + /* BE TYPE SPECIFIC */ + if (be_type & BE_REQ_SUDO_RULES) { + dbret = dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, + &num_rules); + if (dbret == FALSE) { + goto fail; + } + + dbret = dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, + &array_iter); + if (dbret == FALSE) { + goto fail; + } + + for (i = 0; i < num_rules; i++) { + ret = sysdb_attrs_get_string(rules[i], SYSDB_NAME, &rule_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not get rule name [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + dbret = dbus_message_iter_append_basic(&array_iter, + DBUS_TYPE_STRING, + &rule_name); + if (dbret == FALSE) { + goto fail; + } + } + + dbret = dbus_message_iter_close_container(&iter, &array_iter); + if (dbret == FALSE) { + goto fail; + } + } + + return msg; + +fail: + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build message\n"); + dbus_message_unref(msg); + return NULL; +} + +struct sss_dp_get_sudoers_state { + uint16_t dp_error; + uint32_t error; + const char *error_message; +}; + +static void sss_dp_get_sudoers_done(struct tevent_req *subreq); + +struct tevent_req * +sss_dp_get_sudoers_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *dom, + bool fast_reply, + enum sss_dp_sudo_type type, + const char *name, + uint32_t num_rules, + struct sysdb_attrs **rules) +{ + struct sss_dp_get_sudoers_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + struct be_conn *be_conn; + DBusMessage *msg; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sss_dp_get_sudoers_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + if (is_files_provider(dom)) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Domain %s does not check DP\n", + dom->name); + state->dp_error = DP_ERR_OK; + state->error = EOK; + state->error_message = talloc_strdup(state, "Success"); + if (state->error_message == NULL) { + ret = ENOMEM; + goto done; + } + ret = EOK; + goto done; + } + + ret = sss_dp_get_domain_conn(rctx, dom->conn_name, &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "BUG: The Data Provider connection for %s is not available!\n", + dom->name); + ret = EIO; + goto done; + } + + msg = sss_dp_get_sudoers_msg(state, be_conn->bus_name, dom, fast_reply, + type, name, num_rules, rules); + if (msg == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = sbus_call_dp_dp_sudoHandler_send(state, be_conn->conn, msg); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sss_dp_get_sudoers_done, req); + + ret = EAGAIN; + +done: +#ifdef BUILD_FILES_PROVIDER + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, rctx->ev); + } else +#endif + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, rctx->ev); + } + + return req; +} + +static void sss_dp_get_sudoers_done(struct tevent_req *subreq) +{ + struct sss_dp_get_sudoers_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sss_dp_get_sudoers_state); + + ret = sbus_call_dp_dp_sudoHandler_recv(state, subreq, &state->dp_error, + &state->error, + &state->error_message); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +sss_dp_get_sudoers_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char ** _error_message) +{ + struct sss_dp_get_sudoers_state *state; + state = tevent_req_data(req, struct sss_dp_get_sudoers_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_dp_error = state->dp_error; + *_error = state->error; + *_error_message = talloc_steal(mem_ctx, state->error_message); + + return EOK; +} diff --git a/src/responder/sudo/sudosrv_get_sudorules.c b/src/responder/sudo/sudosrv_get_sudorules.c new file mode 100644 index 0000000..1bcfc37 --- /dev/null +++ b/src/responder/sudo/sudosrv_get_sudorules.c @@ -0,0 +1,887 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + Jakub Hrozek <jhrozek@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" + +#include <stdint.h> +#include <string.h> +#include <talloc.h> +#include <tevent.h> + +#include "util/util.h" +#include "db/sysdb_sudo.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/sudo/sudosrv_private.h" +#include "providers/data_provider.h" + +static int +sudo_order_cmp(const void *a, const void *b, bool lower_wins) +{ + 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 (lower_wins) { + /* The lowest value takes priority. Original wrong SSSD behaviour. */ + if (o1 > o2) { + return 1; + } else if (o1 < o2) { + return -1; + } + } else { + /* The higher value takes priority. Standard LDAP behaviour. */ + if (o1 < o2) { + return 1; + } else if (o1 > o2) { + return -1; + } + } + + return 0; +} + +static int +sudo_order_low_cmp_fn(const void *a, const void *b) +{ + return sudo_order_cmp(a, b, true); +} + +static int +sudo_order_high_cmp_fn(const void *a, const void *b) +{ + return sudo_order_cmp(a, b, false); +} + +static errno_t +sort_sudo_rules(struct sysdb_attrs **rules, size_t count, bool lower_wins) +{ + if (lower_wins) { + DEBUG(SSSDBG_TRACE_FUNC, "Sorting rules with lower-wins logic\n"); + qsort(rules, count, sizeof(struct sysdb_attrs *), + sudo_order_low_cmp_fn); + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Sorting rules with higher-wins logic\n"); + qsort(rules, count, sizeof(struct sysdb_attrs *), + sudo_order_high_cmp_fn); + } + + return EOK; +} + +static errno_t sudosrv_format_runas(struct resp_ctx *rctx, + struct sysdb_attrs *rule, + const char *attr) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message_element *el; + struct sss_domain_info *dom; + const char *value; + char *fqname; + unsigned int i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + ret = sysdb_attrs_get_el_ext(rule, attr, false, &el); + if (ret == ENOENT) { + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get %s attribute " + "[%d]: %s\n", attr, ret, sss_strerror(ret)); + goto done; + } + + for (i = 0; i < el->num_values; i++) { + value = (const char *)el->values[i].data; + if (value == NULL) { + continue; + } + + dom = find_domain_by_object_name_ex(rctx->domains, value, true, + SSS_GND_DESCEND); + if (dom == NULL) { + continue; + } + + ret = sss_output_fqname(tmp_ctx, dom, value, + rctx->override_space, &fqname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to convert %s to output fqname " + "[%d]: %s\n", value, ret, sss_strerror(ret)); + goto done; + } + + talloc_free(el->values[i].data); + el->values[i].data = (uint8_t*)talloc_steal(el->values, fqname); + el->values[i].length = strlen(fqname); + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t sudosrv_format_rules(struct resp_ctx *rctx, + struct sysdb_attrs **rules, + uint32_t num_rules) +{ + uint32_t i; + errno_t ret = EOK; + + + for (i = 0; i < num_rules; i++) { + ret = sudosrv_format_runas(rctx, rules[i], + SYSDB_SUDO_CACHE_AT_RUNAS); + if (ret != EOK) { + return ret; + } + + ret = sudosrv_format_runas(rctx, rules[i], + SYSDB_SUDO_CACHE_AT_RUNASUSER); + if (ret != EOK) { + return ret; + } + + ret = sudosrv_format_runas(rctx, rules[i], + SYSDB_SUDO_CACHE_AT_RUNASGROUP); + if (ret != EOK) { + return ret; + } + } + + return ret; +} + +static errno_t sudosrv_query_cache(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char **attrs, + const char *filter, + struct sysdb_attrs ***_rules, + uint32_t *_count) +{ + TALLOC_CTX *tmp_ctx; + 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; + } + + 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, domain, filter, SUDORULE_SUBDIR, + attrs, &count, &msgs); + if (ret == ENOENT) { + *_rules = NULL; + *_count = 0; + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error looking up SUDO rules\n"); + 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; + } + + *_rules = talloc_steal(mem_ctx, rules); + *_count = (uint32_t)count; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sudosrv_expired_rules(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + uid_t uid, + const char *username, + char **groups, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules) +{ + const char *attrs[] = { SYSDB_NAME, NULL }; + char *filter; + errno_t ret; + + filter = sysdb_sudo_filter_expired(NULL, username, groups, uid); + if (filter == NULL) { + return ENOMEM; + } + + ret = sudosrv_query_cache(mem_ctx, domain, attrs, filter, + _rules, _num_rules); + talloc_free(filter); + + return ret; +} + +static errno_t sudosrv_cached_rules_by_user(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + uid_t cli_uid, + uid_t orig_uid, + const char *username, + char **groupnames, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs **rules; + uint32_t num_rules; + uint32_t i; + const char *filter; + const char *val; + errno_t ret; + const char *attrs[] = { SYSDB_OBJECTCLASS, + SYSDB_SUDO_CACHE_AT_CN, + SYSDB_SUDO_CACHE_AT_HOST, + SYSDB_SUDO_CACHE_AT_COMMAND, + SYSDB_SUDO_CACHE_AT_OPTION, + SYSDB_SUDO_CACHE_AT_RUNAS, + 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 }; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + filter = sysdb_sudo_filter_user(tmp_ctx, username, groupnames, orig_uid); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sudosrv_query_cache(tmp_ctx, domain, attrs, filter, + &rules, &num_rules); + if (ret != EOK) { + goto done; + } + + val = talloc_asprintf(tmp_ctx, "#%"SPRIuid, cli_uid); + if (val == NULL) { + ret = ENOMEM; + goto done; + } + + /* Add sudoUser: #uid to prevent conflicts with fqnames. */ + DEBUG(SSSDBG_TRACE_FUNC, "Replacing sudoUser attribute with " + "sudoUser: %s\n", val); + for (i = 0; i < num_rules; i++) { + ret = sysdb_attrs_add_string(rules[i], SYSDB_SUDO_CACHE_AT_USER, val); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to alter sudoUser attribute " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + } + + *_rules = talloc_steal(mem_ctx, rules); + *_num_rules = num_rules; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sudosrv_cached_rules_by_ng(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + uid_t uid, + const char *username, + char **groupnames, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules) +{ + char *filter; + errno_t ret; + 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_RUNAS, + 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 }; + + filter = sysdb_sudo_filter_netgroups(NULL, username, groupnames, uid); + if (filter == NULL) { + return ENOMEM; + } + + ret = sudosrv_query_cache(mem_ctx, domain, attrs, filter, + _rules, _num_rules); + talloc_free(filter); + + return ret; +} + +static errno_t sudosrv_cached_rules(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *domain, + uid_t cli_uid, + uid_t orig_uid, + const char *username, + char **groups, + bool inverse_order, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs **user_rules; + struct sysdb_attrs **ng_rules; + struct sysdb_attrs **rules; + uint32_t num_user_rules; + uint32_t num_ng_rules; + uint32_t num_rules; + uint32_t rule_iter, i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sudosrv_cached_rules_by_user(tmp_ctx, domain, + cli_uid, orig_uid, username, groups, + &user_rules, &num_user_rules); + if (ret != EOK) { + goto done; + } + + ret = sudosrv_cached_rules_by_ng(tmp_ctx, domain, + orig_uid, username, groups, + &ng_rules, &num_ng_rules); + if (ret != EOK) { + goto done; + } + + num_rules = num_user_rules + num_ng_rules; + if (num_rules == 0) { + *_rules = NULL; + *_num_rules = 0; + ret = EOK; + goto done; + } + + rules = talloc_array(tmp_ctx, struct sysdb_attrs *, num_rules); + if (rules == NULL) { + ret = ENOMEM; + goto done; + } + + rule_iter = 0; + for (i = 0; i < num_user_rules; rule_iter++, i++) { + rules[rule_iter] = talloc_steal(rules, user_rules[i]); + } + + for (i = 0; i < num_ng_rules; rule_iter++, i++) { + rules[rule_iter] = talloc_steal(rules, ng_rules[i]); + } + + ret = sort_sudo_rules(rules, num_rules, inverse_order); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not sort rules by sudoOrder\n"); + goto done; + } + + ret = sudosrv_format_rules(rctx, rules, num_rules); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not format sudo rules\n"); + goto done; + } + + *_rules = talloc_steal(mem_ctx, rules); + *_num_rules = num_rules; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sudosrv_cached_defaults(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules) +{ + char *filter; + errno_t ret; + 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_RUNAS, + 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 }; + + filter = sysdb_sudo_filter_defaults(NULL); + if (filter == NULL) { + return ENOMEM; + } + + ret = sudosrv_query_cache(mem_ctx, domain, attrs, filter, + _rules, _num_rules); + talloc_free(filter); + + return ret; +} + +static errno_t sudosrv_fetch_rules(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + enum sss_sudo_type type, + struct sss_domain_info *domain, + uid_t cli_uid, + uid_t orig_uid, + const char *username, + char **groups, + bool inverse_order, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules) +{ + struct sysdb_attrs **rules = NULL; + const char *debug_name = "unknown"; + uint32_t num_rules; + errno_t ret; + + switch (type) { + case SSS_SUDO_USER: + DEBUG(SSSDBG_TRACE_FUNC, "Retrieving rules for [%s@%s]\n", + username, domain->name); + debug_name = "rules"; + + ret = sudosrv_cached_rules(mem_ctx, rctx, domain, + cli_uid, orig_uid, username, groups, + inverse_order, &rules, &num_rules); + + break; + case SSS_SUDO_DEFAULTS: + debug_name = "default options"; + DEBUG(SSSDBG_TRACE_FUNC, "Retrieving default options for [%s@%s]\n", + username, domain->name); + + ret = sudosrv_cached_defaults(mem_ctx, domain, &rules, &num_rules); + + break; + default: + ret = EINVAL; + } + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve %s [%d]: %s\n", + debug_name, ret, sss_strerror(ret)); + return ret; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Returning %u %s for [%s@%s]\n", + num_rules, debug_name, username, domain->name); + + *_rules = rules; + *_num_rules = num_rules; + + return EOK; +} + +static void +sudosrv_dp_oob_req_done(struct tevent_req *req) +{ + DEBUG(SSSDBG_TRACE_FUNC, "Out of band refresh finished\n"); + talloc_free(req); +} + +struct sudosrv_refresh_rules_state { + struct resp_ctx *rctx; + struct sss_domain_info *domain; + const char *username; +}; + +static void sudosrv_refresh_rules_done(struct tevent_req *subreq); + +static struct tevent_req * +sudosrv_refresh_rules_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_domain_info *domain, + int threshold, + uid_t uid, + const char *username, + char **groups) +{ + struct sudosrv_refresh_rules_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + struct sysdb_attrs **rules; + uint32_t num_rules; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sudosrv_refresh_rules_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->rctx = rctx; + state->domain = domain; + state->username = username; + + ret = sudosrv_expired_rules(state, domain, uid, username, groups, + &rules, &num_rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to retrieve expired sudo rules [%d]: %s\n", + ret, strerror(ret)); + goto immediately; + } + + if (num_rules == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "No expired rules were found for [%s@%s].\n", + username, domain->name); + ret = EOK; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Refreshing %d expired rules of [%s@%s]\n", + num_rules, username, domain->name); + + if (num_rules > threshold) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Rules threshold [%d] is reached, performing full refresh " + "instead.\n", threshold); + + subreq = sss_dp_get_sudoers_send(state, rctx, domain, false, + SSS_DP_SUDO_FULL_REFRESH, + username, 0, NULL); + } else { + subreq = sss_dp_get_sudoers_send(state, rctx, domain, false, + SSS_DP_SUDO_REFRESH_RULES, + username, num_rules, rules); + } + + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sudosrv_refresh_rules_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void sudosrv_refresh_rules_done(struct tevent_req *subreq) +{ + struct sudosrv_refresh_rules_state *state; + struct tevent_req *req; + dbus_uint16_t err_maj; + dbus_uint32_t err_min; + const char *err_msg; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sudosrv_refresh_rules_state); + + ret = sss_dp_get_sudoers_recv(state, subreq, &err_maj, &err_min, &err_msg); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to refresh rules [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } else if (err_maj != 0 || err_min != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to get information from Data Provider, " + "Error: %u, %u, %s\n", + (unsigned int)err_maj, (unsigned int)err_min, + (err_msg == NULL ? "(null)" : err_msg)); + goto done; + } + + if (err_min == ENOENT) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Some expired rules were removed from the server, scheduling " + "full refresh out of band\n"); + subreq = sss_dp_get_sudoers_send(state->rctx, state->rctx, + state->domain, false, + SSS_DP_SUDO_FULL_REFRESH, + state->username, 0, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot issue DP request.\n"); + ret = EOK; /* We don't care. */ + goto done; + } + + tevent_req_set_callback(subreq, sudosrv_dp_oob_req_done, NULL); + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t sudosrv_refresh_rules_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sudosrv_get_rules_state { + struct tevent_context *ev; + struct resp_ctx *rctx; + enum sss_sudo_type type; + uid_t cli_uid; + const char *username; + struct sss_domain_info *domain; + char **groups; + bool inverse_order; + int threshold; + + uid_t orig_uid; + const char *orig_username; + + struct sysdb_attrs **rules; + uint32_t num_rules; +}; + +static void sudosrv_get_rules_initgr_done(struct tevent_req *subreq); +static void sudosrv_get_rules_done(struct tevent_req *subreq); + +struct tevent_req *sudosrv_get_rules_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sudo_ctx *sudo_ctx, + enum sss_sudo_type type, + uid_t cli_uid, + const char *username) +{ + struct sudosrv_get_rules_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sudosrv_get_rules_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->rctx = sudo_ctx->rctx; + state->type = type; + state->cli_uid = cli_uid; + state->inverse_order = sudo_ctx->inverse_order; + state->threshold = sudo_ctx->threshold; + + DEBUG(SSSDBG_TRACE_FUNC, "Running initgroups for [%s]\n", username); + + subreq = cache_req_initgr_by_name_send(state, ev, sudo_ctx->rctx, + sudo_ctx->rctx->ncache, 0, + CACHE_REQ_POSIX_DOM, NULL, + username); + if (subreq == NULL) { + ret = ENOMEM; + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } else { + tevent_req_set_callback(subreq, sudosrv_get_rules_initgr_done, req); + } + + return req; +} + +static void sudosrv_get_rules_initgr_done(struct tevent_req *subreq) +{ + struct sudosrv_get_rules_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sudosrv_get_rules_state); + + ret = cache_req_initgr_by_name_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + state->domain = result->domain; + state->username = talloc_steal(state, result->lookup_name); + talloc_zfree(result); + + ret = sysdb_get_sudo_user_info(state, state->domain, state->username, + &state->orig_username, + &state->orig_uid, + &state->groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to obtain user groups [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + subreq = sudosrv_refresh_rules_send(state, state->ev, state->rctx, + state->domain, state->threshold, + state->orig_uid, + state->orig_username, + state->groups); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sudosrv_get_rules_done, req); + + ret = EAGAIN; + +done: + if (ret != EOK && ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } else if (ret != EAGAIN) { + tevent_req_done(req); + } +} + +static void sudosrv_get_rules_done(struct tevent_req *subreq) +{ + struct sudosrv_get_rules_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sudosrv_get_rules_state); + + ret = sudosrv_refresh_rules_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to refresh expired rules, we will return what is " + "in cache.\n"); + } + + ret = sudosrv_fetch_rules(state, state->rctx, state->type, state->domain, + state->cli_uid, + state->orig_uid, + state->orig_username, + state->groups, + state->inverse_order, + &state->rules, &state->num_rules); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t sudosrv_get_rules_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules) +{ + struct sudosrv_get_rules_state *state = NULL; + state = tevent_req_data(req, struct sudosrv_get_rules_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_rules = talloc_steal(mem_ctx, state->rules); + *_num_rules = state->num_rules; + + return EOK; +} diff --git a/src/responder/sudo/sudosrv_private.h b/src/responder/sudo/sudosrv_private.h new file mode 100644 index 0000000..157afaa --- /dev/null +++ b/src/responder/sudo/sudosrv_private.h @@ -0,0 +1,112 @@ +/* + Authors: + Pavel Březina <pbrezina@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/>. +*/ + +#ifndef _SUDOSRV_PRIVATE_H_ +#define _SUDOSRV_PRIVATE_H_ + +#include <stdint.h> +#include <talloc.h> +#include <sys/types.h> + +#include "src/db/sysdb.h" +#include "responder/common/responder.h" + +#define SSS_SUDO_ERROR_OK 0 + +enum sss_dp_sudo_type { + SSS_DP_SUDO_REFRESH_RULES, + SSS_DP_SUDO_FULL_REFRESH +}; + +enum sss_sudo_type { + SSS_SUDO_DEFAULTS, + SSS_SUDO_USER +}; + +struct sudo_ctx { + struct resp_ctx *rctx; + + /* + * options + */ + bool timed; + bool inverse_order; + int threshold; +}; + +struct sudo_cmd_ctx { + struct cli_ctx *cli_ctx; + struct sudo_ctx *sudo_ctx; + enum sss_sudo_type type; + + /* input data */ + uid_t uid; + char *rawname; + + /* output data */ + struct sysdb_attrs **rules; + uint32_t num_rules; +}; + +struct sss_cmd_table *get_sudo_cmds(void); + +struct tevent_req *sudosrv_get_rules_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sudo_ctx *sudo_ctx, + enum sss_sudo_type type, + uid_t cli_uid, + const char *username); + +errno_t sudosrv_get_rules_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules); + +errno_t sudosrv_parse_query(TALLOC_CTX *mem_ctx, + uint8_t *query_body, + size_t query_len, + char **_rawname, + uid_t *_uid); + +errno_t sudosrv_build_response(TALLOC_CTX *mem_ctx, + uint32_t error, + uint32_t rules_num, + struct sysdb_attrs **rules, + uint8_t **_response_body, + size_t *_response_len); + +struct tevent_req * +sss_dp_get_sudoers_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *dom, + bool fast_reply, + enum sss_dp_sudo_type type, + const char *name, + uint32_t num_rules, + struct sysdb_attrs **rules); + +errno_t +sss_dp_get_sudoers_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char ** _error_message); + +#endif /* _SUDOSRV_PRIVATE_H_ */ diff --git a/src/responder/sudo/sudosrv_query.c b/src/responder/sudo/sudosrv_query.c new file mode 100644 index 0000000..a868ebe --- /dev/null +++ b/src/responder/sudo/sudosrv_query.c @@ -0,0 +1,307 @@ +/* + Authors: + Pavel Březina <pbrezina@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 <string.h> +#include <stdint.h> +#include <errno.h> +#include <talloc.h> +#include <tevent.h> + +#include "util/util.h" +#include "responder/sudo/sudosrv_private.h" + +static int sudosrv_response_append_string(TALLOC_CTX *mem_ctx, + const char *str, + size_t str_len, + uint8_t **_response_body, + size_t *_response_len) +{ + size_t response_len = *_response_len; + uint8_t *response_body = *_response_body; + + response_body = talloc_realloc(mem_ctx, response_body, uint8_t, + response_len + (str_len * sizeof(char))); + if (response_body == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc() failed\n"); + return ENOMEM; + } + memcpy(response_body + response_len, str, str_len); + response_len += str_len; + + *_response_body = response_body; + *_response_len = response_len; + + return EOK; +} + +static int sudosrv_response_append_uint32(TALLOC_CTX *mem_ctx, + uint32_t number, + uint8_t **_response_body, + size_t *_response_len) +{ + size_t response_len = *_response_len; + uint8_t *response_body = *_response_body; + + response_body = talloc_realloc(mem_ctx, response_body, uint8_t, + response_len + sizeof(uint32_t)); + if (response_body == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc() failed\n"); + return ENOMEM; + } + SAFEALIGN_SET_UINT32(response_body + response_len, number, &response_len); + + *_response_body = response_body; + *_response_len = response_len; + + return EOK; +} + +static int sudosrv_response_append_attr(TALLOC_CTX *mem_ctx, + const char *name, + unsigned int values_num, + struct ldb_val *values, + uint8_t **_response_body, + size_t *_response_len) +{ + uint8_t *response_body = *_response_body; + size_t response_len = *_response_len; + TALLOC_CTX *tmp_ctx = NULL; + unsigned int i = 0; + int ret = EOK; + const char *strval; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + /* attr name */ + ret = sudosrv_response_append_string(tmp_ctx, name, strlen(name) + 1, + &response_body, &response_len); + if (ret != EOK) { + goto done; + } + + /* values count */ + ret = sudosrv_response_append_uint32(tmp_ctx, values_num, + &response_body, &response_len); + if (ret != EOK) { + goto done; + } + + /* values */ + for (i = 0; i < values_num; i++) { + strval = (const char *) values[i].data; + + if (strlen((strval)) != values[i].length) { + DEBUG(SSSDBG_CRIT_FAILURE, "value is not a string\n"); + ret = EINVAL; + goto done; + } + + ret = sudosrv_response_append_string(tmp_ctx, + strval, + values[i].length + 1, + &response_body, &response_len); + DEBUG(SSSDBG_TRACE_INTERNAL, "%s:%s\n", name, strval); + if (ret != EOK) { + goto done; + } + } + + *_response_body = talloc_steal(mem_ctx, response_body); + *_response_len = response_len; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int sudosrv_response_append_rule(TALLOC_CTX *mem_ctx, + int attrs_num, + struct ldb_message_element *attrs, + uint8_t **_response_body, + size_t *_response_len) +{ + uint8_t *response_body = *_response_body; + size_t response_len = *_response_len; + TALLOC_CTX *tmp_ctx = NULL; + int i = 0; + int ret = EOK; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + /* attrs count */ + ret = sudosrv_response_append_uint32(tmp_ctx, attrs_num, + &response_body, &response_len); + if (ret != EOK) { + goto done; + } + + /* attrs */ + for (i = 0; i < attrs_num; i++) { + ret = sudosrv_response_append_attr(tmp_ctx, attrs[i].name, + attrs[i].num_values, attrs[i].values, + &response_body, &response_len); + if (ret != EOK) { + goto done; + } + } + + *_response_body = talloc_steal(mem_ctx, response_body); + *_response_len = response_len; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +/* + * Response format: + * <error_code(uint32_t)><domain(char*)>\0<num_entries(uint32_t)><rule1><rule2>... + * <ruleN> = <num_attrs(uint32_t)><attr1><attr2>... + * <attrN> = <name(char*)>\0<num_values(uint32_t)><value1(char*)>\0<value2(char*)>\0... + * + * if <error_code> is not SSS_SUDO_ERROR_OK, the rest of the data is skipped. + */ +errno_t sudosrv_build_response(TALLOC_CTX *mem_ctx, + uint32_t error, + uint32_t rules_num, + struct sysdb_attrs **rules, + uint8_t **_response_body, + size_t *_response_len) +{ + uint8_t *response_body = NULL; + size_t response_len = 0; + TALLOC_CTX *tmp_ctx = NULL; + uint32_t i = 0; + errno_t ret = EOK; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + /* error code */ + ret = sudosrv_response_append_uint32(tmp_ctx, error, + &response_body, &response_len); + if (ret != EOK) { + goto fail; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "error: [%"PRIu32"]\n", error); + + if (error != SSS_SUDO_ERROR_OK) { + goto done; + } + + /* domain name - deprecated + * TODO: when possible change the protocol */ + ret = sudosrv_response_append_string(tmp_ctx, "\0", 1, + &response_body, &response_len); + if (ret != EOK) { + goto fail; + } + + /* rules count */ + ret = sudosrv_response_append_uint32(tmp_ctx, rules_num, + &response_body, &response_len); + if (ret != EOK) { + goto fail; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "rules_num: [%"PRIu32"]\n", rules_num); + + /* rules */ + for (i = 0; i < rules_num; i++) { + DEBUG(SSSDBG_TRACE_INTERNAL, "rule [%"PRIu32"]/[%"PRIu32"]\n", i+1, rules_num); + ret = sudosrv_response_append_rule(tmp_ctx, rules[i]->num, rules[i]->a, + &response_body, &response_len); + if (ret != EOK) { + goto fail; + } + } + +done: + *_response_body = talloc_steal(mem_ctx, response_body); + *_response_len = response_len; + + ret = EOK; + +fail: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sudosrv_parse_query(TALLOC_CTX *mem_ctx, + uint8_t *query_body, + size_t query_len, + char **_rawname, + uid_t *_uid) +{ + size_t offset = 0; + size_t rawname_len; + char *rawname; + uid_t uid; + + /* uid */ + if (query_len < sizeof(uid_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Query is too small\n"); + return EINVAL; + } + safealign_memcpy(&uid, query_body, sizeof(uid_t), &offset); + + /* username[@domain] */ + rawname = (char*)(query_body + offset); + rawname_len = query_len - offset; /* strlen + zero */ + + if (rawname[rawname_len - 1] != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Username is not zero terminated\n"); + return EINVAL; + } + + if (rawname_len < 2) { /* at least one character and zero */ + DEBUG(SSSDBG_CRIT_FAILURE, "Query does not contain username\n"); + return EINVAL; + } + + if (!sss_utf8_check((uint8_t*)rawname, rawname_len - 1)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Supplied data is not valid UTF-8 string\n"); + return EINVAL; + } + + rawname = talloc_strdup(mem_ctx, rawname); + if (rawname == NULL) { + return ENOMEM; + } + + *_uid = uid; + *_rawname = rawname; + + return EOK; +} |