Add regular expression filtering for manager events.
authorJeff Peeler <jpeeler@digium.com>
Tue, 22 Jun 2010 16:29:18 +0000 (16:29 +0000)
committerJeff Peeler <jpeeler@digium.com>
Tue, 22 Jun 2010 16:29:18 +0000 (16:29 +0000)
This patch as documented in the sample config allows one to optionally apply
white, black, or both types of filtering to manager events. The new
'eventfilter' option is set per user.

(closes issue #14861)
Reported by: fnordian
Patches:
      eventfilter3.patch uploaded by fnordian (license 110),
      modified by me

Review: https://reviewboard.asterisk.org/r/673/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@271868 65c4cc65-6c06-0410-ace0-fbb531ad65f3

CHANGES
configs/manager.conf.sample
main/manager.c

diff --git a/CHANGES b/CHANGES
index 5adee58..ea0338f 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -402,6 +402,8 @@ Asterisk Manager Interface
    AOC-E messages on a channel.
  * A DBGetComplete event now follows a DBGetResponse, to make the DBGet action
    conform more closely to similar events.
+ * Added a new eventfilter option per user to allow whitelisting and blacklisting
+   of events.
 
 Channel Event Logging
 ---------------------
index c6536bc..bfa1343 100644 (file)
@@ -74,6 +74,22 @@ bindaddr = 0.0.0.0
 ;deny=0.0.0.0/0.0.0.0
 ;permit=209.16.236.73/255.255.255.0
 ;
+;eventfilter=Event: Newchannel
+;eventfilter=!Channel: DAHDI*
+; The eventfilter option is used to whitelist or blacklist events per user to be
+; reported with regular expressions and are allowed if both the regex matches
+; and the user has read access set below. Filters are assumed to be for whitelisting
+; unless preceeded by an exclamation point, which marks it as being black.
+; Evaluation of the filters is as follows:
+; - If no filters are configured all events are reported as normal.
+; - If there are white filters only: implied black all filter processed first,
+; then white filters.
+; - If there are black filters only: implied white all filter processed first,
+; then black filters.
+; - If there are both white and black filters: implied black all filter processed
+; first, then white filters, and lastly black filters.
+
+;
 ; If the device connected via this user accepts input slowly,
 ; the timeout for writes to it can be increased to keep it
 ; from being disconnected (value is in milliseconds)
index 283a841..ab234de 100644 (file)
@@ -51,6 +51,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include <sys/time.h>
 #include <signal.h>
 #include <sys/mman.h>
+#include <sys/types.h>
+#include <regex.h>
 
 #include "asterisk/channel.h"
 #include "asterisk/file.h"
@@ -941,6 +943,8 @@ struct mansession_session {
        int writetimeout;       /*!< Timeout for ast_carefulwrite() */
        int pending_event;         /*!< Pending events indicator in case when waiting_thread is NULL */
        time_t noncetime;       /*!< Timer for nonce value expiration */
+       struct ao2_container *whitefilters;
+       struct ao2_container *blackfilters;
        unsigned long oldnonce; /*!< Stale nonce value */
        unsigned long nc;       /*!< incremental  nonce counter */
        AST_LIST_HEAD_NOLOCK(mansession_datastores, ast_datastore) datastores; /*!< Data stores on the session */
@@ -986,6 +990,8 @@ struct ast_manager_user {
        int writetimeout;               /*! Per user Timeout for ast_carefulwrite() */
        int displayconnects;            /*!< XXX unused */
        int keep;                       /*!< mark entries created on a reload */
+       struct ao2_container *whitefilters;
+       struct ao2_container *blackfilters;
        char *a1_hash;                  /*!< precalculated A1 for Digest auth */
        AST_RWLIST_ENTRY(ast_manager_user) list;
 };
@@ -1212,6 +1218,12 @@ static struct mansession_session *unref_mansession(struct mansession_session *s)
        return s;
 }
 
+static void event_filter_destructor(void *obj)
+{
+       regex_t *regex_filter = obj;
+       regfree(regex_filter);
+}
+
 static void session_destructor(void *obj)
 {
        struct mansession_session *session = obj;
@@ -1230,6 +1242,11 @@ static void session_destructor(void *obj)
        if (eqe) {
                ast_atomic_fetchadd_int(&eqe->usecount, -1);
        }
+
+       ao2_t_callback(session->whitefilters, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL, "unlink all white filters");
+       ao2_t_callback(session->blackfilters, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL, "unlink all black filters");
+       ao2_t_ref(session->whitefilters, -1 , "decrement ref for white container, should be last one");
+       ao2_t_ref(session->blackfilters, -1 , "decrement ref for black container, should be last one");
 }
 
 /*! \brief Allocate manager session structure and add it to the list of sessions */
@@ -2190,6 +2207,8 @@ static int authenticate(struct mansession *s, const struct message *m)
        const char *password = astman_get_header(m, "Secret");
        int error = -1;
        struct ast_manager_user *user = NULL;
+       regex_t *regex_filter;
+       struct ao2_iterator filter_iter;
 
        if (ast_strlen_zero(username)) {        /* missing username */
                return -1;
@@ -2244,10 +2263,30 @@ static int authenticate(struct mansession *s, const struct message *m)
 
        /* auth complete */
 
+       /* All of the user parameters are copied to the session so that in the event
+     * of a reload and a configuration change, the session parameters are not
+     * changed. */
        ast_copy_string(s->session->username, username, sizeof(s->session->username));
        s->session->readperm = user->readperm;
        s->session->writeperm = user->writeperm;
        s->session->writetimeout = user->writetimeout;
+       s->session->whitefilters = ao2_container_alloc(1, NULL, NULL);
+       s->session->blackfilters = ao2_container_alloc(1, NULL, NULL);
+       if (s->session->whitefilters && s->session->blackfilters) {
+               filter_iter = ao2_iterator_init(user->whitefilters, 0);
+               while ((regex_filter = ao2_iterator_next(&filter_iter))) {
+                       ao2_t_link(s->session->whitefilters, regex_filter, "add white user filter to session");
+               }
+               ao2_iterator_destroy(&filter_iter);
+
+               filter_iter = ao2_iterator_init(user->blackfilters, 0);
+               while ((regex_filter = ao2_iterator_next(&filter_iter))) {
+                       ao2_t_link(s->session->blackfilters, regex_filter, "add black user filter to session");
+               }
+               ao2_iterator_destroy(&filter_iter);
+       } else {
+               ast_log(LOG_WARNING, "Allocation for filters failed, no filtering will occur.\n");
+       }
        s->session->sessionstart = time(NULL);
        s->session->sessionstart_tv = ast_tvnow();
        set_eventmask(s, astman_get_header(m, "Events"));
@@ -3966,6 +4005,59 @@ static int action_timeout(struct mansession *s, const struct message *m)
        return 0;
 }
 
+static int whitefilter_cmp_fn(void *obj, void *arg, void *data, int flags)
+{
+       regex_t *regex_filter = obj;
+       const char *eventdata = arg;
+       int *result = data;
+
+       if (!regexec(regex_filter, eventdata, 0, NULL, 0)) {
+               *result = 1;
+               return (CMP_MATCH | CMP_STOP);
+       }
+
+       return 0;
+}
+
+static int blackfilter_cmp_fn(void *obj, void *arg, void *data, int flags)
+{
+       regex_t *regex_filter = obj;
+       const char *eventdata = arg;
+       int *result = data;
+
+       if (regexec(regex_filter, eventdata, 0, NULL, 0)) {
+               *result = 1;
+               return (CMP_MATCH | CMP_STOP);
+       }
+
+       return 0;
+}
+
+static int match_filter(struct mansession *s, char *eventdata)
+{
+       int result = 0;
+
+       ast_debug(3, "Examining event:\n%s\n", eventdata);
+       if (!ao2_container_count(s->session->whitefilters) && !ao2_container_count(s->session->blackfilters)) {
+               return 1; /* no filtering means match all */
+       } else if (ao2_container_count(s->session->whitefilters) && !ao2_container_count(s->session->blackfilters)) {
+               /* white filters only: implied black all filter processed first, then white filters */
+               ao2_t_callback_data(s->session->whitefilters, OBJ_NODATA, whitefilter_cmp_fn, eventdata, &result, "find filter in session filter container"); 
+       } else if (!ao2_container_count(s->session->whitefilters) && ao2_container_count(s->session->blackfilters)) {
+               /* black filters only: implied white all filter processed first, then black filters */
+               ao2_t_callback_data(s->session->blackfilters, OBJ_NODATA, blackfilter_cmp_fn, eventdata, &result, "find filter in session filter container"); 
+       } else {
+               /* white and black filters: implied black all filter processed first, then white filters, and lastly black filters */
+               ao2_t_callback_data(s->session->whitefilters, OBJ_NODATA, whitefilter_cmp_fn, eventdata, &result, "find filter in session filter container"); 
+               if (result) {
+                       result = 0;
+                       ao2_t_callback_data(s->session->blackfilters, OBJ_NODATA, blackfilter_cmp_fn, eventdata, &result, "find filter in session filter container"); 
+               }
+       }
+
+       return result;
+}
+
 /*!
  * Send any applicable events to the client listening on this socket.
  * Wait only for a finite time on each event, and drop all events whether
@@ -3983,9 +4075,10 @@ static int process_events(struct mansession *s)
                        if (!ret && s->session->authenticated &&
                            (s->session->readperm & eqe->category) == eqe->category &&
                            (s->session->send_events & eqe->category) == eqe->category) {
-                               if (send_string(s, eqe->eventdata) < 0) {
-                                       ret = -1;       /* don't send more */
-                               }
+                                       if (match_filter(s, eqe->eventdata)) {
+                                               if (send_string(s, eqe->eventdata) < 0)
+                                                       ret = -1;       /* don't send more */
+                                       }
                        }
                        s->session->last_ev = eqe;
                }
