From 033bffd32fcff44d94292c31108722d1a03c0714 Mon Sep 17 00:00:00 2001 From: "Eliel C. Sardanons" Date: Mon, 1 Dec 2008 18:52:14 +0000 Subject: Introduce CLI permissions. Based on cli_permissions.conf configuration file, we are able to permit or deny cli commands based on some patterns and the local user and group running rasterisk. (Sorry if I missed some of the testers). Reviewboard: http://reviewboard.digium.com/r/11/ (closes issue #11123) Reported by: eliel Tested by: eliel, IgorG, Laureano, otherwiseguy, mvanbaak git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@160062 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- CHANGES | 6 + configs/cli_permissions.conf.sample | 82 +++++++ configure | 5 +- configure.ac | 2 +- include/asterisk/_private.h | 1 + include/asterisk/autoconfig.h.in | 3 + include/asterisk/cli.h | 22 +- main/asterisk.c | 86 ++++++-- main/cli.c | 418 ++++++++++++++++++++++++++++++++++-- 9 files changed, 595 insertions(+), 30 deletions(-) create mode 100644 configs/cli_permissions.conf.sample diff --git a/CHANGES b/CHANGES index abe6a0441..491e28640 100644 --- a/CHANGES +++ b/CHANGES @@ -402,6 +402,12 @@ Dialplan functions CLI Changes ----------- + * Added CLI permissions, config file: cli_permissions.conf + default is to allow all commands for every local user/group. + Also this new feature added three new CLI commands: + - cli check permissions {|@|@} [] + - cli reload permissions + - cli show permissions * New CLI command "core show hint" (usage: core show hint ) * New CLI command "core show settings" * Added 'core show channels count' CLI command. diff --git a/configs/cli_permissions.conf.sample b/configs/cli_permissions.conf.sample new file mode 100644 index 000000000..4a6973f50 --- /dev/null +++ b/configs/cli_permissions.conf.sample @@ -0,0 +1,82 @@ +; +; CLI permissions configuration example for Asterisk +; +; All the users that you want to connect with asterisk using +; rasterisk, should have write/read access to the +; asterisk socket (asterisk.ctl). You could change the permissions +; of this file in 'asterisk.conf' config parameter: 'astctlpermissions' (0666) +; found on the [files] section. +; +; general options: +; +; default_perm = permit | deny +; This is the default permissions to apply for a user that +; does not has a permissions definided. +; +; user options: +; permit = | all ; allow the user to run 'command' | +; ; allow the user to run 'all' the commands +; deny = | all ; disallow the user to run 'command' | +; ; disallow the user to run 'all' commands. +; + +[general] + +default_perm=permit ; To leave asterisk working as normal + ; we should set this parameter to 'permit' +; +; Follows the per-users permissions configs. +; +; This list is read in the sequence that is being written, so +; In this example the user 'eliel' is allow to run only the following +; commands: +; sip show peer +; core set debug +; core set verbose +; If the user is not specified, the default_perm option will be apply to +; every command. +; +; Notice that you can also use regular expressions to allow or deny access to a +; certain command like: 'core show application D*'. In this example the user will be +; allowed to view the documentation for all the applications starting with 'D'. +; Another regular expression could be: 'channel originate SIP/[0-9]* extension *' +; allowing the user to use 'channel originate' on a sip channel and with the 'extension' +; parameter and avoiding the use of the 'application' parameter. +; +; We can also use the templates syntax: +; [supportTemplate](!) +; deny=all +; permit=sip show ; all commands starting with 'sip show' will be allowed +; permit=core show +; +; You can specify permissions for a local group instead of a user, +; just put a '@' and we will know that is a group. +; IMPORTANT NOTE: Users permissions overwrite group permissions. +; +;[@adm] +;deny=all +;permit=sip +;permit=core +; +; +;[eliel] +;deny=all +;permit=sip show peer +;deny=sip show peers +;permit=core set +; +; +;User 'tommy' inherits from template 'supportTemplate': +; deny=all +; permit=sip show +; permit=core show +;[tommy](supportTemplate) +;permit=core set debug +;permit=dialplan show +; +; +;[mark] +;deny=all +;permit=all +; +; diff --git a/configure b/configure index c0b79795d..e305e86b1 100755 --- a/configure +++ b/configure @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.ac Revision: 159631 . +# From configure.ac Revision: 159818 . # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.61 for asterisk 1.6. # @@ -15639,7 +15639,8 @@ done -for ac_func in asprintf atexit dup2 endpwent ftruncate getcwd gethostbyname gethostname getloadavg gettimeofday ioperm inet_ntoa isascii localtime_r memchr memmove memset mkdir munmap putenv re_comp regcomp select setenv socket strcasecmp strcasestr strchr strcspn strdup strerror strlcat strlcpy strncasecmp strndup strnlen strrchr strsep strspn strstr strtol strtoq unsetenv utime vasprintf + +for ac_func in asprintf atexit dup2 endpwent ftruncate getcwd gethostbyname gethostname getloadavg gettimeofday ioperm inet_ntoa isascii localtime_r memchr memmove memset mkdir munmap putenv re_comp regcomp select setenv socket strcasecmp strcasestr strchr strcspn strdup strerror strlcat strlcpy strncasecmp strndup strnlen strrchr strsep strspn strstr strtol strtoq unsetenv utime vasprintf getpeereid do as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh` { echo "$as_me:$LINENO: checking for $ac_func" >&5 diff --git a/configure.ac b/configure.ac index 26c6e5a28..ad885a28d 100644 --- a/configure.ac +++ b/configure.ac @@ -336,7 +336,7 @@ AC_FUNC_STRNLEN AC_FUNC_STRTOD AC_FUNC_UTIME_NULL AC_FUNC_VPRINTF -AC_CHECK_FUNCS([asprintf atexit dup2 endpwent ftruncate getcwd gethostbyname gethostname getloadavg gettimeofday ioperm inet_ntoa isascii localtime_r memchr memmove memset mkdir munmap putenv re_comp regcomp select setenv socket strcasecmp strcasestr strchr strcspn strdup strerror strlcat strlcpy strncasecmp strndup strnlen strrchr strsep strspn strstr strtol strtoq unsetenv utime vasprintf]) +AC_CHECK_FUNCS([asprintf atexit dup2 endpwent ftruncate getcwd gethostbyname gethostname getloadavg gettimeofday ioperm inet_ntoa isascii localtime_r memchr memmove memset mkdir munmap putenv re_comp regcomp select setenv socket strcasecmp strcasestr strchr strcspn strdup strerror strlcat strlcpy strncasecmp strndup strnlen strrchr strsep strspn strstr strtol strtoq unsetenv utime vasprintf getpeereid]) AC_CHECK_FUNCS([glob]) diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h index 83ae166de..98857025c 100644 --- a/include/asterisk/_private.h +++ b/include/asterisk/_private.h @@ -24,6 +24,7 @@ int ast_term_init(void); /*!< Provided by term.c */ int astdb_init(void); /*!< Provided by db.c */ void ast_channels_init(void); /*!< Provided by channel.c */ void ast_builtins_init(void); /*!< Provided by cli.c */ +int ast_cli_perms_init(int reload); /*!< Provided by cli.c */ int dnsmgr_init(void); /*!< Provided by dnsmgr.c */ void dnsmgr_start_refresh(void); /*!< Provided by dnsmgr.c */ int dnsmgr_reload(void); /*!< Provided by dnsmgr.c */ diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in index d6b3f8971..c058291df 100644 --- a/include/asterisk/autoconfig.h.in +++ b/include/asterisk/autoconfig.h.in @@ -322,6 +322,9 @@ /* Define to 1 if you have the `getpagesize' function. */ #undef HAVE_GETPAGESIZE +/* Define to 1 if you have the `getpeereid' function. */ +#undef HAVE_GETPEEREID + /* Define to 1 if you have the `gettimeofday' function. */ #undef HAVE_GETTIMEOFDAY diff --git a/include/asterisk/cli.h b/include/asterisk/cli.h index 535b4e6af..a02764dfa 100644 --- a/include/asterisk/cli.h +++ b/include/asterisk/cli.h @@ -32,6 +32,10 @@ extern "C" { void ast_cli(int fd, const char *fmt, ...) __attribute__((format(printf, 2, 3))); +/* dont check permissions while passing this option as a 'uid' + * to the cli_has_permissions() function. */ +#define CLI_NO_PERMS -1 + #define RESULT_SUCCESS 0 #define RESULT_SHOWUSAGE 1 #define RESULT_FAILURE 2 @@ -191,23 +195,35 @@ char *ast_cli_complete(const char *word, char *const choices[], int pos); /*! * \brief Interprets a command - * Interpret a command s, sending output to fd + * Interpret a command s, sending output to fd if uid:gid has permissions + * to run this command. uid = CLI_NO_PERMS to avoid checking user permissions + * gid = CLI_NO_PERMS to avoid checking group permissions. + * \param uid User ID that is trying to run the command. + * \param gid Group ID that is trying to run the command. * \param fd pipe * \param s incoming string * \retval 0 on success * \retval -1 on failure */ -int ast_cli_command(int fd, const char *s); +int ast_cli_command_full(int uid, int gid, int fd, const char *s); + +#define ast_cli_command(fd,s) ast_cli_command_full(CLI_NO_PERMS, CLI_NO_PERMS, fd, s) /*! * \brief Executes multiple CLI commands * Interpret strings separated by NULL and execute each one, sending output to fd + * if uid has permissions, uid = CLI_NO_PERMS to avoid checking users permissions. + * gid = CLI_NO_PERMS to avoid checking group permissions. + * \param uid User ID that is trying to run the command. + * \param gid Group ID that is trying to run the command. * \param fd pipe * \param size is the total size of the string * \param s incoming string * \retval number of commands executed */ -int ast_cli_command_multiple(int fd, size_t size, const char *s); +int ast_cli_command_multiple_full(int uid, int gid, int fd, size_t size, const char *s); + +#define ast_cli_command_multiple(fd,size,s) ast_cli_command_multiple_full(CLI_NO_PERMS, CLI_NO_PERMS, fd, size, s) /*! \brief Registers a command or an array of commands * \param e which cli entry to register. diff --git a/main/asterisk.c b/main/asterisk.c index 00b8df1e5..bef3cba49 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -176,6 +176,8 @@ struct console { int p[2]; /*!< Pipe */ pthread_t t; /*!< Thread of handler */ int mute; /*!< Is the console muted for logs */ + int uid; /*!< Remote user ID. */ + int gid; /*!< Remote group ID. */ int levels[NUMLOGLEVELS]; /*!< Which log levels are enabled for the console */ }; @@ -988,6 +990,48 @@ static void network_verboser(const char *s) static pthread_t lthread; +/*! + * \brief read() function supporting the reception of user credentials. + * + * \param fd Socket file descriptor. + * \param buffer Receive buffer. + * \param size 'buffer' size. + * \param con Console structure to set received credentials + * \retval -1 on error + * \retval the number of bytes received on success. + */ +static int read_credentials(int fd, char *buffer, size_t size, struct console *con) +{ +#if defined(SO_PEERCRED) + struct ucred cred; + socklen_t len = sizeof(cred); +#endif + int result, uid, gid; + + result = read(fd, buffer, size); + if (result < 0) { + return result; + } + +#if defined(SO_PEERCRED) + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len)) { + return result; + } + uid = cred.uid; + gid = cred.gid; +#elif defined(HAVE_GETPEEREID) + if (getpeereid(fd, &uid, &gid)) { + return result; + } +#else + return result; +#endif + con->uid = uid; + con->gid = gid; + + return result; +} + static void *netconsole(void *vconsole) { struct console *con = vconsole; @@ -1015,19 +1059,19 @@ static void *netconsole(void *vconsole) continue; } if (fds[0].revents) { - res = read(con->fd, tmp, sizeof(tmp)); + res = read_credentials(con->fd, tmp, sizeof(tmp), con); if (res < 1) { break; } tmp[res] = 0; if (strncmp(tmp, "cli quit after ", 15) == 0) { - ast_cli_command_multiple(con->fd, res - 15, tmp + 15); + ast_cli_command_multiple_full(con->uid, con->gid, con->fd, res - 15, tmp + 15); break; } - ast_cli_command_multiple(con->fd, res, tmp); + ast_cli_command_multiple_full(con->uid, con->gid, con->fd, res, tmp); } if (fds[1].revents) { - res = read(con->p[0], tmp, sizeof(tmp)); + res = read_credentials(con->p[0], tmp, sizeof(tmp), con); if (res < 1) { ast_log(LOG_ERROR, "read returned %d\n", res); break; @@ -1072,8 +1116,19 @@ static void *listener(void *unused) if (errno != EINTR) ast_log(LOG_WARNING, "Accept returned %d: %s\n", s, strerror(errno)); } else { - for (x = 0; x < AST_MAX_CONNECTS; x++) { - if (consoles[x].fd < 0) { +#if !defined(SO_PASSCRED) + { +#else + int sckopt = 1; + /* turn on socket credentials passing. */ + if (setsockopt(s, SOL_SOCKET, SO_PASSCRED, &sckopt, sizeof(sckopt)) < 0) { + ast_log(LOG_WARNING, "Unable to turn on socket credentials passing\n"); + } else { +#endif + for (x = 0; x < AST_MAX_CONNECTS; x++) { + if (consoles[x].fd >= 0) { + continue; + } if (socketpair(AF_LOCAL, SOCK_STREAM, 0, consoles[x].p)) { ast_log(LOG_ERROR, "Unable to create pipe: %s\n", strerror(errno)); consoles[x].fd = -1; @@ -1085,6 +1140,10 @@ static void *listener(void *unused) fcntl(consoles[x].p[1], F_SETFL, flags | O_NONBLOCK); consoles[x].fd = s; consoles[x].mute = 1; /* Default is muted, we will un-mute if necessary */ + /* Default uid and gid to -2, so then in cli.c/cli_has_permissions() we will be able + to know if the user didn't send the credentials. */ + consoles[x].uid = -2; + consoles[x].gid = -2; if (ast_pthread_create_detached_background(&consoles[x].t, NULL, netconsole, &consoles[x])) { ast_log(LOG_ERROR, "Unable to spawn thread to handle connection: %s\n", strerror(errno)); close(consoles[x].p[0]); @@ -1095,13 +1154,13 @@ static void *listener(void *unused) } break; } + if (x >= AST_MAX_CONNECTS) { + fdprint(s, "No more connections allowed\n"); + ast_log(LOG_WARNING, "No more connections allowed\n"); + close(s); + } else if (consoles[x].fd > -1) + ast_verb(3, "Remote UNIX connection\n"); } - if (x >= AST_MAX_CONNECTS) { - fdprint(s, "No more connections allowed\n"); - ast_log(LOG_WARNING, "No more connections allowed\n"); - close(s); - } else if (consoles[x].fd > -1) - ast_verb(3, "Remote UNIX connection\n"); } } return NULL; @@ -3350,6 +3409,9 @@ int main(int argc, char *argv[]) exit(1); } + /* loads the cli_permissoins.conf file needed to implement cli restrictions. */ + ast_cli_perms_init(0); + /* AMI is initialized after loading modules because of a potential * conflict between issuing a module reload from manager and * registering manager actions. This will cause reversed locking diff --git a/main/cli.c b/main/cli.c index 979254d04..6152867c1 100644 --- a/main/cli.c +++ b/main/cli.c @@ -33,6 +33,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include #include #include +#include +#include #include "asterisk/cli.h" #include "asterisk/linkedlists.h" @@ -45,6 +47,35 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "editline/readline/readline.h" #include "asterisk/threadstorage.h" +/*! + * \brief List of restrictions per user. + */ +struct cli_perm { + unsigned int permit:1; /*!< 1=Permit 0=Deny */ + char *command; /*!< Command name (to apply restrictions) */ + AST_LIST_ENTRY(cli_perm) list; +}; + +AST_LIST_HEAD_NOLOCK(cli_perm_head, cli_perm); + +/*! \brief list of users to apply restrictions. */ +struct usergroup_cli_perm { + int uid; /*!< User ID (-1 disabled) */ + int gid; /*!< Group ID (-1 disabled) */ + struct cli_perm_head *perms; /*!< List of permissions. */ + AST_LIST_ENTRY(usergroup_cli_perm) list;/*!< List mechanics */ +}; +/*! \brief CLI permissions config file. */ +static const char perms_config[] = "cli_permissions.conf"; +/*! \brief Default permissions value 1=Permit 0=Deny */ +static int cli_default_perm = 1; + +/*! \brief mutex used to prevent a user from running the 'cli reload permissions' command while + * it is already running. */ +AST_MUTEX_DEFINE_STATIC(permsconfiglock); +/*! \brief List of users and permissions. */ +AST_RWLIST_HEAD_STATIC(cli_perms, usergroup_cli_perm); + /*! * \brief map a debug or verbose value to a filename */ @@ -117,6 +148,74 @@ unsigned int ast_verbose_get_by_file(const char *file) return res; } +/*! \internal + * \brief Check if the user with 'uid' and 'gid' is allow to execute 'command', + * if command starts with '_' then not check permissions, just permit + * to run the 'command'. + * if uid == -1 or gid == -1 do not check permissions. + * if uid == -2 and gid == -2 is because rasterisk client didn't send + * the credentials, so the cli_default_perm will be applied. + * \param uid User ID. + * \param gid Group ID. + * \param command Command name to check permissions. + * \retval 1 if has permission + * \retval 0 if it is not allowed. + */ +static int cli_has_permissions(int uid, int gid, const char *command) +{ + struct usergroup_cli_perm *user_perm; + struct cli_perm *perm; + /* set to the default permissions general option. */ + int isallowg = cli_default_perm, isallowu = -1, ispattern; + regex_t regexbuf; + + /* if uid == -1 or gid == -1 do not check permissions. + if uid == -2 and gid == -2 is because rasterisk client didn't send + the credentials, so the cli_default_perm will be applied. */ + if ((uid == CLI_NO_PERMS && gid == CLI_NO_PERMS) || command[0] == '_') { + return 1; + } + + if (gid < 0 && uid < 0) { + return cli_default_perm; + } + + AST_RWLIST_RDLOCK(&cli_perms); + AST_LIST_TRAVERSE(&cli_perms, user_perm, list) { + if (user_perm->gid != gid && user_perm->uid != uid) { + continue; + } + AST_LIST_TRAVERSE(user_perm->perms, perm, list) { + if (strcasecmp(perm->command, "all") && strncasecmp(perm->command, command, strlen(perm->command))) { + /* if the perm->command is a pattern, check it against command. */ + ispattern = !regcomp(®exbuf, perm->command, REG_EXTENDED | REG_NOSUB | REG_ICASE); + if (ispattern && regexec(®exbuf, command, 0, NULL, 0)) { + regfree(®exbuf); + continue; + } + if (!ispattern) { + continue; + } + regfree(®exbuf); + } + if (user_perm->uid == uid) { + /* this is a user definition. */ + isallowu = perm->permit; + } else { + /* otherwise is a group definition. */ + isallowg = perm->permit; + } + } + } + AST_RWLIST_UNLOCK(&cli_perms); + if (isallowu > -1) { + /* user definition override group definition. */ + isallowg = isallowu; + } + + return isallowg; +} + static AST_RWLIST_HEAD_STATIC(helpers, ast_cli_entry); static char *complete_fn(const char *word, int state) @@ -479,6 +578,15 @@ static void print_uptimestr(int fd, struct timeval timeval, const char *prefix, ast_cli(fd, "%s: %s\n", prefix, out->str); } +static struct ast_cli_entry *cli_next(struct ast_cli_entry *e) +{ + if (e) { + return AST_LIST_NEXT(e, list); + } else { + return AST_LIST_FIRST(&helpers); + } +} + static char * handle_showuptime(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct timeval curtime = ast_tvnow(); @@ -753,6 +861,146 @@ static char *handle_softhangup(struct ast_cli_entry *e, int cmd, struct ast_cli_ return CLI_SUCCESS; } +/*! \brief handles CLI command 'cli show permissions' */ +static char *handle_cli_show_permissions(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct usergroup_cli_perm *cp; + struct cli_perm *perm; + struct passwd *pw = NULL; + struct group *gr = NULL; + + switch (cmd) { + case CLI_INIT: + e->command = "cli show permissions"; + e->usage = + "Usage: cli show permissions\n" + " Shows CLI configured permissions.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + AST_RWLIST_RDLOCK(&cli_perms); + AST_LIST_TRAVERSE(&cli_perms, cp, list) { + if (cp->uid >= 0) { + pw = getpwuid(cp->uid); + if (pw) { + ast_cli(a->fd, "user: %s [uid=%d]\n", pw->pw_name, cp->uid); + } + } else { + gr = getgrgid(cp->gid); + if (gr) { + ast_cli(a->fd, "group: %s [gid=%d]\n", gr->gr_name, cp->gid); + } + } + ast_cli(a->fd, "Permissions:\n"); + if (cp->perms) { + AST_LIST_TRAVERSE(cp->perms, perm, list) { + ast_cli(a->fd, "\t%s -> %s\n", perm->permit ? "permit" : "deny", perm->command); + } + } + ast_cli(a->fd, "\n"); + } + AST_RWLIST_UNLOCK(&cli_perms); + + return CLI_SUCCESS; +} + +/*! \brief handles CLI command 'cli reload permissions' */ +static char *handle_cli_reload_permissions(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "cli reload permissions"; + e->usage = + "Usage: cli reload permissions\n" + " Reload the 'cli_permissions.conf' file.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ast_cli_perms_init(1); + + return CLI_SUCCESS; +} + +/*! \brief handles CLI command 'cli check permissions' */ +static char *handle_cli_check_permissions(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct passwd *pw = NULL; + struct group *gr; + int gid = -1, uid = -1; + char command[AST_MAX_ARGS] = ""; + struct ast_cli_entry *ce = NULL; + int found = 0; + char *group, *tmp; + + switch (cmd) { + case CLI_INIT: + e->command = "cli check permissions"; + e->usage = + "Usage: cli check permissions {|@|@} []\n" + " Check permissions config for a user@group or list the allowed commands for the specified user.\n" + " The username or the groupname may be omitted.\n"; + return NULL; + case CLI_GENERATE: + if (a->pos >= 4) { + return ast_cli_generator(a->line + strlen("cli check permissions") + strlen(a->argv[3]) + 1, a->word, a->n); + } + return NULL; + } + + if (a->argc < 4) { + return CLI_SHOWUSAGE; + } + + tmp = ast_strdupa(a->argv[3]); + group = strchr(tmp, '@'); + if (group) { + gr = getgrnam(&group[1]); + if (!gr) { + ast_cli(a->fd, "Unknown group '%s'\n", &group[1]); + return CLI_FAILURE; + } + group[0] = '\0'; + gid = gr->gr_gid; + } + + if (!group && ast_strlen_zero(tmp)) { + ast_cli(a->fd, "You didn't supply a username\n"); + } else if (!ast_strlen_zero(tmp) && !(pw = getpwnam(tmp))) { + ast_cli(a->fd, "Unknown user '%s'\n", tmp); + return CLI_FAILURE; + } else if (pw) { + uid = pw->pw_uid; + } + + if (a->argc == 4) { + while ((ce = cli_next(ce))) { + /* Hide commands that start with '_' */ + if (ce->_full_cmd[0] == '_') { + continue; + } + if (cli_has_permissions(uid, gid, ce->_full_cmd)) { + ast_cli(a->fd, "%30.30s %s\n", ce->_full_cmd, S_OR(ce->summary, "")); + found++; + } + } + if (!found) { + ast_cli(a->fd, "You are not allowed to run any command on Asterisk\n"); + } + } else { + ast_join(command, sizeof(command), a->argv + 4); + ast_cli(a->fd, "%s '%s%s%s' is %s to run command: '%s'\n", uid >= 0 ? "User" : "Group", tmp, + group && uid >= 0 ? "@" : "", + group ? &group[1] : "", + cli_has_permissions(uid, gid, command) ? "allowed" : "not allowed", command); + } + + return CLI_SUCCESS; +} + static char *__ast_cli_generator(const char *text, const char *word, int state, int lock); static char *handle_commandmatchesarray(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) @@ -1176,6 +1424,12 @@ static struct ast_cli_entry cli_cli[] = { AST_CLI_DEFINE(handle_showuptime, "Show uptime information"), AST_CLI_DEFINE(handle_softhangup, "Request a hangup on a given channel"), + + AST_CLI_DEFINE(handle_cli_reload_permissions, "Reload CLI permissions config"), + + AST_CLI_DEFINE(handle_cli_show_permissions, "Show CLI permissions"), + + AST_CLI_DEFINE(handle_cli_check_permissions, "Try a permissions config for a user"), }; /*! @@ -1205,19 +1459,149 @@ static int set_full_cmd(struct ast_cli_entry *e) return 0; } -/*! \brief initialize the _full_cmd string in * each of the builtins. */ -void ast_builtins_init(void) +/*! \brief cleanup (free) cli_perms linkedlist. */ +static void destroy_user_perms (void) { - ast_cli_register_multiple(cli_cli, sizeof(cli_cli) / sizeof(struct ast_cli_entry)); + struct cli_perm *perm; + struct usergroup_cli_perm *user_perm; + + AST_RWLIST_WRLOCK(&cli_perms); + while ((user_perm = AST_LIST_REMOVE_HEAD(&cli_perms, list))) { + while ((perm = AST_LIST_REMOVE_HEAD(user_perm->perms, list))) { + ast_free(perm->command); + ast_free(perm); + } + ast_free(user_perm); + } + AST_RWLIST_UNLOCK(&cli_perms); } -static struct ast_cli_entry *cli_next(struct ast_cli_entry *e) -{ - if (e) { - return AST_LIST_NEXT(e, list); - } else { - return AST_LIST_FIRST(&helpers); +int ast_cli_perms_init(int reload) { + struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + struct ast_config *cfg; + char *cat = NULL; + struct ast_variable *v; + struct usergroup_cli_perm *user_group, *cp_entry; + struct cli_perm *perm = NULL; + struct passwd *pw; + struct group *gr; + + if (ast_mutex_trylock(&permsconfiglock)) { + ast_log(LOG_NOTICE, "You must wait until last 'cli reload permissions' command finish\n"); + return 1; + } + + cfg = ast_config_load2(perms_config, "" /* core, can't reload */, config_flags); + if (!cfg) { + ast_log (LOG_WARNING, "No cli permissions file found (%s)\n", perms_config); + ast_mutex_unlock(&permsconfiglock); + return 1; + } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) { + ast_mutex_unlock(&permsconfiglock); + return 0; } + + /* free current structures. */ + destroy_user_perms(); + + while ((cat = ast_category_browse(cfg, cat))) { + if (!strcasecmp(cat, "general")) { + /* General options */ + for (v = ast_variable_browse(cfg, cat); v; v = v->next) { + if (!strcasecmp(v->name, "default_perm")) { + cli_default_perm = (!strcasecmp(v->value, "permit")) ? 1: 0; + } + } + continue; + } + + /* users or groups */ + gr = NULL, pw = NULL; + if (cat[0] == '@') { + /* This is a group */ + gr = getgrnam(&cat[1]); + if (!gr) { + ast_log (LOG_WARNING, "Unknown group '%s'\n", &cat[1]); + continue; + } + } else { + /* This is a user */ + pw = getpwnam(cat); + if (!pw) { + ast_log (LOG_WARNING, "Unknown user '%s'\n", cat); + continue; + } + } + user_group = NULL; + /* Check for duplicates */ + AST_RWLIST_WRLOCK(&cli_perms); + AST_LIST_TRAVERSE(&cli_perms, cp_entry, list) { + if ((pw && cp_entry->uid == pw->pw_uid) || (gr && cp_entry->gid == gr->gr_gid)) { + /* if it is duplicated, just added this new settings, to + the current list. */ + user_group = cp_entry; + break; + } + } + AST_RWLIST_UNLOCK(&cli_perms); + + if (!user_group) { + /* alloc space for the new user config. */ + user_group = ast_calloc(1, sizeof(*user_group)); + if (!user_group) { + continue; + } + user_group->uid = (pw ? pw->pw_uid : -1); + user_group->gid = (gr ? gr->gr_gid : -1); + user_group->perms = ast_calloc(1, sizeof(*user_group->perms)); + if (!user_group->perms) { + ast_free(user_group); + continue; + } + } + for (v = ast_variable_browse(cfg, cat); v; v = v->next) { + if (ast_strlen_zero(v->value)) { + /* we need to check this condition cause it could break security. */ + ast_log(LOG_WARNING, "Empty permit/deny option in user '%s'\n", cat); + continue; + } + if (!strcasecmp(v->name, "permit")) { + perm = ast_calloc(1, sizeof(*perm)); + if (perm) { + perm->permit = 1; + perm->command = ast_strdup(v->value); + } + } else if (!strcasecmp(v->name, "deny")) { + perm = ast_calloc(1, sizeof(*perm)); + if (perm) { + perm->permit = 0; + perm->command = ast_strdup(v->value); + } + } else { + /* up to now, only 'permit' and 'deny' are possible values. */ + ast_log(LOG_WARNING, "Unknown '%s' option\n", v->name); + continue; + } + if (perm) { + /* Added the permission to the user's list. */ + AST_LIST_INSERT_TAIL(user_group->perms, perm, list); + perm = NULL; + } + } + AST_RWLIST_WRLOCK(&cli_perms); + AST_RWLIST_INSERT_TAIL(&cli_perms, user_group, list); + AST_RWLIST_UNLOCK(&cli_perms); + } + + ast_config_destroy(cfg); + ast_mutex_unlock(&permsconfiglock); + return 0; +} + +/*! \brief initialize the _full_cmd string in * each of the builtins. */ +void ast_builtins_init(void) +{ + ast_cli_register_multiple(cli_cli, sizeof(cli_cli) / sizeof(struct ast_cli_entry)); } /*! @@ -1795,12 +2179,13 @@ char *ast_cli_generator(const char *text, const char *word, int state) return __ast_cli_generator(text, word, state, 1); } -int ast_cli_command(int fd, const char *s) +int ast_cli_command_full(int uid, int gid, int fd, const char *s) { char *args[AST_MAX_ARGS + 1]; struct ast_cli_entry *e; int x; char *duplicate = parse_args(s, &x, args + 1, AST_MAX_ARGS, NULL); + char tmp[AST_MAX_ARGS + 1]; char *retval = NULL; struct ast_cli_args a = { .fd = fd, .argc = x, .argv = args+1 }; @@ -1820,6 +2205,15 @@ int ast_cli_command(int fd, const char *s) ast_cli(fd, "No such command '%s' (type 'core show help %s' for other possible commands)\n", s, find_best(args + 1)); goto done; } + + ast_join(tmp, sizeof(tmp), args + 1); + /* Check if the user has rights to run this command. */ + if (!cli_has_permissions(uid, gid, tmp)) { + ast_cli(fd, "You don't have permissions to run '%s' command\n", tmp); + ast_free(duplicate); + return 0; + } + /* * Within the handler, argv[-1] contains a pointer to the ast_cli_entry. * Remember that the array returned by parse_args is NULL-terminated. @@ -1840,7 +2234,7 @@ done: return 0; } -int ast_cli_command_multiple(int fd, size_t size, const char *s) +int ast_cli_command_multiple_full(int uid, int gid, int fd, size_t size, const char *s) { char cmd[512]; int x, y = 0, count = 0; @@ -1849,7 +2243,7 @@ int ast_cli_command_multiple(int fd, size_t size, const char *s) cmd[y] = s[x]; y++; if (s[x] == '\0') { - ast_cli_command(fd, cmd); + ast_cli_command_full(uid, gid, fd, cmd); y = 0; count++; } -- cgit v1.2.3