Named ACLs: Introduces a system for creating and sharing ACLs
authorJonathan Rose <jrose@digium.com>
Wed, 11 Jul 2012 18:33:36 +0000 (18:33 +0000)
committerJonathan Rose <jrose@digium.com>
Wed, 11 Jul 2012 18:33:36 +0000 (18:33 +0000)
This patch adds Named ACL functionality to Asterisk. This allows system
administrators to define an ACL and refer to it by a unique name. Configurable
items can then refer to that name when specifying access control lists.
It also includes updates to all core supported consumers of ACLs. That includes
manager, chan_sip, and chan_iax2. This feature is based on the deluxepine-trunk
by Olle E. Johansson and provides a subset of the Named ACL functionality
implemented in that branch. For more information on this feature, see acl.conf
and/or the Asterisk wiki.

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

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

19 files changed:
CHANGES
channels/chan_iax2.c
channels/chan_sip.c
channels/sip/include/sip.h
configs/acl.conf.sample [new file with mode: 0644]
configs/extconfig.conf.sample
configs/iax.conf.sample
configs/manager.conf.sample
configs/sip.conf.sample
include/asterisk/acl.h
include/asterisk/channel.h
include/asterisk/config.h
include/asterisk/event_defs.h
main/acl.c
main/asterisk.c
main/config.c
main/loader.c
main/manager.c
main/named_acl.c [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
index 1caf2dd..dd26544 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -46,6 +46,12 @@ Core
    have their own verbosity level.  The command 'core set verbose' will now set
    a separate level for each remote console without affecting any other
    console.
+ * Named ACLs can now be specified in acl.conf and used in configurations that
+   use ACLs. As a general rule, if some derivative of 'permit' or 'deny' is
+   used to specify an ACL, a similar form of 'acl' will add a named ACL to the
+   working ACL. In addition, some CLI commands have
+   been added to provide informational and configuration reload capabilities to
+   this feature ('acl show [named acl]' and 'reload acl').
  * Hangup handlers can be attached to channels using the CHANNEL(hangup_handler_xxx)
    options.  Hangup handlers will run when the channel is hung up similar to the
    h extension.
index ad6cdd1..cc51624 100644 (file)
@@ -275,6 +275,7 @@ static char language[MAX_LANGUAGE] = "";
 static char regcontext[AST_MAX_CONTEXT] = "";
 
 static struct ast_event_sub *network_change_event_subscription; /*!< subscription id for network change events */
+static struct ast_event_sub *acl_change_event_subscription; /*!< subscription id for ACL change events */
 static int network_change_event_sched_id = -1;
 
 static int maxauthreq = 3;
@@ -438,7 +439,7 @@ struct iax2_context {
 #define IAX_SHRINKCALLERID      (uint64_t)(1 << 31)   /*!< Turn on and off caller id shrinking */
 static int global_rtautoclear = 120;
 
-static int reload_config(void);
+static int reload_config(int forced_reload);
 
 /*!
  * \brief Call token validation settings.
@@ -479,7 +480,7 @@ struct iax2_user {
        int maxauthreq; /*!< Maximum allowed outstanding AUTHREQs */
        int curauthreq; /*!< Current number of outstanding AUTHREQs */
        struct ast_codec_pref prefs;
-       struct ast_ha *ha;
+       struct ast_acl_list *acl;
        struct iax2_context *contexts;
        struct ast_variable *vars;
        enum calltoken_peer_enum calltoken_required;        /*!< Is calltoken validation required or not, can be YES, NO, or AUTO */
@@ -539,7 +540,7 @@ struct iax2_peer {
 
        struct ast_event_sub *mwi_event_sub;
 
-       struct ast_ha *ha;
+       struct ast_acl_list *acl;
        enum calltoken_peer_enum calltoken_required;        /*!< Is calltoken validation required or not, can be YES, NO, or AUTO */
 };
 
@@ -1242,6 +1243,7 @@ static struct callno_entry *get_unused_callno(int trunk, int validated);
 static int replace_callno(const void *obj);
 static void sched_delay_remove(struct sockaddr_in *sin, struct callno_entry *callno_entry);
 static void network_change_event_cb(const struct ast_event *, void *);
+static void acl_change_event_cb(const struct ast_event *, void *);
 
 static struct ast_channel_tech iax2_tech = {
        .type = "IAX2",
@@ -1324,6 +1326,21 @@ static void network_change_event_unsubscribe(void)
        }
 }
 
+static void acl_change_event_subscribe(void)
+{
+       if (!acl_change_event_subscription) {
+               acl_change_event_subscription = ast_event_subscribe(AST_EVENT_ACL_CHANGE,
+                       acl_change_event_cb, "IAX2 ACL Change", NULL, AST_EVENT_IE_END);
+       }
+}
+
+static void acl_change_event_unsubscribe(void)
+{
+       if (acl_change_event_subscription) {
+               acl_change_event_subscription = ast_event_unsubscribe(acl_change_event_subscription);
+       }
+}
+
 static int network_change_event_sched_cb(const void *data)
 {
        struct iax2_registry *reg;
@@ -1346,6 +1363,12 @@ static void network_change_event_cb(const struct ast_event *event, void *userdat
 
 }
 
+static void acl_change_event_cb(const struct ast_event *event, void *userdata)
+{
+       ast_log(LOG_NOTICE, "Reloading chan_iax2 in response to ACL change event.\n");
+       reload_config(1);
+}
+
 
 /*! \brief Send manager event at call setup to link between Asterisk channel name
        and IAX2 call identifiers */
@@ -3818,7 +3841,7 @@ static char *handle_cli_iax2_show_peer(struct ast_cli_entry *e, int cmd, struct
                ast_cli(a->fd, "  Encryption   : %s\n", peer->encmethods ? ast_str_buffer(encmethods) : "No");
                ast_cli(a->fd, "  Callerid     : %s\n", ast_callerid_merge(cbuf, sizeof(cbuf), peer->cid_name, peer->cid_num, "<unspecified>"));
                ast_cli(a->fd, "  Expire       : %d\n", peer->expire);
-               ast_cli(a->fd, "  ACL          : %s\n", (peer->ha ? "Yes" : "No"));
+               ast_cli(a->fd, "  ACL          : %s\n", (ast_acl_list_is_empty(peer->acl) ? "No" : "Yes"));
                ast_cli(a->fd, "  Addr->IP     : %s Port %d\n",  peer_addr.sin_addr.s_addr ? ast_inet_ntoa(peer_addr.sin_addr) : "(Unspecified)", ntohs(peer_addr.sin_port));
                ast_cli(a->fd, "  Defaddr->IP  : %s Port %d\n", ast_inet_ntoa(peer->defaddr.sin_addr), ntohs(peer->defaddr.sin_port));
                ast_cli(a->fd, "  Username     : %s\n", peer->username);
@@ -6701,7 +6724,7 @@ static char *handle_cli_iax2_show_users(struct ast_cli_entry *e, int cmd, struct
 
                ast_cli(a->fd, FORMAT2, user->name, auth, user->authmethods, 
                        user->contexts ? user->contexts->context : DEFAULT_CONTEXT,
-                       user->ha ? "Yes" : "No", pstr);
+                       ast_acl_list_is_empty(user->acl) ? "No" : "Yes", pstr);
        }
        ao2_iterator_destroy(&i);
 
@@ -7681,7 +7704,7 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies
        while ((user = ao2_iterator_next(&i))) {
                if ((ast_strlen_zero(iaxs[callno]->username) ||                         /* No username specified */
                        !strcmp(iaxs[callno]->username, user->name))    /* Or this username specified */
-                       && ast_apply_ha(user->ha, &addr)        /* Access is permitted from this IP */
+                       && ast_apply_acl(user->acl, &addr, "IAX2 user ACL: ")   /* Access is permitted from this IP */
                        && (ast_strlen_zero(iaxs[callno]->context) ||                   /* No context specified */
                             apply_context(user->contexts, iaxs[callno]->context))) {                   /* Context is permitted */
                        if (!ast_strlen_zero(iaxs[callno]->username)) {
@@ -7692,7 +7715,7 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies
                                break;
                        } else if (ast_strlen_zero(user->secret) && ast_strlen_zero(user->dbsecret) && ast_strlen_zero(user->inkeys)) {
                                /* No required authentication */
-                               if (user->ha) {
+                               if (user->acl) {
                                        /* There was host authentication and we passed, bonus! */
                                        if (bestscore < 4) {
                                                bestscore = 4;
@@ -7712,7 +7735,7 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies
                                        }
                                }
                        } else {
-                               if (user->ha) {
+                               if (user->acl) {
                                        /* Authentication, but host access too, eh, it's something.. */
                                        if (bestscore < 2) {
                                                bestscore = 2;
@@ -8072,7 +8095,7 @@ static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies *
        }
 
        ast_sockaddr_from_sin(&addr, sin);
-       if (!ast_apply_ha(p->ha, &addr)) {
+       if (!ast_apply_acl(p->acl, &addr, "IAX2 Peer ACL: ")) {
                if (authdebug)
                        ast_log(LOG_NOTICE, "Host %s denied access to register peer '%s'\n", ast_inet_ntoa(sin->sin_addr), p->name);
                goto return_unref;
@@ -12514,7 +12537,7 @@ static void peer_destructor(void *obj)
        struct iax2_peer *peer = obj;
        int callno = peer->callno;
 
-       ast_free_ha(peer->ha);
+       ast_free_acl_list(peer->acl);
 
        if (callno > 0) {
                ast_mutex_lock(&iaxsl[callno]);
@@ -12537,10 +12560,11 @@ static void peer_destructor(void *obj)
 static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly)
 {
        struct iax2_peer *peer = NULL;
-       struct ast_ha *oldha = NULL;
+       struct ast_acl_list *oldacl = NULL;
        int maskfound = 0;
        int found = 0;
        int firstpass = 1;
+       int subscribe_acl_change = 0;
 
        if (!temponly) {
                peer = ao2_find(peers, name, OBJ_KEY);
@@ -12551,8 +12575,8 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st
        if (peer) {
                found++;
                if (firstpass) {
-                       oldha = peer->ha;
-                       peer->ha = NULL;
+                       oldacl = peer->acl;
+                       peer->acl = NULL;
                }
                unlink_peer(peer);
        } else if ((peer = ao2_alloc(sizeof(*peer), peer_destructor))) {
@@ -12683,8 +12707,9 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st
                        } else if (!strcasecmp(v->name, "sourceaddress")) {
                                peer_set_srcaddr(peer, v->value);
                        } else if (!strcasecmp(v->name, "permit") ||
-                                          !strcasecmp(v->name, "deny")) {
-                               peer->ha = ast_append_ha(v->name, v->value, peer->ha, NULL);
+                                          !strcasecmp(v->name, "deny") ||
+                                          !strcasecmp(v->name, "acl")) {
+                               ast_append_acl(v->name, v->value, &peer->acl, NULL, &subscribe_acl_change);
                        } else if (!strcasecmp(v->name, "mask")) {
                                maskfound++;
                                inet_aton(v->value, &peer->mask);
@@ -12795,8 +12820,8 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st
                ast_clear_flag64(peer, IAX_DELME);
        }
 
-       if (oldha)
-               ast_free_ha(oldha);
+       if (oldacl)
+               ast_free_acl_list(oldacl);
 
        if (!ast_strlen_zero(peer->mailbox)) {
                char *mailbox, *context;
@@ -12810,6 +12835,10 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st
                        AST_EVENT_IE_END);
        }
 
+       if (subscribe_acl_change) {
+               acl_change_event_subscribe();
+       }
+
        return peer;
 }
 
@@ -12817,7 +12846,7 @@ static void user_destructor(void *obj)
 {
        struct iax2_user *user = obj;
 
-       ast_free_ha(user->ha);
+       ast_free_acl_list(user->acl);
        free_context(user->contexts);
        if(user->vars) {
                ast_variables_destroy(user->vars);
@@ -12831,11 +12860,12 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st
 {
        struct iax2_user *user = NULL;
        struct iax2_context *con, *conl = NULL;
-       struct ast_ha *oldha = NULL;
+       struct ast_acl_list *oldacl = NULL;
        struct iax2_context *oldcon = NULL;
        int format;
        int firstpass=1;
        int oldcurauthreq = 0;
+       int subscribe_acl_change = 0;
        char *varname = NULL, *varval = NULL;
        struct ast_variable *tmpvar = NULL;
 
@@ -12848,9 +12878,9 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st
        if (user) {
                if (firstpass) {
                        oldcurauthreq = user->curauthreq;
-                       oldha = user->ha;
+                       oldacl = user->acl;
                        oldcon = user->contexts;
-                       user->ha = NULL;
+                       user->acl = NULL;
                        user->contexts = NULL;
                }
                /* Already in the list, remove it and it will be added back (or FREE'd) */
@@ -12899,8 +12929,9 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st
                                        conl = con;
                                }
                        } else if (!strcasecmp(v->name, "permit") ||
-                                          !strcasecmp(v->name, "deny")) {
-                               user->ha = ast_append_ha(v->name, v->value, user->ha, NULL);
+                                          !strcasecmp(v->name, "deny") ||
+                                          !strcasecmp(v->name, "acl")) {
+                               ast_append_acl(v->name, v->value, &user->acl, NULL, &subscribe_acl_change);
                        } else if (!strcasecmp(v->name, "setvar")) {
                                varname = ast_strdupa(v->value);
                                if (varname && (varval = strchr(varname,'='))) {
@@ -13069,10 +13100,17 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st
                ast_clear_flag64(user, IAX_DELME);
        }
 cleanup:
-       if (oldha)
-               ast_free_ha(oldha);
-       if (oldcon)
+       if (oldacl) {
+               ast_free_acl_list(oldacl);
+       }
+       if (oldcon) {
                free_context(oldcon);
+       }
+
+       if (subscribe_acl_change) {
+               acl_change_event_subscribe();
+       }
+
        return user;
 }
 
@@ -13171,7 +13209,7 @@ static void set_config_destroy(void)
 }
 
 /*! \brief Load configuration */
-static int set_config(const char *config_file, int reload)
+static int set_config(const char *config_file, int reload, int forced)
 {
        struct ast_config *cfg, *ucfg;
        iax2_format capability = iax2_capability;
@@ -13187,7 +13225,7 @@ static int set_config(const char *config_file, int reload)
        struct iax2_user *user;
        struct iax2_peer *peer;
        struct ast_netsock *ns;
-       struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+       struct ast_flags config_flags = { (reload && !forced) ? CONFIG_FLAG_FILEUNCHANGED : 0 };
 #if 0
        static unsigned short int last_port=0;
 #endif
@@ -13667,12 +13705,12 @@ static void poke_all_peers(void)
        }
        ao2_iterator_destroy(&i);
 }
-static int reload_config(void)
+static int reload_config(int forced_reload)
 {
        static const char config[] = "iax.conf";
        struct iax2_registry *reg;
 
-       if (set_config(config, 1) > 0) {
+       if (set_config(config, 1, forced_reload) > 0) {
                prune_peers();
                prune_users();
                ao2_callback(callno_limits, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, prune_addr_range_cb, NULL);
@@ -13711,14 +13749,14 @@ static char *handle_cli_iax2_reload(struct ast_cli_entry *e, int cmd, struct ast
                return NULL;
        }
 
-       reload_config();
+       reload_config(0);
 
        return CLI_SUCCESS;
 }
 
 static int reload(void)
 {
-       return reload_config();
+       return reload_config(0);
 }
 
 static int cache_get_callno_locked(const char *data)
@@ -14483,6 +14521,7 @@ static int __unload_module(void)
        int x;
 
        network_change_event_unsubscribe();
+       acl_change_event_unsubscribe();
 
        ast_manager_unregister("IAXpeers");
        ast_manager_unregister("IAXpeerlist");
@@ -14818,7 +14857,7 @@ static int users_data_provider_get(const struct ast_data_search *search,
                ast_data_add_int(data_enum_node, "value", user->amaflags);
                ast_data_add_str(data_enum_node, "text", ast_cdr_flags2str(user->amaflags));
 
-               ast_data_add_bool(data_user, "access-control", user->ha ? 1 : 0);
+               ast_data_add_bool(data_user, "access-control", ast_acl_list_is_empty(user->acl) ? 0 : 1);
 
                if (ast_test_flag64(user, IAX_CODEC_NOCAP)) {
                        pstr = "REQ only";
@@ -14922,7 +14961,7 @@ static int load_module(void)
                ast_timer_set_rate(timer, 1000 / trunkfreq);
        }
 
-       if (set_config(config, 0) == -1) {
+       if (set_config(config, 0, 0) == -1) {
                if (timer) {
                        ast_timer_close(timer);
                }
index 70d645e..0fee3a5 100644 (file)
@@ -793,6 +793,7 @@ static struct ast_flags global_flags[3] = {{0}};  /*!< global SIP_ flags */
 static int global_t38_maxdatagram;                /*!< global T.38 FaxMaxDatagram override */
 
 static struct ast_event_sub *network_change_event_subscription; /*!< subscription id for network change events */
+static struct ast_event_sub *acl_change_event_subscription; /*!< subscription id for named ACL system change events */
 static int network_change_event_sched_id = -1;
 
 static char used_context[AST_MAX_CONTEXT];        /*!< name of automatically created context for unloading */
@@ -1407,6 +1408,7 @@ static void sip_poke_all_peers(void);
 static void sip_peer_hold(struct sip_pvt *p, int hold);
 static void mwi_event_cb(const struct ast_event *, void *);
 static void network_change_event_cb(const struct ast_event *, void *);
+static void acl_change_event_cb(const struct ast_event *event, void *userdata);
 static void sip_keepalive_all_peers(void);
 
 /*--- Applications, functions, CLI and manager command helpers */
@@ -4711,8 +4713,8 @@ static void sip_destroy_peer(struct sip_peer *peer)
        }
 
        register_peer_exten(peer, FALSE);
-       ast_free_ha(peer->ha);
-       ast_free_ha(peer->directmediaha);
+       ast_free_acl_list(peer->acl);
+       ast_free_acl_list(peer->directmediaacl);
        if (peer->selfdestruct)
                ast_atomic_fetchadd_int(&apeerobjs, -1);
        else if (!ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) && peer->is_realtime) {
@@ -5499,7 +5501,9 @@ static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer)
                dialog->noncodeccapability |= AST_RTP_DTMF;
        else
                dialog->noncodeccapability &= ~AST_RTP_DTMF;
-       dialog->directmediaha = ast_duplicate_ha_list(peer->directmediaha);
+
+       dialog->directmediaacl = ast_duplicate_acl_list(peer->directmediaacl);
+
        if (peer->call_limit)
                ast_set_flag(&dialog->flags[0], SIP_CALL_LIMIT);
        if (!dialog->portinuri)
@@ -5992,9 +5996,8 @@ void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist)
                p->tsrtp = NULL;
        }
 
-       if (p->directmediaha) {
-               ast_free_ha(p->directmediaha);
-               p->directmediaha = NULL;
+       if (p->directmediaacl) {
+               p->directmediaacl = ast_free_acl_list(p->directmediaacl);
        }
 
        ast_string_field_free_memory(p);
@@ -15034,8 +15037,8 @@ static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, st
        }
 
        /* Check that they're allowed to register at this IP */
-       if (ast_apply_ha(sip_cfg.contact_ha, &peer->addr) != AST_SENSE_ALLOW ||
-                       ast_apply_ha(peer->contactha, &peer->addr) != AST_SENSE_ALLOW) {
+       if (ast_apply_acl(sip_cfg.contact_acl, &peer->addr, "SIP contact ACL: ") != AST_SENSE_ALLOW ||
+                       ast_apply_acl(peer->contactacl, &peer->addr, "SIP contact ACL: ") != AST_SENSE_ALLOW) {
                ast_log(LOG_WARNING, "Domain '%s' disallowed by contact ACL (violating IP %s)\n", hostport,
                        ast_sockaddr_stringify_addr(&testsa));
                ast_string_field_set(peer, fullcontact, "");
@@ -15485,6 +15488,22 @@ static void network_change_event_unsubscribe(void)
        }
 }
 
+static void acl_change_event_subscribe(void)
+{
+       if (!acl_change_event_subscription) {
+               acl_change_event_subscription = ast_event_subscribe(AST_EVENT_ACL_CHANGE,
+               acl_change_event_cb, "Manager must react to Named ACL changes", NULL, AST_EVENT_IE_END);
+       }
+
+}
+
+static void acl_change_event_unsubscribe(void)
+{
+       if (acl_change_event_subscription) {
+               acl_change_event_subscription = ast_event_unsubscribe(acl_change_event_subscription);
+       }
+}
+
 static int network_change_event_sched_cb(const void *data)
 {
        network_change_event_sched_id = -1;
@@ -15796,7 +15815,7 @@ static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sock
        }
        peer = sip_find_peer(name, NULL, TRUE, FINDPEERS, FALSE, 0);
 
-       if (!(peer && ast_apply_ha(peer->ha, addr))) {
+       if (!(peer && ast_apply_acl(peer->acl, addr, "SIP Peer ACL: "))) {
                /* Peer fails ACL check */
                if (peer) {
                        sip_unref_peer(peer, "register_verify: sip_unref_peer: from sip_find_peer operation");
@@ -16950,7 +16969,7 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of,
                return AUTH_DONT_KNOW;
        }
 
-       if (!ast_apply_ha(peer->ha, addr)) {
+       if (!ast_apply_acl(peer->acl, addr, "SIP Peer ACL: ")) {
                ast_debug(2, "Found peer '%s' for '%s', but fails host access\n", peer->name, of);
                sip_unref_peer(peer, "sip_unref_peer: check_peer_ok: from sip_find_peer call, early return of AUTH_ACL_FAILED");
                return AUTH_ACL_FAILED;
@@ -17754,7 +17773,7 @@ static char *sip_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_arg
                        user->secret,
                        user->accountcode,
                        user->context,
-                       AST_CLI_YESNO(user->ha != NULL),
+                       AST_CLI_YESNO(ast_acl_list_is_empty(user->acl) == 0),
                        AST_CLI_YESNO(ast_test_flag(&user->flags[0], SIP_NAT_FORCE_RPORT)));
                ao2_unlock(user);
                sip_unref_peer(user, "sip show users");
@@ -18013,7 +18032,7 @@ static char *_sip_show_peers(int fd, int *total, struct mansession *s, const str
                        ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_RPORT) ?
                                ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT) ? " A " : " a " :
                                ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT) ? " N " : "   ",    /* NAT=yes? */
-                       peer->ha ? " A " : "   ",       /* permit/deny */
+                       (!ast_acl_list_is_empty(peer->acl)) ? " A " : "   ",       /* permit/deny */
                        tmp_port, status,
                        peer->description ? peer->description : "",
                        realtimepeers ? (peer->is_realtime ? "Cached RT" : "") : "");
@@ -18048,7 +18067,7 @@ static char *_sip_show_peers(int fd, int *total, struct mansession *s, const str
                        ast_test_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP) ? "yes" : "no",
                        ast_test_flag(&peer->flags[1], SIP_PAGE2_VIDEOSUPPORT) ? "yes" : "no",  /* VIDEOSUPPORT=yes? */
                        ast_test_flag(&peer->flags[1], SIP_PAGE2_TEXTSUPPORT) ? "yes" : "no",   /* TEXTSUPPORT=yes? */
-                       peer->ha ? "yes" : "no",       /* permit/deny */
+                       ast_acl_list_is_empty(peer->acl) ? "no" : "yes",       /* permit/deny/acl */
                        status,
                        realtimepeers ? (peer->is_realtime ? "yes" : "no") : "no",
                        peer->description);
@@ -18719,8 +18738,8 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct
                ast_cli(fd, "  Insecure     : %s\n", insecure2str(ast_test_flag(&peer->flags[0], SIP_INSECURE)));
                ast_cli(fd, "  Force rport  : %s\n", force_rport_string(peer->flags));
                ast_cli(fd, "  Symmetric RTP: %s\n", comedia_string(peer->flags));
-               ast_cli(fd, "  ACL          : %s\n", AST_CLI_YESNO(peer->ha != NULL));
-               ast_cli(fd, "  DirectMedACL : %s\n", AST_CLI_YESNO(peer->directmediaha != NULL));
+               ast_cli(fd, "  ACL          : %s\n", AST_CLI_YESNO(ast_acl_list_is_empty(peer->acl) == 0));
+               ast_cli(fd, "  DirectMedACL : %s\n", AST_CLI_YESNO(ast_acl_list_is_empty(peer->directmediaacl) == 0));
                ast_cli(fd, "  T.38 support : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT)));
                ast_cli(fd, "  T.38 EC mode : %s\n", faxec2str(ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT)));
                ast_cli(fd, "  T.38 MaxDtgrm: %d\n", peer->t38_maxdatagram);
@@ -18838,7 +18857,7 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct
                astman_append(s, "SIP-Comedia: %s\r\n", ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA) ?
                                (ast_test_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP) ? "A" : "a") :
                                (ast_test_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP) ? "Y" : "N"));
-               astman_append(s, "ACL: %s\r\n", (peer->ha?"Y":"N"));
+               astman_append(s, "ACL: %s\r\n", (ast_acl_list_is_empty(peer->acl) ? "N" : "Y"));
                astman_append(s, "SIP-CanReinvite: %s\r\n", (ast_test_flag(&peer->flags[0], SIP_DIRECT_MEDIA)?"Y":"N"));
                astman_append(s, "SIP-DirectMedia: %s\r\n", (ast_test_flag(&peer->flags[0], SIP_DIRECT_MEDIA)?"Y":"N"));
                astman_append(s, "SIP-PromiscRedir: %s\r\n", (ast_test_flag(&peer->flags[0], SIP_PROMISCREDIR)?"Y":"N"));
@@ -18989,7 +19008,7 @@ static char *sip_show_user(struct ast_cli_entry *e, int cmd, struct ast_cli_args
                ast_cli(a->fd, "  Pickupgroup  : ");
                print_group(a->fd, user->pickupgroup, 0);
                ast_cli(a->fd, "  Callerid     : %s\n", ast_callerid_merge(cbuf, sizeof(cbuf), user->cid_name, user->cid_num, "<unspecified>"));
-               ast_cli(a->fd, "  ACL          : %s\n", AST_CLI_YESNO(user->ha != NULL));
+               ast_cli(a->fd, "  ACL          : %s\n", AST_CLI_YESNO(ast_acl_list_is_empty(user->acl) == 0));
                ast_cli(a->fd, "  Sess-Timers  : %s\n", stmode2str(user->stimer.st_mode_oper));
                ast_cli(a->fd, "  Sess-Refresh : %s\n", strefresher2str(user->stimer.st_ref));
                ast_cli(a->fd, "  Sess-Expires : %d secs\n", user->stimer.st_max_se);
@@ -27643,6 +27662,23 @@ static int restart_monitor(void)
        return 0;
 }
 
+static void acl_change_event_cb(const struct ast_event *event, void *userdata)
+{
+       ast_log(LOG_NOTICE, "Reloading chan_sip in response to ACL change event.\n");
+
+       ast_mutex_lock(&sip_reload_lock);
+
+       if (sip_reloading) {
+               ast_verbose("Previous SIP reload not yet done\n");
+       } else {
+               sip_reloading = TRUE;
+               sip_reloadreason = CHANNEL_ACL_RELOAD;
+       }
+
+       ast_mutex_unlock(&sip_reload_lock);
+
+       restart_monitor();
+}
 
 /*! \brief Session-Timers: Restart session timer */
 static void restart_session_timer(struct sip_pvt *p)
@@ -29041,8 +29077,8 @@ static void add_peer_mailboxes(struct sip_peer *peer, const char *value)
 static struct sip_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime, int devstate_only)
 {
        struct sip_peer *peer = NULL;
-       struct ast_ha *oldha = NULL;
-       struct ast_ha *olddirectmediaha = NULL;
+       struct ast_acl_list *oldacl = NULL;
+       struct ast_acl_list *olddirectmediaacl = NULL;
        int found = 0;
        int firstpass = 1;
        uint16_t port = 0;
@@ -29056,6 +29092,7 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
        static int deprecation_warning = 1;
        int alt_fullcontact = alt ? 1 : 0, headercount = 0;
        struct ast_str *fullcontact = ast_str_alloca(512);
+       int acl_change_subscription_needed = 0;
 
        if (!realtime || ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS)) {
                /* Note we do NOT use sip_find_peer here, to avoid realtime recursion */
@@ -29104,10 +29141,10 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
 
        /* Note that our peer HAS had its reference count increased */
        if (firstpass) {
-               oldha = peer->ha;
-               peer->ha = NULL;
-               olddirectmediaha = peer->directmediaha;
-               peer->directmediaha = NULL;
+               oldacl = peer->acl;
+               peer->acl = NULL;
+               olddirectmediaacl = peer->directmediaacl;
+               peer->directmediaacl = NULL;
                set_peer_defaults(peer);        /* Set peer defaults */
                peer->type = 0;
        }
@@ -29308,25 +29345,25 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
                                        sip_unref_peer(peer, "sip_unref_peer: from build_peer defaultip");
                                        return NULL;
                                }
-                       } else if (!strcasecmp(v->name, "permit") || !strcasecmp(v->name, "deny")) {
+                       } else if (!strcasecmp(v->name, "permit") || !strcasecmp(v->name, "deny") || !strcasecmp(v->name, "acl")) {
                                int ha_error = 0;
                                if (!ast_strlen_zero(v->value)) {
-                                       peer->ha = ast_append_ha(v->name, v->value, peer->ha, &ha_error);
+                                       ast_append_acl(v->name, v->value, &peer->acl, &ha_error, &acl_change_subscription_needed);
                                }
                                if (ha_error) {
                                        ast_log(LOG_ERROR, "Bad ACL entry in configuration line %d : %s\n", v->lineno, v->value);
                                }
-                       } else if (!strcasecmp(v->name, "contactpermit") || !strcasecmp(v->name, "contactdeny")) {
+                       } else if (!strcasecmp(v->name, "contactpermit") || !strcasecmp(v->name, "contactdeny") || !strcasecmp(v->name, "contactacl")) {
                                int ha_error = 0;
                                if (!ast_strlen_zero(v->value)) {
-                                       peer->contactha = ast_append_ha(v->name + 7, v->value, peer->contactha, &ha_error);
+                                       ast_append_acl(v->name + 7, v->value, &peer->contactacl, &ha_error, &acl_change_subscription_needed);
                                }
                                if (ha_error) {
                                        ast_log(LOG_ERROR, "Bad ACL entry in configuration line %d : %s\n", v->lineno, v->value);
                                }
-                       } else if (!strcasecmp(v->name, "directmediapermit") || !strcasecmp(v->name, "directmediadeny")) {
+                       } else if (!strcasecmp(v->name, "directmediapermit") || !strcasecmp(v->name, "directmediadeny") || !strcasecmp(v->name, "directmediaacl")) {
                                int ha_error = 0;
-                               peer->directmediaha = ast_append_ha(v->name + 11, v->value, peer->directmediaha, &ha_error);
+                               ast_append_acl(v->name + 11, v->value, &peer->directmediaacl, &ha_error, &acl_change_subscription_needed);
                                if (ha_error) {
                                        ast_log(LOG_ERROR, "Bad directmedia ACL entry in configuration line %d : %s\n", v->lineno, v->value);
                                }
@@ -29674,8 +29711,8 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
 
                if (global_dynamic_exclude_static && !ast_sockaddr_isnull(&peer->addr)) {
                        int ha_error = 0;
-                       sip_cfg.contact_ha = ast_append_ha("deny", ast_sockaddr_stringify_addr(&peer->addr), 
-                                                       sip_cfg.contact_ha, &ha_error);
+
+                       ast_append_acl("deny", ast_sockaddr_stringify_addr(&peer->addr), &sip_cfg.contact_acl, &ha_error, NULL);
                        if (ha_error) {
                                ast_log(LOG_ERROR, "Bad or unresolved host/IP entry in configuration for peer %s, cannot add to contact ACL\n", peer->name);
                        }
@@ -29746,8 +29783,8 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
 
        peer->the_mark = 0;
 
-       ast_free_ha(oldha);
-       ast_free_ha(olddirectmediaha);
+       oldacl = ast_free_acl_list(oldacl);
+       olddirectmediaacl = ast_free_acl_list(olddirectmediaacl);
        if (!ast_strlen_zero(peer->callback)) { /* build string from peer info */
                char *reg_string;
                if (asprintf(&reg_string, "%s?%s:%s@%s/%s", peer->name, peer->username, !ast_strlen_zero(peer->remotesecret) ? peer->remotesecret : peer->secret, peer->tohost, peer->callback) < 0) {
@@ -29757,6 +29794,12 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
                        ast_free(reg_string);
                }
        }
+
+       /* If an ACL change subscription is needed and doesn't exist, we need one. */
+       if (acl_change_subscription_needed) {
+               acl_change_event_subscribe();
+       }
+
        return peer;
 }
 
@@ -29838,13 +29881,14 @@ static int reload_config(enum channelreloadreason reason)
        char *cat, *stringp, *context, *oldregcontext;
        char newcontexts[AST_MAX_CONTEXT], oldcontexts[AST_MAX_CONTEXT];
        struct ast_flags dummy[3];
-       struct ast_flags config_flags = { reason == CHANNEL_MODULE_LOAD ? 0 : ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) ? 0 : CONFIG_FLAG_FILEUNCHANGED };
+       struct ast_flags config_flags = { (reason == CHANNEL_MODULE_LOAD || reason == CHANNEL_ACL_RELOAD) ? 0 : ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) ? 0 : CONFIG_FLAG_FILEUNCHANGED };
        int auto_sip_domains = FALSE;
        struct ast_sockaddr old_bindaddr = bindaddr;
        int registry_count = 0, peer_count = 0, timerb_set = 0, timert1_set = 0;
        int subscribe_network_change = 1;
        time_t run_start, run_end;
        int bindport = 0;
+       int acl_change_subscription_needed = 0;
 
        run_start = time(0);
        ast_unload_realtime("sipregs");
@@ -29887,8 +29931,7 @@ static int reload_config(enum channelreloadreason reason)
                }
        }
 
-       ast_free_ha(sip_cfg.contact_ha);
-       sip_cfg.contact_ha = NULL;
+       sip_cfg.contact_acl = ast_free_acl_list(sip_cfg.contact_acl);
 
        default_tls_cfg.enabled = FALSE;                /* Default: Disable TLS */
 
@@ -29981,7 +30024,7 @@ static int reload_config(enum channelreloadreason reason)
        sip_cfg.accept_outofcall_message = DEFAULT_ACCEPT_OUTOFCALL_MESSAGE;
        sip_cfg.allowsubscribe = FALSE;
        sip_cfg.disallowed_methods = SIP_UNKNOWN;
-       sip_cfg.contact_ha = NULL;              /* Reset the contact ACL */
+       sip_cfg.contact_acl = NULL;             /* Reset the contact ACL */
        snprintf(global_useragent, sizeof(global_useragent), "%s %s", DEFAULT_USERAGENT, ast_get_version());
        snprintf(global_sdpsession, sizeof(global_sdpsession), "%s %s", DEFAULT_SDPSESSION, ast_get_version());
        snprintf(global_sdpowner, sizeof(global_sdpowner), "%s", DEFAULT_SDPOWNER);
@@ -30179,9 +30222,9 @@ static int reload_config(enum channelreloadreason reason)
                                  ast_sockaddr_stringify(&sip_tcp_desc.local_address));
                } else if (!strcasecmp(v->name, "dynamic_exclude_static") || !strcasecmp(v->name, "dynamic_excludes_static")) {
                        global_dynamic_exclude_static = ast_true(v->value);
-               } else if (!strcasecmp(v->name, "contactpermit") || !strcasecmp(v->name, "contactdeny")) {
+               } else if (!strcasecmp(v->name, "contactpermit") || !strcasecmp(v->name, "contactdeny") || !strcasecmp(v->name, "contactacl")) {
                        int ha_error = 0;
-                       sip_cfg.contact_ha = ast_append_ha(v->name + 7, v->value, sip_cfg.contact_ha, &ha_error);
+                       ast_append_acl(v->name + 7, v->value, &sip_cfg.contact_acl, &ha_error, &acl_change_subscription_needed);
                        if (ha_error) {
                                ast_log(LOG_ERROR, "Bad ACL entry in configuration line %d : %s\n", v->lineno, v->value);
                        }
@@ -30959,10 +31002,15 @@ static int reload_config(enum channelreloadreason reason)
        run_end = time(0);
        ast_debug(4, "SIP reload_config done...Runtime= %d sec\n", (int)(run_end-run_start));
 
+       /* If an ACL change subscription is needed and doesn't exist, we need one. */
+       if (acl_change_subscription_needed) {
+               acl_change_event_subscribe();
+       }
+
        return 0;
 }
 
