[asterisk-commits] eliel: branch eliel/cli-permissions r135298 - in /team/eliel/cli-permissions:...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Sat Aug 2 01:23:13 CDT 2008


Author: eliel
Date: Sat Aug  2 01:23:12 2008
New Revision: 135298

URL: http://svn.digium.com/view/asterisk?view=rev&rev=135298
Log:
Implement CLI permissions
Restrict users to run only a subset of all the CLI commands.
Read/Write access to the socket asterisk.ctl is needed (check asterisk.conf).
CLI commands implemented:
	cli permissions show    (shows permissions configuration)
	cli permissions check   (try to run a CLI command)
	cli permissions reload  (reload configuration)
CLI permissions configuration file is 'permissions.conf'


Added:
    team/eliel/cli-permissions/configs/permissions.conf.sample   (with props)
Modified:
    team/eliel/cli-permissions/CHANGES
    team/eliel/cli-permissions/apps/app_rpt.c
    team/eliel/cli-permissions/include/asterisk/_private.h
    team/eliel/cli-permissions/include/asterisk/cli.h
    team/eliel/cli-permissions/main/asterisk.c
    team/eliel/cli-permissions/main/cli.c
    team/eliel/cli-permissions/main/config.c
    team/eliel/cli-permissions/main/manager.c
    team/eliel/cli-permissions/pbx/pbx_gtkconsole.c

Modified: team/eliel/cli-permissions/CHANGES
URL: http://svn.digium.com/view/asterisk/team/eliel/cli-permissions/CHANGES?view=diff&rev=135298&r1=135297&r2=135298
==============================================================================
--- team/eliel/cli-permissions/CHANGES (original)
+++ team/eliel/cli-permissions/CHANGES Sat Aug  2 01:23:12 2008
@@ -288,6 +288,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 {<username>|@<groupname>|<username>@<groupname>} [<command>]
+      - cli permissions reload
+      - cli permissions show 
   * New CLI command "core show hint" (usage: core show hint <exten>)
   * New CLI command "core show settings"
   * Added 'core show channels count' CLI command.

Modified: team/eliel/cli-permissions/apps/app_rpt.c
URL: http://svn.digium.com/view/asterisk/team/eliel/cli-permissions/apps/app_rpt.c?view=diff&rev=135298&r1=135297&r2=135298
==============================================================================
--- team/eliel/cli-permissions/apps/app_rpt.c (original)
+++ team/eliel/cli-permissions/apps/app_rpt.c Sat Aug  2 01:23:12 2008
@@ -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;

Added: team/eliel/cli-permissions/configs/permissions.conf.sample
URL: http://svn.digium.com/view/asterisk/team/eliel/cli-permissions/configs/permissions.conf.sample?view=auto&rev=135298
==============================================================================
--- team/eliel/cli-permissions/configs/permissions.conf.sample (added)
+++ team/eliel/cli-permissions/configs/permissions.conf.sample Sat Aug  2 01:23:12 2008
@@ -1,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 = <command name> | all		; allow the user to run 'command' |
+;					; allow the user to run 'all' the commands
+; deny = <command name> | 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
+;
+;

Propchange: team/eliel/cli-permissions/configs/permissions.conf.sample
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/eliel/cli-permissions/configs/permissions.conf.sample
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: team/eliel/cli-permissions/configs/permissions.conf.sample
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: team/eliel/cli-permissions/include/asterisk/_private.h
URL: http://svn.digium.com/view/asterisk/team/eliel/cli-permissions/include/asterisk/_private.h?view=diff&rev=135298&r1=135297&r2=135298
==============================================================================
--- team/eliel/cli-permissions/include/asterisk/_private.h (original)
+++ team/eliel/cli-permissions/include/asterisk/_private.h Sat Aug  2 01:23:12 2008
@@ -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 */

Modified: team/eliel/cli-permissions/include/asterisk/cli.h
URL: http://svn.digium.com/view/asterisk/team/eliel/cli-permissions/include/asterisk/cli.h?view=diff&rev=135298&r1=135297&r2=135298
==============================================================================
--- team/eliel/cli-permissions/include/asterisk/cli.h (original)
+++ team/eliel/cli-permissions/include/asterisk/cli.h Sat Aug  2 01:23:12 2008
@@ -31,6 +31,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
@@ -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.

