Index: pbx/pbx_gtkconsole.c =================================================================== --- pbx/pbx_gtkconsole.c (revision 133769) +++ pbx/pbx_gtkconsole.c (working copy) @@ -323,7 +323,7 @@ { #if 0 /* Clever... */ - ast_cli_command(clipipe[1], "quit"); + ast_cli_command(CLI_NO_PERMS, CLI_NO_PERMS, clipipe[1], "quit"); #else kill(getpid(), SIGTERM); #endif @@ -350,7 +350,7 @@ strncpy(buf, gtk_entry_get_text(GTK_ENTRY(cli)), sizeof(buf) - 1); gtk_entry_set_text(GTK_ENTRY(cli), ""); if (strlen(buf)) { - ast_cli_command(clipipe[1], buf); + ast_cli_command(CLI_NO_PERMS, CLI_NO_PERMS, clipipe[1], buf); } return TRUE; } Index: apps/app_rpt.c =================================================================== --- apps/app_rpt.c (revision 133769) +++ apps/app_rpt.c (working copy) @@ -14014,8 +14014,8 @@ MONITOR_DISK_BLOCKS_PER_MINUTE) / 60; } if (blocksleft >= myrpt->p.monminblocks) - ast_cli_command(nullfd,mycmd); - } else ast_cli_command(nullfd,mycmd); + ast_cli_command(CLI_NO_PERMS, CLI_NO_PERMS, nullfd,mycmd); + } else ast_cli_command(CLI_NO_PERMS, CLI_NO_PERMS, nullfd,mycmd); /* look at callerid to see what node this comes from */ if (!chan->cid.cid_num) /* if doesn't have caller id */ { @@ -14540,7 +14540,7 @@ /* wait for telem to be done */ while(myrpt->tele.next != &myrpt->tele) usleep(100000); sprintf(tmp,"mixmonitor stop %s",chan->name); - ast_cli_command(nullfd,tmp); + ast_cli_command(CLI_NO_PERMS, CLI_NO_PERMS, nullfd,tmp); close(nullfd); rpt_mutex_lock(&myrpt->lock); myrpt->hfscanmode = 0; Index: CHANGES =================================================================== --- CHANGES (revision 133769) +++ CHANGES (working copy) @@ -266,6 +266,12 @@ CLI Changes ----------- + * Added CLI permissions, config file: permissions.conf + default is to allow all commands for every local user/group. + Also this new feature added three new CLI commands: + - cli permissions check {|@|@} [] + - cli permissions reload + - cli permissions show * New CLI command "core show hint" (usage: core show hint ) * New CLI command "core show settings" * Added 'core show channels count' CLI command. Index: include/asterisk/_private.h =================================================================== --- include/asterisk/_private.h (revision 133769) +++ include/asterisk/_private.h (working copy) @@ -24,6 +24,7 @@ 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 */ Index: include/asterisk/cli.h =================================================================== --- include/asterisk/cli.h (revision 133769) +++ include/asterisk/cli.h (working copy) @@ -32,6 +32,10 @@ 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 @@ -197,23 +201,29 @@ /*! * \brief Interprets a command - * Interpret a command s, sending output to fd + * Interpret a command s, sending output to fd if uid has permissions + * to run this command. uid = CLI_NO_PERMS to dont check 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(int uid, int gid, int fd, const char *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 dont check 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(int uid, int gid, int fd, size_t size, const char *s); /*! \brief Registers a command or an array of commands * \param e which cli entry to register. Index: main/config.c =================================================================== --- main/config.c (revision 133769) +++ main/config.c (working copy) @@ -2438,7 +2438,7 @@ if (!strcmp(cfmtime->filename, a->argv[2])) { char *buf = alloca(strlen("module reload ") + strlen(cfmtime->who_asked) + 1); sprintf(buf, "module reload %s", cfmtime->who_asked); - ast_cli_command(a->fd, buf); + ast_cli_command(CLI_NO_PERMS, CLI_NO_PERMS, a->fd, buf); } } AST_LIST_UNLOCK(&cfmtime_head); Index: main/manager.c =================================================================== --- main/manager.c (revision 133769) +++ main/manager.c (working copy) @@ -2211,7 +2211,7 @@ if (!ast_strlen_zero(id)) astman_append(s, "ActionID: %s\r\n", id); /* FIXME: Wedge a ActionID response in here, waiting for later changes */ - ast_cli_command(fd, cmd); /* XXX need to change this to use a FILE * */ + ast_cli_command(CLI_NO_PERMS, CLI_NO_PERMS, fd, cmd); /* XXX need to change this to use a FILE * */ l = lseek(fd, 0, SEEK_END); /* how many chars available */ /* This has a potential to overflow the stack. Hence, use the heap. */ Index: main/asterisk.c =================================================================== --- main/asterisk.c (revision 133769) +++ main/asterisk.c (working copy) @@ -176,6 +176,8 @@ 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 */ }; @@ -995,6 +997,56 @@ 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 + * \return -1 on error, or the number of bytes received. + */ +static int read_credentials(int fd, char *buffer, size_t size, struct console *con) +{ + struct ucred cred; + struct cmsghdr *cmsg; + struct iovec iov; + int result; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = NULL, + .msg_controllen = 0, + .msg_flags = 0 + }; + + memset(&cred, 0, sizeof(cred)); + + iov.iov_len = size; + iov.iov_base = buffer; + + msg.msg_controllen = CMSG_SPACE(sizeof(struct ucred)); + msg.msg_control = ast_calloc(1, msg.msg_controllen); + + result = recvmsg(fd, &msg, 0); + if (result < 0) { + ast_log (LOG_ERROR, "Error receiving credentials msg\n"); + ast_free(msg.msg_control); + return -1; + } + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) { + memcpy(&cred, CMSG_DATA(cmsg), sizeof(cred)); + break; + } + } + con->uid = cred.uid; + con->gid = cred.gid; + ast_free(msg.msg_control); + return result; +} + static void *netconsole(void *vconsole) { struct console *con = vconsole; @@ -1007,6 +1059,7 @@ ast_copy_string(hostname, "", sizeof(hostname)); snprintf(tmp, sizeof(tmp), "%s/%ld/%s\n", hostname, (long)ast_mainpid, ast_get_version()); fdprint(con->fd, tmp); + for (;;) { fds[0].fd = con->fd; fds[0].events = POLLIN; @@ -1022,19 +1075,19 @@ 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(con->uid, con->gid, con->fd, res - 15, tmp + 15); break; } - ast_cli_command_multiple(con->fd, res, tmp); + ast_cli_command_multiple(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; @@ -1061,6 +1114,7 @@ int x; int flags; struct pollfd fds[1]; + int sckopt = 1; for (;;) { if (ast_socket < 0) return NULL; @@ -1079,36 +1133,45 @@ 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 (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; - fdprint(s, "Server failed to create pipe\n"); - close(s); + /* 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 { + for (x = 0; x < AST_MAX_CONNECTS; x++) { + if (consoles[x].fd < 0) { + 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; + fdprint(s, "Server failed to create pipe\n"); + close(s); + break; + } + flags = fcntl(consoles[x].p[1], F_GETFL); + 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]); + close(consoles[x].p[1]); + consoles[x].fd = -1; + fdprint(s, "Server failed to spawn thread\n"); + close(s); + } break; } - flags = fcntl(consoles[x].p[1], F_GETFL); - 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 */ - 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]); - close(consoles[x].p[1]); - consoles[x].fd = -1; - fdprint(s, "Server failed to spawn thread\n"); - close(s); - } - 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; @@ -1514,7 +1577,7 @@ else ast_safe_system(getenv("SHELL") ? getenv("SHELL") : "/bin/sh"); } else - ast_cli_command(STDOUT_FILENO, s); + ast_cli_command(CLI_NO_PERMS, CLI_NO_PERMS, STDOUT_FILENO, s); } static int remoteconsolehandler(char *s) @@ -2884,7 +2947,7 @@ for (v = ast_variable_browse(cfg, "startup_commands"); v; v = v->next) { if (ast_true(v->value)) - ast_cli_command(fd, v->name); + ast_cli_command(CLI_NO_PERMS, CLI_NO_PERMS, fd, v->name); } close(fd); @@ -3387,6 +3450,9 @@ printf("%s", term_quit()); exit(1); } + /* loads the permissoins.conf file needed to implement + cli restrictions. */ + ast_cli_perms_init(0); dnsmgr_start_refresh(); Index: main/cli.c =================================================================== --- main/cli.c (revision 133769) +++ main/cli.c (working copy) @@ -33,6 +33,9 @@ #include #include #include +#include +#include +#include #include "asterisk/cli.h" #include "asterisk/linkedlists.h" @@ -46,6 +49,36 @@ #include "asterisk/threadstorage.h" /*! + * \brief List of restrictions per user. + */ +struct cli_perm { + 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; + AST_LIST_ENTRY(usergroup_cli_perm) list;/*!< List mechanics */ +}; + +static const char perms_config[] = "permissions.conf"; /*!< CLI permissions config file. */ +int default_perm = 1; /*!< Default permissions value 1=Permit 0=Deny */ + +/* mutex used to prevent a user from running the 'cli permissions reload' command while + * it is already running. */ +AST_MUTEX_DEFINE_STATIC(permsconfiglock); +/* List of users and permissions. */ +AST_LIST_HEAD_STATIC(cli_perms, usergroup_cli_perm); + +/*! * \brief map a debug or verbose value to a filename */ struct ast_debug_file { @@ -117,6 +150,60 @@ return res; } +/*! \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'. + * \param uid User ID. + * \param gid Group ID. + * \param command Command name to check permissions. + * \return 1 if has permission, 0 if it is not allowed. + */ +static int cli_has_permissions (int uid, int gid, char *command) { + struct usergroup_cli_perm *user_perm; + struct cli_perm *perm; + + /* set to the default permissions general option. */ + int isallow = default_perm; + + /* 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 default_perm will be applied. */ + if ((uid == CLI_NO_PERMS && gid == CLI_NO_PERMS) || command[0] == '_') return 1; + + if (gid >= 0) { + /* First check group permissions */ + AST_LIST_LOCK(&cli_perms); + AST_LIST_TRAVERSE(&cli_perms, user_perm, list) { + if (user_perm->gid == gid) { + AST_LIST_TRAVERSE(user_perm->perms, perm, list) { + if (!strcasecmp(perm->command, "all") || !strncasecmp(perm->command, command, strlen(perm->command))) { + isallow = perm->permit; + } + } + break; + } + } + AST_LIST_UNLOCK(&cli_perms); + } + if (uid >= 0) { + /* Overwrite gid permissions if user permissions are configured. */ + AST_LIST_LOCK(&cli_perms); + AST_LIST_TRAVERSE(&cli_perms, user_perm, list) { + if (user_perm->uid == uid) { + AST_LIST_TRAVERSE(user_perm->perms, perm, list) { + if (!strcasecmp(perm->command, "all") || !strncasecmp(perm->command, command, strlen(perm->command))) { + isallow = perm->permit; + } + } + break; + } + } + AST_LIST_UNLOCK(&cli_perms); + } + + return isallow; +} + static AST_RWLIST_HEAD_STATIC(helpers, ast_cli_entry); static char *complete_fn(const char *word, int state) @@ -503,6 +590,15 @@ ast_cli(fd, "%s: %s\n", prefix, out->str); } +static struct ast_cli_entry *cli_next(struct ast_cli_entry *e) +{ + if (e == NULL) + e = AST_LIST_FIRST(&helpers); + if (e) + e = AST_LIST_NEXT(e, list); + return e; +} + static char * handle_showuptime(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct timeval curtime = ast_tvnow(); @@ -777,6 +873,140 @@ return CLI_SUCCESS; } +/*! \brief handles CLI command 'cli permissions show' */ +static char *handle_cli_permissions_show(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 permissions show"; + e->usage = + "Usage: cli permissions show\n" + " Shows CLI configured permissions.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + AST_LIST_LOCK(&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_LIST_UNLOCK(&cli_perms); + + return CLI_SUCCESS; +} + +/*! \brief handles CLI command 'cli permissions reload' */ +static char *handle_cli_permissions_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "cli permissions reload"; + e->usage = + "Usage: cli permissions reload\n" + " Reload the 'permissions.conf' file.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ast_cli_perms_init(1); + + return CLI_SUCCESS; +} + +/*! \brief handles CLI command 'cli permissions check' */ +static char *handle_cli_permissions_check(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 permissions check"; + e->usage = + "Usage: cli permissions check {|@|@} []\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: + 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; + /* Hide commands that are marked as deprecated. */ + if (ce->deprecated) + 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) ? "permit" : "deny", 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) @@ -1218,6 +1448,12 @@ 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_permissions_reload, "Reload CLI permissions config"), + + AST_CLI_DEFINE(handle_cli_permissions_show, "Show CLI permissions"), + + AST_CLI_DEFINE(handle_cli_permissions_check, "Try a permissions config for a user"), }; /*! @@ -1247,21 +1483,151 @@ return 0; } +/*! \brief cleanup (free) cli_perms linkedlist. */ +static void destroy_user_perms (void) { + struct cli_perm *perm; + struct usergroup_cli_perm *user_perm; + + AST_LIST_LOCK(&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_LIST_UNLOCK(&cli_perms); + +} + +/*! \brief Loads permissions config file (permissions.conf) + * + * \param reload If reload is 1 do not re-load configuration unless + * the file permissions.conf was changed. + * \return 1 on error, 0 on success. + */ +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 permissions reload' 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")) { + default_perm = (!strcasecmp(v->value, "permit")) ? 1: 0; + } + } + } else { + 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]); + } else { + /* This is a user */ + pw = getpwnam(cat); + if (!pw) + ast_log (LOG_WARNING, "Unknown user '%s'\n", cat); + } + if (pw || gr) { + user_group = NULL; + /* Check for duplicates */ + AST_LIST_LOCK(&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; + } + } + if (!user_group) { + /* alloc space for the new user config. */ + user_group = ast_calloc(1, sizeof(*user_group)); + if (user_group) { + if (pw) { + user_group->uid = pw->pw_uid; + user_group->gid = -1; + } else { + user_group->gid = gr->gr_gid; + user_group->uid = -1; + } + user_group->perms = ast_calloc(1, sizeof(*user_group->perms)); + } + AST_LIST_INSERT_TAIL(&cli_perms, user_group, list); + } + 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); + } + if (perm) { + /* Added the permission to the user's list. */ + AST_LIST_INSERT_TAIL(user_group->perms, perm, list); + perm = NULL; + } + } + AST_LIST_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)); } -static struct ast_cli_entry *cli_next(struct ast_cli_entry *e) -{ - if (e == NULL) - e = AST_LIST_FIRST(&helpers); - if (e) - e = AST_LIST_NEXT(e, list); - return e; -} - /*! * match a word in the CLI entry. * returns -1 on mismatch, 0 on match of an optional word, @@ -1853,12 +2219,13 @@ return __ast_cli_generator(text, word, state, 1); } -int ast_cli_command(int fd, const char *s) +int ast_cli_command(int uid, int gid, int fd, const char *s) { char *args[AST_MAX_ARGS + 1]; struct ast_cli_entry *e; int x; char *dup = 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 }; @@ -1878,6 +2245,15 @@ ast_cli(fd, "No such command '%s' (type '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(dup); + 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. @@ -1908,7 +2284,7 @@ return 0; } -int ast_cli_command_multiple(int fd, size_t size, const char *s) +int ast_cli_command_multiple(int uid, int gid, int fd, size_t size, const char *s) { char cmd[512]; int x, y = 0, count = 0; @@ -1917,7 +2293,7 @@ cmd[y] = s[x]; y++; if (s[x] == '\0') { - ast_cli_command(fd, cmd); + ast_cli_command(uid, gid, fd, cmd); y = 0; count++; } Index: configs/permissions.conf.sample =================================================================== --- configs/permissions.conf.sample (revision 0) +++ configs/permissions.conf.sample (revision 0) @@ -0,0 +1,74 @@ +; +; CLI permissions configuration example for Asterisk +; +; All the users that you want to connect with asterisk using +; rasterisk, should have write/read/execute 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. +; 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 +; +;