-static int apply_directmedia_ha(struct sip_pvt *p, struct ast_ha *directmediaha, const char *op)
+static int apply_directmedia_acl(struct sip_pvt *p, struct ast_acl_list *directmediaacl, const char *op)
 {
        struct ast_sockaddr us = { { 0, }, }, them = { { 0, }, };
        int res = AST_SENSE_ALLOW;
@@ -30970,7 +31018,7 @@ static int apply_directmedia_ha(struct sip_pvt *p, struct ast_ha *directmediaha,
        ast_rtp_instance_get_remote_address(p->rtp, &them);
        ast_rtp_instance_get_local_address(p->rtp, &us);
 
-       if ((res = ast_apply_ha(directmediaha, &them)) == AST_SENSE_DENY) {
+       if ((res = ast_apply_acl(directmediaacl, &them, "SIP Direct Media ACL: ")) == AST_SENSE_DENY) {
                const char *us_addr = ast_strdupa(ast_sockaddr_stringify(&us));
                const char *them_addr = ast_strdupa(ast_sockaddr_stringify(&them));
 
@@ -31045,8 +31093,8 @@ static int sip_set_udptl_peer(struct ast_channel *chan, struct ast_udptl *udptl)
 static int sip_allow_anyrtp_remote(struct ast_channel *chan1, struct ast_channel *chan2, char *rtptype)
 {
        struct sip_pvt *p1 = NULL, *p2 = NULL;
-       struct ast_ha *p2_directmediaha = NULL; /* opposed directmediaha for comparing against first channel host address */
-       struct ast_ha *p1_directmediaha = NULL; /* opposed directmediaha for comparing against second channel host address */
+       struct ast_acl_list *p2_directmediaacl = NULL; /* opposed directmediaha for comparing against first channel host address */
+       struct ast_acl_list *p1_directmediaacl = NULL; /* opposed directmediaha for comparing against second channel host address */
        int res = 1;
 
        if (!(p1 = ast_channel_tech_pvt(chan1))) {
@@ -31058,18 +31106,18 @@ static int sip_allow_anyrtp_remote(struct ast_channel *chan1, struct ast_channel
        }
 
        sip_pvt_lock(p2);
-       if (p2->relatedpeer && p2->relatedpeer->directmediaha) {
-               p2_directmediaha = ast_duplicate_ha_list(p2->relatedpeer->directmediaha);
+       if (p2->relatedpeer && p2->relatedpeer->directmediaacl) {
+               p2_directmediaacl = ast_duplicate_acl_list(p2->relatedpeer->directmediaacl);
        }
        sip_pvt_unlock(p2);
 
        sip_pvt_lock(p1);
-       if (p1->relatedpeer && p1->relatedpeer->directmediaha) {
-               p1_directmediaha = ast_duplicate_ha_list(p1->relatedpeer->directmediaha);
+       if (p1->relatedpeer && p1->relatedpeer->directmediaacl) {
+               p1_directmediaacl = ast_duplicate_acl_list(p1->relatedpeer->directmediaacl);
        }
 
-       if (p2_directmediaha && ast_test_flag(&p1->flags[0], SIP_DIRECT_MEDIA)) {
-               if (!apply_directmedia_ha(p1, p2_directmediaha, rtptype)) {
+       if (p2_directmediaacl && ast_test_flag(&p1->flags[0], SIP_DIRECT_MEDIA)) {
+               if (!apply_directmedia_acl(p1, p2_directmediaacl, rtptype)) {
                        res = 0;
                }
        }
@@ -31080,8 +31128,8 @@ static int sip_allow_anyrtp_remote(struct ast_channel *chan1, struct ast_channel
        }
 
        sip_pvt_lock(p2);
-       if (p1_directmediaha && ast_test_flag(&p2->flags[0], SIP_DIRECT_MEDIA)) {
-               if (!apply_directmedia_ha(p2, p1_directmediaha, rtptype)) {
+       if (p1_directmediaacl && ast_test_flag(&p2->flags[0], SIP_DIRECT_MEDIA)) {
+               if (!apply_directmedia_acl(p2, p1_directmediaacl, rtptype)) {
                        res = 0;
                }
        }
@@ -31089,12 +31137,12 @@ static int sip_allow_anyrtp_remote(struct ast_channel *chan1, struct ast_channel
 
 allow_anyrtp_remote_end:
 
-       if (p2_directmediaha) {
-               ast_free_ha(p2_directmediaha);
+       if (p2_directmediaacl) {
+               p2_directmediaacl = ast_free_acl_list(p2_directmediaacl);
        }
 
-       if (p1_directmediaha) {
-               ast_free_ha(p1_directmediaha);
+       if (p1_directmediaacl) {
+               p1_directmediaacl = ast_free_acl_list(p1_directmediaacl);
        }
 
        return res;
@@ -32574,6 +32622,7 @@ static int unload_module(void)
        int wait_count;
 
        network_change_event_unsubscribe();
+       acl_change_event_unsubscribe();
 
        ast_sched_dump(sched);
        
@@ -32723,7 +32772,7 @@ static int unload_module(void)
        ao2_t_ref(sip_monitor_instances, -1, "unref the sip_monitor_instances table");
 
        clear_sip_domains();
-       ast_free_ha(sip_cfg.contact_ha);
+       sip_cfg.contact_acl = ast_free_acl_list(sip_cfg.contact_acl);
        close(sipsock);
        ast_sched_context_destroy(sched);
        con = ast_context_find(used_context);
index 72f9d2b..1e659a7 100644 (file)
@@ -757,7 +757,7 @@ struct sip_settings {
        char default_subscribecontext[AST_MAX_CONTEXT];
        char default_record_on_feature[FEATURE_MAX_LEN];
        char default_record_off_feature[FEATURE_MAX_LEN];
-       struct ast_ha *contact_ha;  /*! \brief Global list of addresses dynamic peers are not allowed to use */
+       struct ast_acl_list *contact_acl;  /*! \brief Global list of addresses dynamic peers are not allowed to use */
        struct ast_format_cap *caps; /*!< Supported codecs */
        int tcp_enabled;
        int default_max_forwards;    /*!< Default max forwards (SIP Anti-loop) */
@@ -1117,7 +1117,7 @@ struct sip_pvt {
        int rtptimeout;                     /*!< RTP timeout time */
        int rtpholdtimeout;                 /*!< RTP timeout time on hold*/
        int rtpkeepalive;                   /*!< RTP send packets for keepalive */
-       struct ast_ha *directmediaha;           /*!< Which IPs are allowed to interchange direct media with this peer - copied from sip_peer */
+       struct ast_acl_list *directmediaacl; /*!< Which IPs are allowed to interchange direct media with this peer - copied from sip_peer */
        struct ast_sockaddr recv;            /*!< Received as */
        struct ast_sockaddr ourip;           /*!< Our IP (as seen from the outside) */
        enum transfermodes allowtransfer;   /*!< REFER: restriction scheme */
@@ -1335,9 +1335,9 @@ struct sip_peer {
        int keepalive;                  /*!<  Keepalive: How often to send keep alive packet */
        int keepalivesend;              /*!<  Keepalive: Scheduled item for sending keep alive packet */
        struct ast_sockaddr defaddr;     /*!<  Default IP address, used until registration */
-       struct ast_ha *ha;              /*!<  Access control list */
-       struct ast_ha *contactha;       /*!<  Restrict what IPs are allowed in the Contact header (for registration) */
-       struct ast_ha *directmediaha;   /*!<  Restrict what IPs are allowed to interchange direct media with */
+       struct ast_acl_list *acl;              /*!<  Access control list */
+       struct ast_acl_list *contactacl;       /*!<  Restrict what IPs are allowed in the Contact header (for registration) */
+       struct ast_acl_list *directmediaacl;   /*!<  Restrict what IPs are allowed to interchange direct media with */
        struct ast_variable *chanvars;  /*!<  Variables to set for channel created by user */
        struct sip_pvt *mwipvt;         /*!<  Subscription for MWI */
        struct sip_st_cfg stimer;       /*!<  SIP Session-Timers */
diff --git a/configs/acl.conf.sample b/configs/acl.conf.sample
new file mode 100644 (file)
index 0000000..ca6906d
--- /dev/null
@@ -0,0 +1,86 @@
+;
+; Named Access Control Lists (ACLs)
+;
+; A convenient way to share acl definitions
+;
+; This configuration file is read on startup
+;
+; CLI Commands
+; -----------------------------------------------------------
+;   acl show                         Show all named ACLs configured
+;   acl show <name>                  Show contents of a particular named ACL
+;   reload acl                       Reload configuration file
+;
+;[general]
+;systemname=asterisksystem1          ; If a system name is specified, realtime
+;                                    ; ACLs will only be retrieved if they have
+;                                    ; a systemname field that matches this value.
+;                                    ; If it's less blank, the field is ignored.
+;
+; Any configuration that uses ACLs which has been made to be able to use named
+; ACLs will specify a named ACL with the 'acl' option in its configuration in
+; a similar fashion to the usual 'permit' and 'deny' options. Example:
+; acl=my_named_acl
+;
+; Multiple named ACLs can be applied by either comma separating the arguments or
+; just by adding additional ACL lines. Example:
+; acl=my_named_acl
+; acl=my_named_acl2
+;
+; or
+;
+; acl=my_named_acl,my_named_acl2
+;
+; ACLs specified by name are evaluated independently from the ACL specified via
+; permit/deny. In order for an address to pass a given ACL, it must pass both
+; the ACL specified by permit/deny for a given item as well as any named ACLs
+; that were specified.
+;
+;[example_named_acl1]
+;deny=0.0.0.0/0.0.0.0
+;permit=209.16.236.0
+;permit=209.16.236.1
+;
+;[example_named_acl2]
+;permit=0.0.0.0/0.0.0.0
+;deny=10.24.20.171
+;deny=10.24.20.103
+;deny=209.16.236.1
+;
+; example_named_acl1 above shows an example of whitelisting. When whitelisting, the
+; named ACLs should follow a deny that blocks everything (like deny=0.0.0.0/0.0.0.0)
+; The following example explains how combining the ACLs works:
+; <in another configuration>
+; [example_item_with_acl]
+; acl=example_named_acl1
+; acl=example_named_acl2
+;
+; Suppose 209.16.236.0 tries to communicate and the ACL for that example is applied to it...
+; First, example_named_acl1 is evaluated. The address is allowed by that ACL.
+; Next, example_named_acl2 is evaluated. The address isn't blocked by example_named_acl2
+; either, so it passes.
+;
+; Suppose instead 209.16.236.1 tries to communicate and the same ACL is applied.
+; First, example_named_acl1 is evaluated and the address is allowed.
+; However, it is blocked by example_named_acl2, so the address is blocked from the combined
+; ACL.
+;
+; Similarly, the permits/denies in specific configurations that make up an ACL definition
+; are also treated as a separate ACL for evaluation. So if we change the example above to:
+; <in another configuration>
+; [example_item_with_acl]
+; acl=example_named_acl1
+; acl=example_named_acl2
+; deny=209.16.236.0
+;
+; Then 209.16.236.0 will be rejected by the non-named component of the combined ACL even
+; though it passes the two named components.
+;
+;
+; Named ACLs can use ipv6 addresses just like normal ACLs.
+;[ipv6_example_1]
+;deny = ::
+;permit = ::1/128
+;
+;[ipv6_example_2]
+;permit = fe80::21d:bad:fad:2323
index 1d67fa5..a83fa2c 100644 (file)
 ;      cdr.conf
 ;      rtp.conf
 ;
+; Named ACLs specified in realtime also can not be used
+; from manager.conf unless the storage driver is preloaded.
+; Attempting to use a realtime stored named ACL before the
+; driver is loaded will result in an invalid ACL which
+; rejects all addresses.
 ;
 ; Realtime configuration engine
 ;
@@ -74,6 +79,7 @@
 ;meetme => mysql,general
 ;queues => odbc,asterisk
 ;queue_members => odbc,asterisk
+;acls => odbc,asterisk
 ;musiconhold => mysql,general
 ;queue_log => mysql,general
 ;
index 19f2a9b..9b5d4bc 100644 (file)
@@ -518,11 +518,11 @@ inkeys=freeworlddialup
 ;
 ; Further user sections may be added, specifying a context and a secret used
 ; for connections with that given authentication name.  Limited IP based
-; access control is allowed by use of "permit" and "deny" keywords.  Multiple
-; rules are permitted.  Multiple permitted contexts may be specified, in
-; which case the first will be the default.  You can also override Caller*ID
-; so that when you receive a call you set the Caller*ID to be what you want
-; instead of trusting what the remote user provides
+; access control is allowed by use of "permit", "deny", and "acl" keywords.
+; Multiple rules are permitted. Multiple permitted contexts may be specified,
+; in which case the first will be the default.  You can also override
+; Caller*ID so that when you receive a call you set the Caller*ID to be what
+; you want instead of trusting what the remote user provides
 ;
 ; There are three authentication methods that are supported:  md5, plaintext,
 ; and rsa.  The least secure is "plaintext", which sends passwords cleartext
@@ -639,6 +639,7 @@ description=Demo System At Digium    ; Description of this peer, as listed by
 ;secret=shazbot  ; only the last specified secret will be used.
 ;context=default
 ;permit=0.0.0.0/0.0.0.0
+;acl=example_named_acl
 
 ;
 ; With immediate=yes, an IAX2 phone or a phone on an IAXy acts as a hot-line
index 5e99cf8..31c1820 100644 (file)
@@ -85,6 +85,7 @@ bindaddr = 0.0.0.0
 ;secret = mysecret
 ;deny=0.0.0.0/0.0.0.0
 ;permit=209.16.236.73/255.255.255.0
+;acl=named_acl_example               ; use a named ACL from acl.conf
 ;
 ;eventfilter=Event: Newchannel
 ;eventfilter=!Channel: DAHDI*
index e92bba9..dcf88aa 100644 (file)
@@ -10,8 +10,8 @@
 ;
 ;      Especially note the following settings:
 ;              - allowguest (default enabled)
-;              - permit/deny - IP address filters
-;              - contactpermit/contactdeny - IP address filters for registrations
+;              - permit/deny/acl - IP address filters
+;              - contactpermit/contactdeny/contactacl - IP address filters for registrations
 ;              - context - Which set of services you offer various users
 ;
 ; SIP dial strings
@@ -454,6 +454,7 @@ srvlookup=yes                   ; Enable DNS SRV lookups on outbound calls
 ;contactdeny=0.0.0.0/0.0.0.0           ; Use contactpermit and contactdeny to
 ;contactpermit=172.16.0.0/255.255.0.0  ; restrict at what IPs your users may
                                        ; register their phones.
+;contactacl=named_acl_example          ; Use named ACLs defined in acl.conf
 
 ;engine=asterisk                ; RTP engine to use when communicating with the device
 
@@ -956,6 +957,7 @@ srvlookup=yes                   ; Enable DNS SRV lookups on outbound calls
                                 ; Use this if some of your phones are on IP addresses that
                                 ; can not reach each other directly. This way you can force 
                                 ; RTP to always flow through asterisk in such cases.
+;directmediaacl=acl_example     ; Use named ACLs defined in acl.conf
 
 ;ignoresdpversion=yes           ; By default, Asterisk will honor the session version
                                 ; number in SDP packets and will only modify the SDP
@@ -1211,10 +1213,11 @@ srvlookup=yes                   ; Enable DNS SRV lookups on outbound calls
 ; t38pt_usertpsource
 ; contactpermit         ; Limit what a host may register as (a neat trick
 ; contactdeny           ; is to register at the same IP as a SIP provider,
-;                       ; then call oneself, and get redirected to that
+; contactacl            ; then call oneself, and get redirected to that
 ;                       ; same location).
 ; directmediapermit
 ; directmediadeny
+; directmediaacl
 ; unsolicited_mailbox
 ; use_q850_reason
 ; maxforwards
@@ -1419,6 +1422,7 @@ srvlookup=yes                   ; Enable DNS SRV lookups on outbound calls
 ;permit=2001:db8::/32            ; IPv6 ACLs can be specified if desired. IPv6 ACLs
                                  ; apply only to IPv6 addresses, and IPv4 ACLs apply
                                  ; only to IPv4 addresses.
+;acl=named_acl_example           ; Use named ACLs defined in acl.conf
 
 ;[cisco1]
 ;type=friend
index 1576aef..0dfb47f 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Asterisk -- An open source telephony toolkit.
  *
- * Copyright (C) 1999 - 2006, Digium, Inc.
+ * Copyright (C) 1999 - 2012, Digium, Inc.
  *
  * Mark Spencer <markster@digium.com>
  *
@@ -29,15 +29,18 @@ extern "C" {
 #endif
 
 #include "asterisk/network.h"
+#include "asterisk/linkedlists.h"
 #include "asterisk/netsock2.h"
 #include "asterisk/io.h"
 
-#define AST_SENSE_DENY                  0
-#define AST_SENSE_ALLOW                 1
+enum ast_acl_sense {
+       AST_SENSE_DENY,
+       AST_SENSE_ALLOW
+};
 
 /* Host based access control */
 
-/*! \brief internal representation of acl entries
+/*! \brief internal representation of ACL entries
  * In principle user applications would have no need for this,
  * but there is sometimes a need to extract individual items,
  * e.g. to print them, and rather than defining iterators to
@@ -49,10 +52,29 @@ struct ast_ha {
        /* Host access rule */
        struct ast_sockaddr addr;
        struct ast_sockaddr netmask;
-       int sense;
+       enum ast_acl_sense sense;
        struct ast_ha *next;
 };
 
+#define ACL_NAME_LENGTH 80
+
+/*!
+ * \brief an ast_acl is a linked list node of ast_ha structs which may have names.
+ *
+ * \note These shouldn't be used directly by ACL consumers. Consumers should handle
+ *       ACLs via ast_acl_list structs.
+ */
+struct ast_acl {
+       struct ast_ha *acl;             /*!< Rules contained by the ACL */
+       int is_realtime;                /*!< If raised, this named ACL was retrieved from realtime storage */
+       int is_invalid;                 /*!< If raised, this is an invalid ACL which will automatically reject everything. */
+       char name[ACL_NAME_LENGTH];     /*!< If this was retrieved from the named ACL subsystem, this is the name of the ACL. */
+       AST_LIST_ENTRY(ast_acl) list;
+};
+
+/*! \brief Wrapper for an ast_acl linked list. */
+AST_LIST_HEAD(ast_acl_list, ast_acl);
+
 /*!
  * \brief Free a list of HAs
  *
@@ -66,6 +88,18 @@ struct ast_ha {
 void ast_free_ha(struct ast_ha *ha);
 
 /*!
+ * \brief Free a list of ACLs
+ *
+ * \details
+ * Given the head of a list of ast_acl structs, it and all appended
+ * acl structs will be freed. This includes the ast_ha structs within
+ * the individual nodes.
+ * \param acl The list of ACLs to free
+ * \retval NULL
+ */
+struct ast_acl_list *ast_free_acl_list(struct ast_acl_list *acl);
+
+/*!
  * \brief Copy the contents of one HA to another
  *
  * \details
@@ -101,6 +135,35 @@ void ast_copy_ha(const struct ast_ha *from, struct ast_ha *to);
 struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha *path, int *error);
 
 /*!
+ * \brief Add a rule to an ACL struct
+ *
+ * \details
+ * This adds a named ACL or an ACL rule to an ast_acl container.
+ * It works in a similar way to ast_append_ha.
+ *
+ * \param sense Can be any among "permit", "deny", or "acl"
+ *        this controls whether the rule being added will simply modify the unnamed ACL at the head of the list
+ *        or if a new named ACL will be added to that ast_acl.
+ * \param stuff If sense is 'permit'/'deny', this is the ip address and subnet mask separated with a '/' like in ast_append ha.
+ *        If it sense is 'acl', then this will be the name of the ACL being appended to the container.
+ * \param path Address of the ACL list being appended
+ * \param[out] error The int that error points to will be set to 1 if an error occurs.
+ * \param[out] named_acl_flag This will raise a flag under certain conditions to indicate that a named ACL has been added by this
+ *        operation. This may be used to indicate that an event subscription should be made against the named ACL subsystem.
+ *        Note: This flag may be raised by this function, but it will never be lowered by it.
+ */
+void ast_append_acl(const char *sense, const char *stuff, struct ast_acl_list **path, int *error, int *named_acl_flag);
+
+/*!
+ * \brief Determines if an ACL is empty or if it contains entries
+ *
+ * \param acl_list The ACL list being checked
+ * \retval 0 - the list is not empty
+ * \retval 1 - the list is empty
+ */
+int ast_acl_list_is_empty(struct ast_acl_list *acl_list);
+
+/*!
  * \brief Apply a set of rules to a given IP address
  *
  * \details
@@ -115,7 +178,25 @@ struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha
  * \retval AST_SENSE_ALLOW The IP address passes our ACL
  * \retval AST_SENSE_DENY The IP address fails our ACL
  */
-int ast_apply_ha(const struct ast_ha *ha, const struct ast_sockaddr *addr);
+enum ast_acl_sense ast_apply_ha(const struct ast_ha *ha, const struct ast_sockaddr *addr);
+
+/*!
+ * \brief Apply a set of rules to a given IP address
+ *
+ * \details
+ * Similar to the above, only uses an acl container, which is a whole slew
+ * of ast_ha lists. It runs ast_apply_ha on each of the ast_ha structs
+ * contained in the acl container. It will deny if any of the ast_ha lists
+ * fail, and it will pass only if all of the rules pass.
+ *
+ * \param acl The head of the list of ACLs to evaluate
+ * \param addr An ast_sockaddr whose address is considered when matching rules
+ * \param purpose Context for which the ACL is being applied - Establishes purpose of a notice when rejected
+ *
+ * \retval AST_SENSE_ALLOW The IP address passes our ACLs
+ * \retval AST_SENSE_DENY The IP address fails our ACLs
+ */
+enum ast_acl_sense ast_apply_acl(struct ast_acl_list *acl_list, const struct ast_sockaddr *addr, const char *purpose);
 
 /*!
  * \brief Get the IP address given a hostname
@@ -199,15 +280,26 @@ int ast_lookup_iface(char *iface, struct ast_sockaddr *address);
  * value is allocated on the heap and must be freed independently
  * of the input parameter when finished.
  *
- * \note
- * This function is not actually used anywhere.
- *
  * \param original The ast_ha to copy
  * \retval The head of the list of duplicated ast_has
  */
 struct ast_ha *ast_duplicate_ha_list(struct ast_ha *original);
 
 /*!
+ * \brief Duplicates the contests of a list of lists of host access rules.
+ *
+ * \details
+ * A deep copy of an ast_acl list is made (which in turn means a deep copy of
+ * each of the ast_ha structs contained within). The returned value is allocated
+ * on the heap and must be freed independently of the input paramater when
+ * finished.
+ *
+ * \param original The ast_acl_list to copy
+ * \retval The new duplicated ast_acl_list
+ */
+struct ast_acl_list *ast_duplicate_acl_list(struct ast_acl_list *original);
+
+/*!
  * \brief Find our IP address
  *
  * \details
@@ -258,6 +350,41 @@ int ast_str2tos(const char *value, unsigned int *tos);
  */
 const char *ast_tos2str(unsigned int tos);
 
+/*!
+ * \brief Retrieve a named ACL
+ *
+ * \details
+ * This function attempts to find a named ACL. If found, a copy
+ * of the requested ACL will be made which must be freed by
+ * the caller.
+ *
+ * \param name Name of the ACL sought
+ * \param[out] is_realtime will be true if the ACL being returned is from realtime
+ * \param[out] is_undefined will be true if no ACL profile can be found for the requested name
+ *
+ * \retval A copy of the named ACL as an ast_ha
+ * \retval NULL if no ACL could be found.
+ */
+struct ast_ha *ast_named_acl_find(const char *name, int *is_realtime, int *is_undefined);
+
+/*!
+ * \brief Initialize and configure the named ACL system.
+ *
+ * \details
+ * This function will prepare the named ACL system for use.
+ * For this reason, it needs to be called before other things that use ACLs are initialized.
+ */
+int ast_named_acl_init(void);
+
+/*!
+ * \brief reload/reconfigure the named ACL system.
+ *
+ * \details
+ * This function is designed to trigger an event upon a successful reload that may update
+ * ACL consumers.
+ */
+int ast_named_acl_reload(void);
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif
index 4f2cc88..85a5274 100644 (file)
@@ -970,6 +970,7 @@ enum channelreloadreason {
        CHANNEL_MODULE_RELOAD,
        CHANNEL_CLI_RELOAD,
        CHANNEL_MANAGER_RELOAD,
+       CHANNEL_ACL_RELOAD,
 };
 
 /*!
index b2ffcf5..1f4ce29 100644 (file)
@@ -190,6 +190,19 @@ void ast_config_destroy(struct ast_config *config);
 struct ast_variable *ast_category_root(struct ast_config *config, char *cat);
 
 /*!
+ * \brief Sorts categories in a config in the order of a numerical value contained within them.
+ *
+ * \param config The config structure you wish to sort
+ * \param variable Which numerical value you wish to sort by
+ * \param descending If true, we sort highest to lowest instead of lowest to highest
+ *
+ * \details
+ * This function will assume a value of 0 for any non-numerical strings and NULL fields.
+ */
+void ast_config_sort_categories(struct ast_config *config, int descending,
+                                                               int (*comparator)(struct ast_category *p, struct ast_category *q));
+
+/*!
  * \brief Goes through categories
  *
  * \param config Which config structure you wish to "browse"
@@ -482,6 +495,15 @@ int ast_config_engine_register(struct ast_config_engine *newconfig);
 int ast_config_engine_deregister(struct ast_config_engine *del);
 
 /*!
+ * \brief Determine if a mapping exists for a given family
+ *
+ * \param family which family you are looking to see if a mapping exists for
+ * \retval 1 if it is mapped
+ * \retval 0 if it is not
+ */
+int ast_realtime_is_mapping_defined(const char *family);
+
+/*!
  * \brief Exposed initialization method for core process
  *
  * \details
index b2bf0e4..d1e8836 100644 (file)
@@ -56,8 +56,10 @@ enum ast_event_type {
        AST_EVENT_NETWORK_CHANGE      = 0x09,
        /*! The presence state for a presence provider */
        AST_EVENT_PRESENCE_STATE      = 0x0a,
+       /*! Used to alert listeners when a named ACL has changed. */
+       AST_EVENT_ACL_CHANGE          = 0x0b,
        /*! Number of event types.  This should be the last event type + 1 */
-       AST_EVENT_TOTAL               = 0x0b,
+       AST_EVENT_TOTAL               = 0x0c,
 };
 
 /*! \brief Event Information Element types */
index e27ed0c..4b14ac5 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Asterisk -- An open source telephony toolkit.
  *
- * Copyright (C) 1999 - 2006, Digium, Inc.
+ * Copyright (C) 1999 - 2012, Digium, Inc.
  *
  * Mark Spencer <markster@digium.com>
  *
@@ -230,6 +230,28 @@ void ast_free_ha(struct ast_ha *ha)
        }
 }
 
+/* Free ACL list structure */
+struct ast_acl_list *ast_free_acl_list(struct ast_acl_list *acl_list)
+{
+       struct ast_acl *current;
+
+       if (!acl_list) {
+               return NULL;
+       }
+
+       AST_LIST_LOCK(acl_list);
+       while ((current = AST_LIST_REMOVE_HEAD(acl_list, list))) {
+               ast_free_ha(current->acl);
+               ast_free(current);
+       }
+       AST_LIST_UNLOCK(acl_list);
+
+       AST_LIST_HEAD_DESTROY(acl_list);
+       ast_free(acl_list);
+
+       return NULL;
+}
+
 /* Copy HA structure */
 void ast_copy_ha(const struct ast_ha *from, struct ast_ha *to)
 {
@@ -275,6 +297,56 @@ struct ast_ha *ast_duplicate_ha_list(struct ast_ha *original)
        return ret;                             /* Return start of list */
 }
 
+static int acl_new(struct ast_acl **pointer, const char *name) {
+       struct ast_acl *acl;
+       if (!(acl = ast_calloc(1, sizeof(*acl)))) {
+               return 1;
+       }
+
+       *pointer = acl;
+       ast_copy_string(acl->name, name, ACL_NAME_LENGTH);
+       return 0;
+}
+
+struct ast_acl_list *ast_duplicate_acl_list(struct ast_acl_list *original)
+{
+       struct ast_acl_list *clone;
+       struct ast_acl *current_cursor;
+       struct ast_acl *current_clone;
+
+       /* Early return if we receive a duplication request for a NULL original. */
+       if (!original) {
+               return NULL;
+       }
+
+       if (!(clone = ast_calloc(1, sizeof(*clone)))) {
+               ast_log(LOG_WARNING, "Failed to allocate ast_acl_list struct while cloning an ACL\n");
+               return NULL;
+       }
+       AST_LIST_HEAD_INIT(clone);
+
+       AST_LIST_LOCK(original);
+
+       AST_LIST_TRAVERSE(original, current_cursor, list) {
+               if ((acl_new(&current_clone, current_cursor->name))) {
+                       ast_log(LOG_WARNING, "Failed to allocate ast_acl struct while cloning an ACL.");
+                       continue;
+               }
+
+               /* Copy data from original ACL to clone ACL */
+               current_clone->acl = ast_duplicate_ha_list(current_cursor->acl);
+
+               current_clone->is_invalid = current_cursor->is_invalid;
+               current_clone->is_realtime = current_cursor->is_realtime;
+
+               AST_LIST_INSERT_TAIL(clone, current_clone, list);
+       }
+
+       AST_LIST_UNLOCK(original);
+
+       return clone;
+}
+
 /*!
  * \brief
  * Isolate a 32-bit section of an IPv6 address
@@ -396,6 +468,135 @@ static int parse_cidr_mask(struct ast_sockaddr *addr, int is_v4, const char *mas
        return 0;
 }
 
+
+
+void ast_append_acl(const char *sense, const char *stuff, struct ast_acl_list **path, int *error, int *named_acl_flag)
+{
+       struct ast_acl *acl = NULL;
+       struct ast_acl *current;
+       struct ast_acl_list *working_list;
+
+       char *tmp, *list;
+
+       /* If the ACL list is currently uninitialized, it must be initialized. */
+       if (*path == NULL) {
+               struct ast_acl_list *list;
+               list = ast_calloc(1, sizeof(*list));
+               if (!list) {
+                       /* Allocation Error */
+                       if (error) {
+                               *error = 1;
+                       }
+                       return;
+               }
+
+               AST_LIST_HEAD_INIT(list);
+               *path = list;
+       }
+
+       working_list = *path;
+
+       AST_LIST_LOCK(working_list);
+
+       /* First we need to determine if we will need to add a new ACL node or if we can use an existing one. */
+       if (strncasecmp(sense, "a", 1)) {
+               /* The first element in the path should be the unnamed, base ACL. If that's the case, we use it. If not,
+                * we have to make one and link it up appropriately. */
+               current = AST_LIST_FIRST(working_list);
+
+               if (!current || !ast_strlen_zero(current->name)) {
+                       if (acl_new(&acl, "")) {
+                               if (error) {
+                                       *error = 1;
+                               }
+                       }
+                       // Need to INSERT the ACL at the head here.
+                       AST_LIST_INSERT_HEAD(working_list, acl, list);
+               } else {
+                       /* If the first element was already the unnamed base ACL, we just use that one. */
+                       acl = current;
+               }
+
+               /* With the proper ACL set for modification, we can just pass this off to the ast_ha append function. */
+               acl->acl = ast_append_ha(sense, stuff, acl->acl, error);
+
+               AST_LIST_UNLOCK(working_list);
+               return;
+       }
+
+       /* We are in ACL append mode, so we know we'll be adding one or more named ACLs. */
+       list = ast_strdupa(stuff);
+
+       while ((tmp = strsep(&list, ","))) {
+               struct ast_ha *named_ha;
+               int already_included = 0;
+
+               /* Remove leading whitespace from the string in case the user put spaces between items */
+               tmp = ast_skip_blanks(tmp);
+
+               /* The first step is to check for a duplicate */
+               AST_LIST_TRAVERSE(working_list, current, list) {
+                       if (!strcasecmp(current->name, tmp)) { /* ACL= */
+                               /* Inclusion of the same ACL multiple times isn't a catastrophic error, but it will raise the error flag and skip the entry. */
+                               ast_log(LOG_ERROR, "Named ACL '%s' is already included in the ast_acl container.", tmp);
+                               if (error) {
+                                       *error = 1;
+                               }
+                               already_included = 1;
+                               break;
+                       }
+               }
+
+               if (already_included) {
+                       continue;
+               }
+
+               if (acl_new(&acl, tmp)) {
+                       /* This is a catastrophic allocation error and we'll return immediately if this happens. */
+                       if (error) {
+                               *error = 1;
+                       }
+                       AST_LIST_UNLOCK(working_list);
+                       return;
+               }
+
+               /* Attempt to grab the Named ACL we are looking for. */
+               named_ha = ast_named_acl_find(tmp, &acl->is_realtime, &acl->is_invalid);
+
+               /* Set the ACL's ast_ha to the duplicated named ACL retrieved above. */
+               acl->acl = named_ha;
+
+               /* Raise the named_acl_flag since we are adding a named ACL to the ACL container. */
+               if (named_acl_flag) {
+                       *named_acl_flag = 1;
+               }
+
+               /* Now insert the new ACL at the end of the list. */
+               AST_LIST_INSERT_TAIL(working_list, acl, list);
+       }
+
+       AST_LIST_UNLOCK(working_list);
+}
+
+int ast_acl_list_is_empty(struct ast_acl_list *acl_list)
+{
+       struct ast_acl *head;
+
+       if (!acl_list) {
+               return 1;
+       }
+
+       AST_LIST_LOCK(acl_list);
+       head = AST_LIST_FIRST(acl_list);
+       AST_LIST_UNLOCK(acl_list);
+
+       if (head) {
+               return 0;
+       }
+
+       return 1;
+}
+
 struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha *path, int *error)
 {
        struct ast_ha *ha;
@@ -509,16 +710,51 @@ struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha
                const char *addr = ast_strdupa(ast_sockaddr_stringify(&ha->addr));
                const char *mask = ast_strdupa(ast_sockaddr_stringify(&ha->netmask));
 
-               ast_debug(1, "%s/%s sense %d appended to acl for peer\n", addr, mask, ha->sense);
+               ast_debug(3, "%s/%s sense %d appended to acl\n", addr, mask, ha->sense);
        }
 
        return ret;
 }
 
-int ast_apply_ha(const struct ast_ha *ha, const struct ast_sockaddr *addr)
+enum ast_acl_sense ast_apply_acl(struct ast_acl_list *acl_list, const struct ast_sockaddr *addr, const char *purpose)
+{
+       struct ast_acl *acl;
+
+       /* If the list is NULL, there are no rules, so we'll allow automatically. */
+       if (!acl_list) {
+               return AST_SENSE_ALLOW;
+       }
+
+       AST_LIST_LOCK(acl_list);
+
+       AST_LIST_TRAVERSE(acl_list, acl, list) {
+               if (acl->is_invalid) {
+                       /* In this case, the baseline ACL shouldn't ever trigger this, but if that somehow happens, it'll still be shown. */
+                       ast_log(LOG_WARNING, "%sRejecting '%s' due to use of an invalid ACL '%s'.\n", purpose ? purpose : "", ast_sockaddr_stringify_addr(addr),
+                                       ast_strlen_zero(acl->name) ? "(BASELINE)" : acl->name);
+                       AST_LIST_UNLOCK(acl_list);
+                       return AST_SENSE_DENY;
+               }
+
+               if (acl->acl) {
+                       if (ast_apply_ha(acl->acl, addr) == AST_SENSE_DENY) {
+                               ast_log(LOG_NOTICE, "%sRejecting '%s' due to a failure to pass ACL '%s'\n", purpose ? purpose : "", ast_sockaddr_stringify_addr(addr),
+                                               ast_strlen_zero(acl->name) ? "(BASELINE)" : acl->name);
+                               AST_LIST_UNLOCK(acl_list);
+                               return AST_SENSE_DENY;
+                       }
+               }
+       }
+
+       AST_LIST_UNLOCK(acl_list);
+
+       return AST_SENSE_ALLOW;
+}
+
+enum ast_acl_sense ast_apply_ha(const struct ast_ha *ha, const struct ast_sockaddr *addr)
 {
        /* Start optimistic */
-       int res = AST_SENSE_ALLOW;
+       enum ast_acl_sense res = AST_SENSE_ALLOW;
        const struct ast_ha *current_ha;
 
        for (current_ha = ha; current_ha; current_ha = current_ha->next) {
index 08aaa57..23d6c23 100644 (file)
@@ -117,6 +117,7 @@ int daemon(int, int);  /* defined in libresolv of all places */
 #include "asterisk/channel.h"
 #include "asterisk/translate.h"
 #include "asterisk/features.h"
+#include "asterisk/acl.h"
 #include "asterisk/ulaw.h"
 #include "asterisk/alaw.h"
 #include "asterisk/callerid.h"
@@ -4033,6 +4034,11 @@ int main(int argc, char *argv[])
                exit(1);
        }
 
+       if (ast_named_acl_init()) { /* Initialize the Named ACL system */
+               printf("%s", term_quit());
+               exit(1);
+       }
+
        ast_http_init();                /* Start the HTTP server, if needed */
 
        if (init_manager()) {
index 9e2df6c..fee6d9e 100644 (file)
@@ -773,6 +773,124 @@ struct ast_variable *ast_category_root(struct ast_config *config, char *cat)
        return NULL;
 }
 
+void ast_config_sort_categories(struct ast_config *config, int descending,
+                                                               int (*comparator)(struct ast_category *p, struct ast_category *q))
+{
+       /*
+        * The contents of this function are adapted from
+        * an example of linked list merge sorting
+        * copyright 2001 Simon Tatham.
+        *
+        * Permission is hereby granted, free of charge, to any person
+        * obtaining a copy of this software and associated documentation
+        * files (the "Software"), to deal in the Software without
+        * restriction, including without limitation the rights to use,
+        * copy, modify, merge, publish, distribute, sublicense, and/or
+        * sell copies of the Software, and to permit persons to whom the
+        * Software is furnished to do so, subject to the following
+        * conditions:
+        *
+        * The above copyright notice and this permission notice shall be
+        * included in all copies or substantial portions of the Software.
+        *
+        * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+        * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+        * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+        * NONINFRINGEMENT.  IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
+        * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+        * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+        * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+        * SOFTWARE.
+        */
+
+       int insize = 1;
+       struct ast_category *p, *q, *e, *tail;
+       int nmerges, psize, qsize, i;
+
+       /* If the descending flag was sent, we'll apply inversion to the comparison function's return. */
+       if (descending) {
+               descending = -1;
+       } else {
+               descending = 1;
+       }
+
+       if (!config->root) {
+               return;
+       }
+
+       while (1) {
+               p = config->root;
+               config->root = NULL;
+               tail = NULL;
+
+               nmerges = 0; /* count number of merges we do in this pass */
+
+               while (p) {
+                       nmerges++; /* there exists a merge to be done */
+
+                       /* step `insize' places along from p */
+                       q = p;
+                       psize = 0;
+                       for (i = 0; i < insize; i++) {
+                               psize++;
+                               q = q->next;
+                               if (!q) {
+                                       break;
+                               }
+                       }
+
+                       /* if q hasn't fallen off end, we have two lists to merge */
+                       qsize = insize;
+
+                       /* now we have two lists; merge them */
+                       while (psize > 0 || (qsize > 0 && q)) {
+                               /* decide whether next element of merge comes from p or q */
+                               if (psize == 0) {
+                                       /* p is empty; e must come from q. */
+                                       e = q;
+                                       q = q->next;
+                                       qsize--;
+                               } else if (qsize == 0 || !q) {
+                                       /* q is empty; e must come from p. */
+                                       e = p; p = p->next; psize--;
+                               } else if ((comparator(p,q) * descending) <= 0) {
+                                       /* First element of p is lower (or same) e must come from p. */
+                                       e = p;
+                                       p = p->next;
+                                       psize--;
+                               } else {
+                                       /* First element of q is lower; e must come from q. */
+                                       e = q;
+                                       q = q->next;
+                                       qsize--;
+                               }
+
+                               /* add the next element to the merged list */
+                               if (tail) {
+                                       tail->next = e;
+                               } else {
+                                       config->root = e;
+                               }
+                               tail = e;
+                       }
+
+                       /* now p has stepped `insize' places along, and q has too */
+                       p = q;
+               }
+
+               tail->next = NULL;
+
+               /* If we have done only one merge, we're finished. */
+               if (nmerges <= 1) { /* allow for nmerges==0, the empty list case */
+                       return;
+               }
+
+               /* Otherwise repeat, merging lists twice the size */
+               insize *= 2;
+       }
+
+}
+
 char *ast_category_browse(struct ast_config *config, const char *prev)
 {
        struct ast_category *cat;
@@ -2244,6 +2362,23 @@ int ast_config_engine_deregister(struct ast_config_engine *del)
        return 0;
 }
 
+int ast_realtime_is_mapping_defined(const char *family)
+{
+       struct ast_config_map *map;
+       ast_mutex_lock(&config_lock);
+
+       for (map = config_maps; map; map = map->next) {
+               if (!strcasecmp(family, map->name)) {
+                       ast_mutex_unlock(&config_lock);
+                       return 1;
+               }
+       }
+
+       ast_mutex_unlock(&config_lock);
+
+       return 0;
+}
+
 /*! \brief Find realtime engine for realtime family */
 static struct ast_config_engine *find_engine(const char *family, int priority, char *database, int dbsiz, char *table, int tabsiz)
 {
index d715092..5dab43b 100644 (file)
@@ -44,6 +44,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/config.h"
 #include "asterisk/channel.h"
 #include "asterisk/term.h"
+#include "asterisk/acl.h"
 #include "asterisk/manager.h"
 #include "asterisk/cdr.h"
 #include "asterisk/enum.h"
@@ -270,6 +271,7 @@ static struct reload_classes {
        { "dnsmgr",     dnsmgr_reload },
        { "extconfig",  read_config_maps },
        { "enum",       ast_enum_reload },
+       { "acl",        ast_named_acl_reload },
        { "manager",    reload_manager },
        { "http",       ast_http_reload },
        { "logger",     logger_reload },
index d70ac6d..c94e94c 100644 (file)
@@ -80,6 +80,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/astobj2.h"
 #include "asterisk/features.h"
 #include "asterisk/security_events.h"
+#include "asterisk/event.h"
 #include "asterisk/aoc.h"
 #include "asterisk/stringfields.h"
 #include "asterisk/presencestate.h"
@@ -990,7 +991,7 @@ static char global_realm[MAXHOSTNAMELEN];   /*!< Default realm */
 
 static int block_sockets;
 static int unauth_sessions = 0;
-
+static struct ast_event_sub *acl_change_event_subscription;
 
 /*! \brief
  * Descriptor for a manager session, either on the AMI socket or over HTTP.
@@ -1011,6 +1012,23 @@ static const struct {
        {{ "restart", "gracefully", NULL }},
 };
 
+static void acl_change_event_cb(const struct ast_event *event, void *userdata);
+
+static void acl_change_event_subscribe(void)
+{
+       if (!acl_change_event_subscription) {
+               acl_change_event_subscription = ast_event_subscribe(AST_EVENT_ACL_CHANGE,
+                       acl_change_event_cb, "Manager must react to Named ACL changes", NULL, AST_EVENT_IE_END);
+       }
+}
+
+static void acl_change_event_unsubscribe(void)
+{
+       if (acl_change_event_subscription) {
+               acl_change_event_subscription = ast_event_unsubscribe(acl_change_event_subscription);
+       }
+}
+
 /* In order to understand what the heck is going on with the
  * mansession_session and mansession structs, we need to have a bit of a history
  * lesson.
@@ -1110,7 +1128,6 @@ static AST_RWLIST_HEAD_STATIC(channelvars, manager_channel_variable);
 struct ast_manager_user {
        char username[80];
        char *secret;                   /*!< Secret for logging in */
-       struct ast_ha *ha;              /*!< ACL setting */
        int readperm;                   /*!< Authorization for reading */
        int writeperm;                  /*!< Authorization for writing */
        int writetimeout;               /*!< Per user Timeout for ast_carefulwrite() */
@@ -1118,6 +1135,7 @@ struct ast_manager_user {
        int keep;                       /*!< mark entries created on a reload */
        struct ao2_container *whitefilters; /*!< Manager event filters - white list */
        struct ao2_container *blackfilters; /*!< Manager event filters - black list */
+       struct ast_acl_list *acl;       /*!< ACL setting */
        char *a1_hash;                  /*!< precalculated A1 for Digest auth */
        AST_RWLIST_ENTRY(ast_manager_user) list;
 };
@@ -1666,13 +1684,13 @@ static char *handle_showmanager(struct ast_cli_entry *e, int cmd, struct ast_cli
        ast_cli(a->fd,
                "       username: %s\n"
                "         secret: %s\n"
-               "            acl: %s\n"
+               "            ACL: %s\n"
                "      read perm: %s\n"
                "     write perm: %s\n"
                "displayconnects: %s\n",
                (user->username ? user->username : "(N/A)"),
                (user->secret ? "<Set>" : "(N/A)"),
-               (user->ha ? "yes" : "no"),
+               ((user->acl && !ast_acl_list_is_empty(user->acl)) ? "yes" : "no"),
                authority_to_str(user->readperm, &rauthority),
                authority_to_str(user->writeperm, &wauthority),
                (user->displayconnects ? "yes" : "no"));
@@ -2478,7 +2496,7 @@ static int authenticate(struct mansession *s, const struct message *m)
        if (!(user = get_manager_by_name_locked(username))) {
                report_invalid_user(s, username);
                ast_log(LOG_NOTICE, "%s tried to authenticate with nonexistent user '%s'\n", ast_sockaddr_stringify_addr(&s->session->addr), username);
-       } else if (user->ha && !ast_apply_ha(user->ha, &s->session->addr)) {
+       } else if (user->acl && (ast_apply_acl(user->acl, &s->session->addr, "Manager User ACL: ") == AST_SENSE_DENY)) {
                report_failed_acl(s, username);
                ast_log(LOG_NOTICE, "%s failed to pass IP ACL as '%s'\n", ast_sockaddr_stringify_addr(&s->session->addr), username);
        } else if (!strcasecmp(astman_get_header(m, "AuthType"), "MD5")) {
@@ -6402,7 +6420,7 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
        }
 
        /* --- We have User for this auth, now check ACL */
-       if (user->ha && !ast_apply_ha(user->ha, remote_address)) {
+       if (user->acl && !ast_apply_acl(user->acl, remote_address, "Manager User ACL:")) {
                AST_RWLIST_UNLOCK(&users);
                ast_log(LOG_NOTICE, "%s failed to pass IP ACL as '%s'\n", ast_sockaddr_stringify_addr(&session->addr), d.username);
                ast_http_error(ser, 403, "Permission denied", "Permission denied\n");
@@ -7070,7 +7088,7 @@ static void load_channelvars(struct ast_variable *var)
        AST_RWLIST_UNLOCK(&channelvars);
 }
 
-static int __init_manager(int reload)
+static int __init_manager(int reload, int by_external_config)
 {
        struct ast_config *ucfg = NULL, *cfg = NULL;
 #ifdef AST_XML_DOCS
@@ -7081,12 +7099,13 @@ static int __init_manager(int reload)
        int newhttptimeout = 60;
        struct ast_manager_user *user = NULL;
        struct ast_variable *var;
-       struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+       struct ast_flags config_flags = { (reload && !by_external_config) ? CONFIG_FLAG_FILEUNCHANGED : 0 };
        char a1[256];
        char a1_hash[256];
        struct ast_sockaddr ami_desc_local_address_tmp;
        struct ast_sockaddr amis_desc_local_address_tmp;
        int tls_was_enabled = 0;
+       int acl_subscription_flag = 0;
 
        manager_enabled = 0;
 
@@ -7160,6 +7179,11 @@ static int __init_manager(int reload)
                return 0;
        }
 
+       /* If this wasn't performed due to a forced reload (because those can be created by ACL change events, we need to unsubscribe to ACL change events. */
+       if (!by_external_config) {
+               acl_change_event_unsubscribe();
+       }
+
        /* default values */
        ast_copy_string(global_realm, S_OR(ast_config_AST_SYSTEM_NAME, DEFAULT_REALM), sizeof(global_realm));
        ast_sockaddr_setnull(&ami_desc.local_address);
@@ -7310,7 +7334,7 @@ static int __init_manager(int reload)
                                        ast_copy_string(user->username, cat, sizeof(user->username));
                                        /* Insert into list */
                                        AST_LIST_INSERT_TAIL(&users, user, list);
-                                       user->ha = NULL;
+                                       user->acl = NULL;
                                        user->keep = 1;
                                        user->readperm = -1;
                                        user->writeperm = -1;
@@ -7367,7 +7391,7 @@ static int __init_manager(int reload)
        /* cat is NULL here in any case */
 
        while ((cat = ast_category_browse(cfg, cat))) {
-               struct ast_ha *oldha;
+               struct ast_acl_list *oldacl;
 
                if (!strcasecmp(cat, "general")) {
                        continue;
@@ -7381,7 +7405,7 @@ static int __init_manager(int reload)
                        /* Copy name over */
                        ast_copy_string(user->username, cat, sizeof(user->username));
 
-                       user->ha = NULL;
+                       user->acl = NULL;
                        user->readperm = 0;
                        user->writeperm = 0;
                        /* Default displayconnect from [general] */
@@ -7399,8 +7423,8 @@ static int __init_manager(int reload)
 
                /* Make sure we keep this user and don't destroy it during cleanup */
                user->keep = 1;
-               oldha = user->ha;
-               user->ha = NULL;
+               oldacl = user->acl;
+               user->acl = NULL;
 
                var = ast_variable_browse(cfg, cat);
                for (; var; var = var->next) {
@@ -7410,8 +7434,9 @@ static int __init_manager(int reload)
                                }
                                user->secret = ast_strdup(var->value);
                        } else if (!strcasecmp(var->name, "deny") ||
-                                      !strcasecmp(var->name, "permit")) {
-                               user->ha = ast_append_ha(var->name, var->value, user->ha, NULL);
+                                      !strcasecmp(var->name, "permit") ||
+                                      !strcasecmp(var->name, "acl")) {
+                               ast_append_acl(var->name, var->value, &user->acl, NULL, &acl_subscription_flag);
                        }  else if (!strcasecmp(var->name, "read") ) {
                                user->readperm = get_perm(var->value);
                        }  else if (!strcasecmp(var->name, "write") ) {
@@ -7432,10 +7457,16 @@ static int __init_manager(int reload)
                                ast_debug(1, "%s is an unknown option.\n", var->name);
                        }
                }
-               ast_free_ha(oldha);
+
+               oldacl = ast_free_acl_list(oldacl);
        }
        ast_config_destroy(cfg);
 
+       /* Check the flag for named ACL event subscription and if we need to, register a subscription. */
+       if (acl_subscription_flag && !by_external_config) {
+               acl_change_event_subscribe();
+       }
+
        /* Perform cleanup - essentially prune out old users that no longer exist */
        AST_RWLIST_TRAVERSE_SAFE_BEGIN(&users, user, list) {
                if (user->keep) {       /* valid record. clear flag for the next round */
@@ -7464,7 +7495,7 @@ static int __init_manager(int reload)
                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);
+               user->acl = ast_free_acl_list(user->acl);
                ast_free(user);
        }
        AST_RWLIST_TRAVERSE_SAFE_END;
@@ -7516,6 +7547,13 @@ static int __init_manager(int reload)
        return 0;
 }
 
+static void acl_change_event_cb(const struct ast_event *event, void *userdata)
+{
+       /* For now, this is going to be performed simply and just execute a forced reload. */
+       ast_log(LOG_NOTICE, "Reloading manager in response to ACL change event.\n");
+       __init_manager(1, 1);
+}
+
 /* clear out every entry in the channelvar list */
 static void free_channelvars(void)
 {
@@ -7529,12 +7567,12 @@ static void free_channelvars(void)
 
 int init_manager(void)
 {
-       return __init_manager(0);
+       return __init_manager(0, 0);
 }
 
 int reload_manager(void)
 {
-       return __init_manager(1);
+       return __init_manager(1, 0);
 }
 
 int astman_datastore_add(struct mansession *s, struct ast_datastore *datastore)
diff --git a/main/named_acl.c b/main/named_acl.c
new file mode 100644 (file)
index 0000000..5248797
--- /dev/null
@@ -0,0 +1,558 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * Copyright (C) 2012, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Named Access Control Lists
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ *
+ * \note Based on a feature proposed by
+ * Olle E. Johansson <oej@edvina.net>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/config.h"
+#include "asterisk/config_options.h"
+#include "asterisk/event.h"
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/acl.h"
+#include "asterisk/astobj2.h"
+
+#define NACL_CONFIG "acl.conf"
+#define ACL_FAMILY "acls"
+
+struct named_acl_global_config {
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(systemname);
+       );
+};
+
+/*
+ * Configuration structure - holds pointers to ao2 containers used for configuration
+ * Since there isn't a general level or any other special levels for acl.conf at this
+ * time, it's really a config options friendly wrapper for the named ACL container
+ */
+struct named_acl_config {
+       struct named_acl_global_config *global;
+       struct ao2_container *named_acl_list;
+};
+
+static AO2_GLOBAL_OBJ_STATIC(globals);
+
+/*! \note These functions are used for placing/retrieving named ACLs in their ao2_container. */
+static void *named_acl_config_alloc(void);
+static void *named_acl_alloc(const char *cat);
+static void *named_acl_find(struct ao2_container *container, const char *cat);
+
+/* Config type for named ACL profiles (must not be named general) */
+static struct aco_type named_acl_type = {
+       .type = ACO_ITEM,                  /*!< named_acls are items stored in containers, not individual global objects */
+       .category_match = ACO_BLACKLIST,
+       .category = "^general$",           /*!< Match everything but "general" */
+       .item_alloc = named_acl_alloc,     /*!< A callback to allocate a new named_acl based on category */
+       .item_find = named_acl_find,       /*!< A callback to find a named_acl in some container of named_acls */
+       .item_offset = offsetof(struct named_acl_config, named_acl_list), /*!< Could leave this out since 0 */
+};
+
+/* Config type for the general part of the ACL profile (must be named general) */
+static struct aco_type global_option = {
+       .type = ACO_GLOBAL,
+       .item_offset = offsetof(struct named_acl_config, global),
+       .category_match = ACO_WHITELIST,
+       .category = "^general$",
+};
+
+/* This array of aco_type structs is necessary to use aco_option_register */
+struct aco_type *named_acl_types[] = ACO_TYPES(&named_acl_type);
+
+struct aco_type *global_options[] = ACO_TYPES(&global_option);
+
+struct aco_file named_acl_conf = {
+       .filename = "acl.conf",
+       .types = ACO_TYPES(&named_acl_type, &global_option),
+};
+
+/* Create a config info struct that describes the config processing for named ACLs. */
+CONFIG_INFO_STANDARD(cfg_info, globals, named_acl_config_alloc,
+       .files = ACO_FILES(&named_acl_conf),
+);
+
+struct named_acl {
+       struct ast_ha *ha;
+       char name[ACL_NAME_LENGTH]; /* Same max length as a configuration category */
+};
+
+static int named_acl_hash_fn(const void *obj, const int flags)
+{
+       const struct named_acl *entry = obj;
+       return ast_str_hash(entry->name);
+}
+
+static int named_acl_cmp_fn(void *obj, void *arg, const int flags)
+{
+       struct named_acl *entry1 = obj;
+       struct named_acl *entry2 = arg;
+
+       return (!strcmp(entry1->name, entry2->name)) ? (CMP_MATCH | CMP_STOP) : 0;
+}
+
+/*! \brief destructor for named_acl_config */
+static void named_acl_config_destructor(void *obj)
+{
+       struct named_acl_config *cfg = obj;
+       ao2_cleanup(cfg->named_acl_list);
+       ao2_cleanup(cfg->global);
+}
+
+static void named_acl_global_config_destructor(void *obj)
+{
+       struct named_acl_global_config *global = obj;
+       ast_string_field_free_memory(global);
+}
+
+/*! \brief allocator callback for named_acl_config. Notice it returns void * since it is used by
+ * the backend config code
+ */
+static void *named_acl_config_alloc(void)
+{
+       struct named_acl_config *cfg;
+
+       if (!(cfg = ao2_alloc(sizeof(*cfg), named_acl_config_destructor))) {
+               return NULL;
+       }
+
+       if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), named_acl_global_config_destructor))) {
+               goto error;
+       }
+
+       if (ast_string_field_init(cfg->global, 128)) {
+               goto error;
+       }
+
+       if (!(cfg->named_acl_list = ao2_container_alloc(37, named_acl_hash_fn, named_acl_cmp_fn))) {
+               goto error;
+       }
+
+       return cfg;
+
+error:
+       ao2_ref(cfg, -1);
+       return NULL;
+}
+
+/*! \brief Destroy a named ACL object */
+static void destroy_named_acl(void *obj)
+{
+       struct named_acl *named_acl = obj;
+       ast_free_ha(named_acl->ha);
+}
+
+/*!
+ * \brief Create a named ACL structure
+ *
+ * \param cat name given to the ACL
+ * \retval NULL failure
+ *\retval non-NULL successfully allocated named ACL
+ */
+void *named_acl_alloc(const char *cat)
+{
+       struct named_acl *named_acl;
+
+       named_acl = ao2_alloc(sizeof(*named_acl), destroy_named_acl);
+       if (!named_acl) {
+               return NULL;
+       }
+
+       ast_copy_string(named_acl->name, cat, sizeof(named_acl->name));
+
+       return named_acl;
+}
+
+/*!
+ * \brief Find a named ACL in a container by its name
+ *
+ * \param container ao2container holding the named ACLs
+ * \param name of the ACL wanted to be found
+ * \retval pointer to the named ACL if available. Null if not found.
+ */
+void *named_acl_find(struct ao2_container *container, const char *cat)
+{
+       struct named_acl tmp;
+       ast_copy_string(tmp.name, cat, sizeof(tmp.name));
+       return ao2_find(container, &tmp, OBJ_POINTER);
+}
+
+/*!
+ * \internal
+ * \brief Callback function to compare the ACL order of two given categories.
+ *        This function is used to sort lists of ACLs received from realtime.
+ *
+ * \param p first category being compared
+ * \param q second category being compared
+ *
+ * \retval -1 (p < q)
+ * \retval 0 (p == q)
+ * \retval 1 (p > q)
+ */
+static int acl_order_comparator(struct ast_category *p, struct ast_category *q)
+{
+       int p_value = 0, q_value = 0;
+       struct ast_variable *p_var = ast_category_first(p);
+       struct ast_variable *q_var = ast_category_first(q);
+
+       while (p_var) {
+               if (!strcasecmp(p_var->name, "rule_order")) {
+                       p_value = atoi(p_var->value);
+                       break;
+               }
+               p_var = p_var->next;
+       }
+
+       while (q_var) {
+               if (!strcasecmp(q_var->name, "rule_order")) {
+                       q_value = atoi(q_var->value);
+                       break;
+               }
+               q_var = q_var->next;
+       }
+
+       if (p_value < q_value) {
+               return -1;
+       } else if (q_value < p_value) {
+               return 1;
+       }
+
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief Search for a named ACL via realtime Database and build the named_acl
+ *        if it is valid.
+ *
+ * \param name of the ACL wanted to be found
+ * \retval pointer to the named ACL if available. Null if the ACL subsystem is unconfigured.
+ */
+static struct named_acl *named_acl_find_realtime(const char *name)
+{
+       struct ast_config *cfg;
+       char *item = NULL;
+       const char *systemname = NULL;
+       struct ast_ha *built_ha = NULL;
+       struct named_acl *acl;
+
+       RAII_VAR(struct named_acl_config *, acl_options, ao2_global_obj_ref(globals), ao2_cleanup);
+
+       /* If we have a systemname set in the global options, we only want to retrieve entries with a matching systemname field. */
+       if (acl_options) {
+               systemname = acl_options->global->systemname;
+       }
+
+       if (ast_strlen_zero(systemname)) {
+               cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, SENTINEL);
+       } else {
+               cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, "systemname", systemname, SENTINEL);
+       }
+
+       if (!cfg) {
+               return NULL;
+       }
+
+       /* At this point, the configuration must be sorted by the order field. */
+       ast_config_sort_categories(cfg, 0, acl_order_comparator);
+
+       while ((item = ast_category_browse(cfg, item))) {
+               int append_ha_error = 0;
+               const char *order = ast_variable_retrieve(cfg, item, "rule_order");
+               const char *sense = ast_variable_retrieve(cfg, item, "sense");
+               const char *rule = ast_variable_retrieve(cfg, item, "rule");
+
+               built_ha = ast_append_ha(sense, rule, built_ha, &append_ha_error);
+               if (append_ha_error) {
+                       /* We need to completely reject an ACL that contains any bad rules. */
+                       ast_log(LOG_ERROR, "Rejecting realtime ACL due to bad ACL definition '%s': %s - %s - %s\n", name, order, sense, rule);
+                       ast_free_ha(built_ha);
+                       return NULL;
+               }
+       }
+
+       ast_config_destroy(cfg);
+
+       acl = named_acl_alloc(name);
+       if (!acl) {
+               ast_log(LOG_ERROR, "allocation error\n");
+               ast_free_ha(built_ha);
+               return NULL;
+       }
+
+       acl->ha = built_ha;
+
+       return acl;
+}
+
+struct ast_ha *ast_named_acl_find(const char *name, int *is_realtime, int *is_undefined) {
+       struct ast_ha *ha = NULL;
+
+       RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
+       RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup);
+
+       if (is_realtime) {
+               *is_realtime = 0;
+       }
+
+       if (is_undefined) {
+               *is_undefined = 0;
+       }
+
+       /* If the config or its named_acl_list hasn't been initialized, abort immediately. */
+       if ((!cfg) || (!(cfg->named_acl_list))) {
+               ast_log(LOG_ERROR, "Attempted to find named ACL '%s', but the ACL configuration isn't available.\n", name);
+               return NULL;
+       }
+
+       named_acl = named_acl_find(cfg->named_acl_list, name);
+
+       /* If a named ACL couldn't be retrieved locally, we need to try realtime storage. */
+       if (!named_acl) {
+               RAII_VAR(struct named_acl *, realtime_acl, NULL, ao2_cleanup);
+
+               /* Attempt to create from realtime */
+               if ((realtime_acl = named_acl_find_realtime(name))) {
+                       if (is_realtime) {
+                               *is_realtime = 1;
+                       }
+                       ha = ast_duplicate_ha_list(realtime_acl->ha);
+                       return ha;
+               }
+
+               /* Couldn't create from realtime. Raise relevant flags and print relevant warnings. */
+               if (ast_realtime_is_mapping_defined(ACL_FAMILY) && !ast_check_realtime(ACL_FAMILY)) {
+                       ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n"
+                               "This ACL may exist in the configured realtime backend, but that backend hasn't been registered yet. "
+                               "Fix this establishing preload for the backend in 'modules.conf'.\n", name);
+               } else {
+                       ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n", name);
+               }
+
+               if (is_undefined) {
+                       *is_undefined = 1;
+               }
+
+               return NULL;
+       }
+
+       ha = ast_duplicate_ha_list(named_acl->ha);
+
+       if (!ha) {
+               ast_log(LOG_NOTICE, "ACL '%s' contains no rules. It is valid, but it will accept addresses unconditionally.\n", name);
+       }
+
+       return ha;
+}
+
+/*!
+ * \internal
+ * \brief Sends an update event corresponding to a given named ACL that has changed.
+ *
+ * \param name Name of the ACL that has changed. May be an empty string (but not NULL)
+ *        If name is an empty string, then all ACLs must be refreshed.
+ *
+ * \retval 0 success
+ * \retval 1 failure
+ */
+static int push_acl_change_event(char *name)
+{
+       struct ast_event *event = ast_event_new(AST_EVENT_ACL_CHANGE,
+                                                       AST_EVENT_IE_DESCRIPTION, AST_EVENT_IE_PLTYPE_STR, name,
+                                                       AST_EVENT_IE_END);
+       if (!event) {
+               ast_log(LOG_ERROR, "Failed to allocate acl.conf reload event. Some modules will have out of date ACLs.\n");
+               return -1;
+       }
+
+       if (ast_event_queue(event)) {
+               ast_event_destroy(event);
+               ast_log(LOG_ERROR, "Failed to queue acl.conf reload event. Some modules will have out of date ACLs.\n");
+               return -1;
+       }
+
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief reload configuration for named ACLs
+ *
+ * \param fd file descriptor for CLI client
+ */
+int ast_named_acl_reload(void)
+{
+       enum aco_process_status status;
+
+       status = aco_process_config(&cfg_info, 1);
+
+       if (status == ACO_PROCESS_ERROR) {
+               ast_log(LOG_WARNING, "Could not reload ACL config\n");
+               return 0;
+       }
+
+       if (status == ACO_PROCESS_UNCHANGED) {
+               /* We don't actually log anything if the config was unchanged,
+                * but we don't need to send a config change event either.
+                */
+               return 0;
+       }
+
+       /* We need to push an ACL change event with no ACL name so that all subscribers update with all ACLs */
+       push_acl_change_event("");
+
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief secondary handler for the 'acl show <name>' command (with arg)
+ *
+ * \param fd file descriptor of the cli
+ * \name name of the ACL requested for display
+ */
+static void cli_display_named_acl(int fd, const char *name)
+{
+       struct ast_ha *ha;
+       int ha_index = 0;
+       int is_realtime = 0;
+
+       RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
+       RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup);
+
+       /* If the configuration or the configuration's named_acl_list is unavailable, abort. */
+       if ((!cfg) || (!cfg->named_acl_list)) {
+               ast_log(LOG_ERROR, "Attempted to show named ACL '%s', but the acl configuration isn't available.\n", name);
+               return;
+       }
+
+       named_acl = named_acl_find(cfg->named_acl_list, name);
+
+       /* If the named_acl couldn't be found with the search, also abort. */
+       if (!named_acl) {
+               if (!(named_acl = named_acl_find_realtime(name))) {
+                       ast_cli(fd, "\nCould not find ACL named '%s'\n", name);
+                       return;
+               }
+
+               is_realtime = 1;
+       }
+
+       ast_cli(fd, "\nACL: %s%s\n---------------------------------------------\n", name, is_realtime ? " (realtime)" : "");
+       for (ha = named_acl->ha; ha; ha = ha->next) {
+               char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
+               char *mask = ast_sockaddr_stringify_addr(&ha->netmask);
+               ast_cli(fd, "%3d: %s - %s/%s\n", ha_index, ha->sense == AST_SENSE_ALLOW ? "allow" : " deny", addr, mask);
+               ha_index++;
+       }
+}
+
+/*!
+ * \internal
+ * \brief secondary handler for the 'acl show' command (no args)
+ *
+ * \param fd file descriptor of the cli
+ */
+static void cli_display_named_acl_list(int fd)
+{
+       struct ao2_iterator i;
+       void *o;
+       RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
+
+       ast_cli(fd, "\nacl\n---\n");
+
+       if (!cfg || !cfg->named_acl_list) {
+               ast_cli(fd, "ACL configuration isn't available.\n");
+               return;
+       }
+       i = ao2_iterator_init(cfg->named_acl_list, 0);
+
+       while ((o = ao2_iterator_next(&i))) {
+               struct named_acl *named_acl = o;
+               ast_cli(fd, "%s\n", named_acl->name);
+               ao2_ref(o, -1);
+       }
+
+       ao2_iterator_destroy(&i);
+}
+
+/* \brief ACL command show <name> */
+static char *handle_show_named_acl_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "acl show";
+               e->usage =
+                       "Usage: acl show [name]\n"
+                       "   Shows a list of named ACLs or lists all entries in a given named ACL.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       }
+
+       if (a->argc == 2) {
+               cli_display_named_acl_list(a->fd);
+               return CLI_SUCCESS;
+       }
+
+       if (a->argc == 3) {
+               cli_display_named_acl(a->fd, a->argv[2]);
+               return CLI_SUCCESS;
+       }
+
+
+       return CLI_SHOWUSAGE;
+}
+
+static struct ast_cli_entry cli_named_acl[] = {
+       AST_CLI_DEFINE(handle_show_named_acl_cmd, "Show a named ACL or list all named ACLs"),
+};
+
+int ast_named_acl_init()
+{
+       ast_cli_register_multiple(cli_named_acl, ARRAY_LEN(cli_named_acl));
+
+       if (aco_info_init(&cfg_info)) {
+               return 0;
+       }
+
+       /* Register the global options */
+       aco_option_register(&cfg_info, "systemname", ACO_EXACT, global_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct named_acl_global_config, systemname));
+
+       /* Register the per level options. */
+       aco_option_register(&cfg_info, "permit", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 1, FLDSET(struct named_acl, ha));
+       aco_option_register(&cfg_info, "deny", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 0, FLDSET(struct named_acl, ha));
+
+       if (aco_process_config(&cfg_info, 0)) {
+               return 0;
+       }
+
+       return 0;
+}