From 1e546fd179a8a5f51523fe2a8a9965398716d8f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ezina?= Date: Fri, 30 Sep 2011 14:10:28 +0200 Subject: [PATCH 02/13] SUDO integration - SUDO plugin --- src/sss_client/sudo_plugin/sss_sudo_to_lib.c | 130 +++++++ src/sss_client/sudo_plugin/sss_sudoplugin.c | 402 ++++++++++++++++++++ src/sss_client/sudo_plugin/sss_sudoplugin.h | 29 ++ .../sudo_plugin/sss_sudoplugin_private.h | 35 ++ 4 files changed, 596 insertions(+), 0 deletions(-) create mode 100644 src/sss_client/sudo_plugin/sss_sudo_to_lib.c create mode 100644 src/sss_client/sudo_plugin/sss_sudoplugin.c create mode 100644 src/sss_client/sudo_plugin/sss_sudoplugin.h create mode 100644 src/sss_client/sudo_plugin/sss_sudoplugin_private.h diff --git a/src/sss_client/sudo_plugin/sss_sudo_to_lib.c b/src/sss_client/sudo_plugin/sss_sudo_to_lib.c new file mode 100644 index 0000000..61bc527 --- /dev/null +++ b/src/sss_client/sudo_plugin/sss_sudo_to_lib.c @@ -0,0 +1,130 @@ +/* + 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 . +*/ + +/* + * This file contains functions that would be nice to have in a common + * plugin library from SUDO upstream. + */ + +#include +#include +#include +#include +#include +#include + +#include "sss_client/sudo_plugin/sss_sudoplugin_private.h" + +int command_exists(const char* command); + +char * get_qualified_command_path(const char *command) +{ + char *path_env = NULL; + char *path = NULL; + char *cp = NULL; + char *qualified = NULL; + char pathbuf[PATH_MAX]; + int dot_in_path = 0; + + /* + * If we were given a fully qualified or relative path + * there is no need to look at $PATH. + */ + if (strchr(command, '/') != NULL) { + if (command_exists(command)) { + return strdup(command); + } + + return NULL; + } + + path_env = getenv("PATH"); + path = strdup(path_env); + if (path == NULL) { + errno = ENOMEM; + return NULL; + } + + if (path == NULL) { + return NULL; + } + + do { + cp = strchr(path, ':'); + if (cp != NULL) { + *cp = '\0'; + } + + /* + * Search current dir last if it is in $PATH This will miss sneaky + * things like using './' or './/' + */ + if (*path == '\0' || (*path == '.' && *(path + 1) == '\0')) { + dot_in_path = 1; + } else { + snprintf(pathbuf, sizeof(char) * PATH_MAX, "%s/%s", path, command); + if (command_exists(pathbuf)) { + qualified = pathbuf; + break; + } + } + + path = cp + 1; + } while(cp != NULL); + + /* + * Check current dir if dot was in the PATH + */ + if (qualified == NULL && dot_in_path) { + snprintf(pathbuf, sizeof(char) * PATH_MAX, "./%s", command); + if (command_exists(pathbuf)) { + qualified = pathbuf; + } + } + + /* free(path_env); do not free since it is from getenv() */ + if (qualified == NULL) { + errno = ENOENT; + return NULL; + } + + return strdup(qualified); +} + +int command_exists(const char* command) +{ + struct stat sb; + + if (command == NULL || command[0] == '\0') { + return 0; + }; + + if (stat(command, &sb) == 0) { + if (S_ISREG(sb.st_mode) + && (sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { + errno = EOK; + return 1; + } + + errno = EACCES; + } + + return 0; +} diff --git a/src/sss_client/sudo_plugin/sss_sudoplugin.c b/src/sss_client/sudo_plugin/sss_sudoplugin.c new file mode 100644 index 0000000..9e2dfce --- /dev/null +++ b/src/sss_client/sudo_plugin/sss_sudoplugin.c @@ -0,0 +1,402 @@ +/* + 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 "config.h" + +#include +#include +#include +#include +#include +#include + +#include "sss_client/sudo_plugin/sss_sudoplugin.h" +#include "sss_client/sudo_plugin/sss_sudoplugin_private.h" +#include "sss_client/sss_cli.h" + +struct plugin_state { + sudo_conv_t conversation; + sudo_printf_t printf; + char * const *settings; + char * const *user_info; + char * const *user_env; +}; + +int policy_open(unsigned int version, + sudo_conv_t sudo_conversation, + sudo_printf_t sudo_printf, + char * const settings[], + char * const user_info[], + char * const user_env[]); + +void policy_close(int exit_status, + int error); + +int policy_version(int verbose); + +int policy_list(int argc, + char * const argv[], + int verbose, + const char *list_user); + +int policy_check(int argc, + char * const argv[], + char *env_add[], + char **command_out[], + char **argv_out[], + char **user_env_out[]); + +static errno_t policy_check_create_query(const char *qualified_command_path, + char * const *settings, + char * const *user_info, + char * const *user_env, + char * const *env_add, + int argc, + char * const argv[], + char **query, + int *query_length); + +static errno_t policy_check_parse_response(char *response, + int response_length, + int *result, + char ***_command_out, + char ***_argv_out, + char ***_user_env_out); + + +/* SUDO Plugin structure */ +struct policy_plugin sss_sudo_policy = { + SUDO_POLICY_PLUGIN, + SUDO_API_VERSION, + policy_open, + policy_close, + policy_version, + policy_check, + policy_list, + NULL, /* validate */ + NULL, /* invalidate */ + NULL /* session init */ +}; + +/* Global plugin state initialized from policy_check with data passed by SUDO */ +static struct plugin_state plugin = {NULL, NULL, NULL, NULL, NULL}; + +int policy_open(unsigned int version, + sudo_conv_t sudo_conversation, + sudo_printf_t sudo_printf, + char * const settings[], + char * const user_info[], + char * const user_env[]) +{ + if (sudo_conversation == NULL || settings == NULL) { + return SSS_SUDO_USAGE_ERROR; + } + + /* check Plugin API version */ + + /* + * TODO: Do we need to check major and minor version or major is enough? + */ + if (version != SUDO_API_VERSION) { + sudo_printf(SUDO_CONV_ERROR_MSG, "The SSS SUDO plugin requires " + "API version %d. Your version is %d", + SUDO_API_VERSION, version); + + return SSS_SUDO_GENERAL_ERROR; + } + + /* save input variables */ + + plugin.conversation = sudo_conversation; + plugin.printf = sudo_printf; + plugin.settings = settings; + plugin.user_info = user_info; + plugin.user_env = user_env; + + return SSS_SUDO_SUCCESS; +} + +void policy_close(int exit_status, int error) +{ + if (error) { + plugin.printf(SUDO_CONV_ERROR_MSG, "Command error: %s\n", strerror(error)); + } else if (WIFEXITED(exit_status)) { + plugin.printf(SUDO_CONV_INFO_MSG, "Command exited with status %d\n", + WEXITSTATUS(exit_status)); + } else if (WIFSIGNALED(exit_status)) { + plugin.printf(SUDO_CONV_INFO_MSG, "Command was killed by signal %d\n", + WTERMSIG(exit_status)); + } + + return; +} + +int policy_version(int verbose) +{ + return SSS_SUDO_SUCCESS; +} + +int policy_list(int argc, + char * const argv[], + int verbose, + const char *list_user) +{ + return SSS_SUDO_SUCCESS; +} + +int policy_check(int argc, + char * const argv[], + char *env_add[], + char **command_out[], + char **argv_out[], + char **user_env_out[]) +{ + char *qualified_command_path = NULL; + char *query = NULL; + int query_length = 0; + int ret = 0; + int errnop; + int sudo_result = SSS_SUDO_FAILURE; + struct sss_cli_req_data request_data; + uint8_t *reply_buf = NULL; + size_t reply_len; + + if (argc <= 0) { + plugin.printf(SUDO_CONV_ERROR_MSG, "sudo: no command specified!\n"); + return SSS_SUDO_USAGE_ERROR; + } + + /* Does command exists? */ + errno = 0; + qualified_command_path = get_qualified_command_path(argv[0]); + if (errno) { + if (qualified_command_path == NULL) { + plugin.printf(SUDO_CONV_ERROR_MSG, + "sudo: %s: %s\n", argv[0], strerror(errno)); + } else { + plugin.printf(SUDO_CONV_ERROR_MSG, + "sudo: %s: %s\n", qualified_command_path, strerror(errno)); + } + return SSS_SUDO_GENERAL_ERROR; + } + + /* create query data */ + ret = policy_check_create_query(qualified_command_path, plugin.settings, + plugin.user_info, plugin.user_env, + env_add, argc, argv, + &query, &query_length); + if (ret != EOK) { + plugin.printf(SUDO_CONV_ERROR_MSG, + "sudo: unable to create query string: %s\n", + strerror(ret)); + ret = SSS_SUDO_GENERAL_ERROR; + goto done; + } + request_data.len = query_length; + request_data.data = (const void*)query; + + /* send query */ + errnop = 0; + ret = sss_sudo_make_request(SSS_SUDO_CHECK, &request_data, &reply_buf, + &reply_len, &errnop); + if (errnop != 0) { + plugin.printf(SUDO_CONV_ERROR_MSG, + "Unable contact SSSD responder: %s\n", strerror(errnop)); + ret = SSS_SUDO_GENERAL_ERROR; + goto done; + } + + /* process response */ + ret = policy_check_parse_response((char*)reply_buf, reply_len, &sudo_result, + command_out, argv_out, user_env_out); + if (ret != EOK) { + plugin.printf(SUDO_CONV_ERROR_MSG, + "sudo: unable to parse response: %s\n", + strerror(ret)); + ret = SSS_SUDO_GENERAL_ERROR; + goto done; + } + + plugin.printf(SUDO_CONV_INFO_MSG, "CMD Return code: %d\n", sudo_result); + plugin.printf(SUDO_CONV_INFO_MSG, "errnop: %d\n", errnop); + + switch (sudo_result) { + case SSS_SUDO_RESPONSE_ALLOW: + ret = SSS_SUDO_SUCCESS; + break; + case SSS_SUDO_RESPONSE_AUTHENTICATE: + /* user can run the command, but must authenticate himself first */ + /* TODO */ + ret = SSS_SUDO_FAILURE; + break; + case SSS_SUDO_RESPONSE_DENY: + ret = SSS_SUDO_FAILURE; + break; + case SSS_SUDO_RESPONSE_UNKNOWN: + /* possibly local user, run sudoers plugin */ + /* TODO */ + ret = SSS_SUDO_GENERAL_ERROR; + plugin.printf(SUDO_CONV_ERROR_MSG, "sudo: Unknown user.\n"); + break; + } + +done: + free(query); + + return ret; +} + +/* + * Creates query string in format: + * qualified_command_path\0argv[0]\0argv[i]\0\0 + * env_add\0\0user_env\0\0settings\0\0user_info\0\0 + * + * where env_add, user_env, settings and user_info are in the form of + * NAME=VALUE pairs. + */ +errno_t policy_check_create_query(const char *qualified_command_path, + char * const *settings, + char * const *user_info, + char * const *user_env, + char * const *env_add, + int argc, + char * const argv[], + char **query, + int *query_length) +{ +#define APPEND_ELEMENT(element) do { \ + iter_length = strlen(element) + 1; /* with ending \0 */ \ + data = (char*)realloc(data, data_length + iter_length); \ + if (data == NULL) { \ + return ENOMEM; \ + } \ + memcpy(data + data_length, element, iter_length); \ + data_length += iter_length; \ +} while(0) + +#define APPEND_ZERO() do { \ + data_length++; \ + data = (char*)realloc(data, data_length); \ + data[data_length] = '\0'; \ +} while(0) + + char *data = NULL; + int data_length = 0; + char * const *iter = NULL; + int iter_length = 0; + int i = 0; + char * const *fields[] = { env_add, user_env, settings, user_info, NULL }; + char * const **field = NULL; + + /* append qualified_command_path */ + + APPEND_ELEMENT(qualified_command_path); + + /* append argv */ + + for (i = 0; i < argc; i++) { + APPEND_ELEMENT(argv[i]); + } + APPEND_ZERO(); + + /* append NAME=VALUE fields */ + for (field = fields; *field != NULL; field++) { + if (**field == NULL) { + APPEND_ZERO(); + APPEND_ZERO(); + } else { + for (iter = *field; *iter != NULL; iter++) { + APPEND_ELEMENT(*iter); + } + APPEND_ZERO(); + } + } + + *query_length = data_length; + *query = data; + + return EOK; + +#undef APPEND_ELEMENT +#undef APPEND_ZERO +} + +errno_t policy_check_parse_response(char *response, + int response_length, + int *result, + char ***_command_out, + char ***_argv_out, + char ***_user_env_out) +{ +#define LOAD_ARRAY(element) do { \ + i = 0; \ + while (*current_position != '\0') { \ + i++; \ + element = (char**)realloc(element, i * sizeof(char*)); \ + element[i - 1] = current_position; \ + current_position = strchr(current_position, '\0'); \ + if (current_position == NULL) { \ + ret = ESPIPE; \ + goto done; \ + } \ + current_position++; \ + } \ + i++; \ + element = (char**)realloc(element, i * sizeof(char*)); \ + element[i - 1] = NULL; \ + current_position++; \ +} while(0) + + char *current_position = NULL; + char **command_out = NULL; + char **argv_out = NULL; + char **user_env_out = NULL; + int i = 0; + int ret; + + if (response_length <= 0 || response == NULL) { + return EINVAL; + } + + /* get responder result */ + memcpy(result, response, sizeof(int)); + current_position = response + sizeof(int); + + /* get argv_out */ + LOAD_ARRAY(argv_out); + + /* get command_out */ + LOAD_ARRAY(command_out); + + /* get user_env_out */ + LOAD_ARRAY(user_env_out); + + /* copy output */ + *_command_out = command_out; + *_argv_out = argv_out; + *_user_env_out = user_env_out; + + ret = EOK; + +done: + return ret; + +#undef LOAD_ARRAY +} diff --git a/src/sss_client/sudo_plugin/sss_sudoplugin.h b/src/sss_client/sudo_plugin/sss_sudoplugin.h new file mode 100644 index 0000000..c2aab1d --- /dev/null +++ b/src/sss_client/sudo_plugin/sss_sudoplugin.h @@ -0,0 +1,29 @@ +/* + 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 . +*/ + +#ifndef SSS_SUDOPLUGIN_H_ +#define SSS_SUDOPLUGIN_H_ + +#define SSS_SUDO_RESPONSE_ALLOW 0 /* allow to run the command */ +#define SSS_SUDO_RESPONSE_DENY 1 /* disallow to run the command */ +#define SSS_SUDO_RESPONSE_UNKNOWN 2 /* user is not known, might be local */ +#define SSS_SUDO_RESPONSE_AUTHENTICATE 3 /* allow but authenticate user first */ + +#endif /* SSS_SUDOPLUGIN_H_ */ diff --git a/src/sss_client/sudo_plugin/sss_sudoplugin_private.h b/src/sss_client/sudo_plugin/sss_sudoplugin_private.h new file mode 100644 index 0000000..f38ed07 --- /dev/null +++ b/src/sss_client/sudo_plugin/sss_sudoplugin_private.h @@ -0,0 +1,35 @@ +/* + 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 . +*/ + +#ifndef SSS_SUDOPLUGIN_PRIVATE_H_ +#define SSS_SUDOPLUGIN_PRIVATE_H_ + +#define SSS_SUDO_SUCCESS 1 +#define SSS_SUDO_FAILURE 0 +#define SSS_SUDO_GENERAL_ERROR -1 +#define SSS_SUDO_USAGE_ERROR -2 + +#ifndef EOK + #define EOK 0 +#endif + +char * get_qualified_command_path(const char *command); + +#endif /* SSS_SUDOPLUGIN_PRIVATE_H_ */ -- 1.7.6.4