@@ -6227,9 +6320,14 @@ static int __init_manager(int reload)
                        /* Default displayconnect from [general] */
                        user->displayconnects = displayconnects;
                        user->writetimeout = 100;
+                       user->whitefilters = ao2_container_alloc(1, NULL, NULL);
+                       user->blackfilters = ao2_container_alloc(1, NULL, NULL);
 
                        /* Insert into list */
                        AST_RWLIST_INSERT_TAIL(&users, user, list);
+               } else {
+                       ao2_t_callback(user->whitefilters, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL, "unlink all white filters");
+                       ao2_t_callback(user->blackfilters, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL, "unlink all black filters");
                }
 
                /* Make sure we keep this user and don't destroy it during cleanup */
@@ -6260,6 +6358,27 @@ static int __init_manager(int reload)
                                } else {
                                        user->writetimeout = value;
                                }
+                       } else if (!strcasecmp(var->name, "eventfilter")) {
+                               const char *value = var->value;
+                               regex_t *new_filter = ao2_t_alloc(sizeof(*new_filter), event_filter_destructor, "event_filter allocation");
+                               if (new_filter) {
+                                       int is_blackfilter;
+                                       if (value[0] == '!') {
+                                               is_blackfilter = 1;
+                                               value++;
+                                       } else {
+                                               is_blackfilter = 0;
+                                       }
+                                       if (regcomp(new_filter, value, 0)) {
+                                               ao2_t_ref(new_filter, -1, "failed to make regx");
+                                       } else {
+                                               if (is_blackfilter) {
+                                                       ao2_t_link(user->blackfilters, new_filter, "link new filter into black user container");
+                                               } else {
+                                                       ao2_t_link(user->whitefilters, new_filter, "link new filter into white user container");
+                                               }
+                                       }
+                               }
                        } else {
                                ast_debug(1, "%s is an unknown option.\n", var->name);
                        }
@@ -6284,6 +6403,7 @@ static int __init_manager(int reload)
                }
                /* We do not need to keep this user so take them out of the list */
                AST_RWLIST_REMOVE_CURRENT(list);
+               ast_debug(4, "Pruning user '%s'\n", user->username);
                /* Free their memory now */
                if (user->a1_hash) {
                        ast_free(user->a1_hash);
@@ -6291,6 +6411,10 @@ static int __init_manager(int reload)
                if (user->secret) {
                        ast_free(user->secret);
                }
+               ao2_t_callback(user->whitefilters, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL, "unlink all white filters");
+               ao2_t_callback(user->blackfilters, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL, "unlink all black filters");
+               ao2_t_ref(user->whitefilters, -1, "decrement ref for white container, should be last one");
+               ao2_t_ref(user->blackfilters, -1, "decrement ref for black container, should be last one");
                ast_free_ha(user->ha);
                ast_free(user);
        }