Modified: team/eliel/cli-permissions/main/asterisk.c
URL: http://svn.digium.com/view/asterisk/team/eliel/cli-permissions/main/asterisk.c?view=diff&rev=135298&r1=135297&r2=135298
==============================================================================
--- team/eliel/cli-permissions/main/asterisk.c (original)
+++ team/eliel/cli-permissions/main/asterisk.c Sat Aug  2 01:23:12 2008
@@ -174,6 +174,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 */
 };
 
@@ -993,6 +995,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;
@@ -1005,6 +1057,7 @@
 		ast_copy_string(hostname, "<Unknown>", 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;
@@ -1020,19 +1073,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;
@@ -1059,6 +1112,7 @@
 	int x;
 	int flags;
 	struct pollfd fds[1];
+	int sckopt = 1;
 	for (;;) {
 		if (ast_socket < 0)
 			return NULL;
@@ -1077,36 +1131,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;
@@ -1512,7 +1575,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)
@@ -2875,7 +2938,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);
@@ -3378,6 +3441,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();
 

Modified: team/eliel/cli-permissions/main/cli.c
URL: http://svn.digium.com/view/asterisk/team/eliel/cli-permissions/main/cli.c?view=diff&rev=135298&r1=135297&r2=135298
==============================================================================
--- team/eliel/cli-permissions/main/cli.c (original)
+++ team/eliel/cli-permissions/main/cli.c Sat Aug  2 01:23:12 2008
@@ -33,6 +33,9 @@
 #include <signal.h>
 #include <ctype.h>
 #include <regex.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
 
 #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 {
@@ -115,6 +148,60 @@
 	AST_RWLIST_UNLOCK(&verbose_files);
 
 	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);
@@ -501,6 +588,15 @@
 	if (x > 0 || out->used == 0)	/* if there is nothing, print 0 seconds */
 		ast_str_append(&out, 0, "%d second%s ", x, ESS(x));
 	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)
@@ -774,6 +870,140 @@
 		ast_channel_unlock(c);
 	} else
 		ast_cli(a->fd, "%s is not a known channel\n", a->argv[2]);
+	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 {<username>|@<groupname>|<username>@<groupname>} [<command>]\n"
+			"       Check permissions config for a user at 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, "<no description available>"));
+				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;
 }
 
@@ -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,19 +1483,149 @@
 	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;
 }
 
 /*!
@@ -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++;
 		}

Modified: team/eliel/cli-permissions/main/config.c
URL: http://svn.digium.com/view/asterisk/team/eliel/cli-permissions/main/config.c?view=diff&rev=135298&r1=135297&r2=135298
==============================================================================
--- team/eliel/cli-permissions/main/config.c (original)
+++ team/eliel/cli-permissions/main/config.c Sat Aug  2 01:23:12 2008
@@ -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);

Modified: team/eliel/cli-permissions/main/manager.c
URL: http://svn.digium.com/view/asterisk/team/eliel/cli-permissions/main/manager.c?view=diff&rev=135298&r1=135297&r2=135298
==============================================================================
--- team/eliel/cli-permissions/main/manager.c (original)
+++ team/eliel/cli-permissions/main/manager.c Sat Aug  2 01:23:12 2008
@@ -2197,7 +2197,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. */

Modified: team/eliel/cli-permissions/pbx/pbx_gtkconsole.c
URL: http://svn.digium.com/view/asterisk/team/eliel/cli-permissions/pbx/pbx_gtkconsole.c?view=diff&rev=135298&r1=135297&r2=135298
==============================================================================
--- team/eliel/cli-permissions/pbx/pbx_gtkconsole.c (original)
+++ team/eliel/cli-permissions/pbx/pbx_gtkconsole.c Sat Aug  2 01:23:12 2008
@@ -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;
 }




More information about the asterisk-commits mailing list