2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2012, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
7 * Jonathan Rose <jrose@digium.com>
9 * See http://www.asterisk.org for more information about
10 * the Asterisk project. Please do not directly contact
11 * any of the maintainers of this project for assistance;
12 * the project provides a web site, mailing lists and IRC
13 * channels for your use.
15 * This program is free software, distributed under the terms of
16 * the GNU General Public License Version 2. See the LICENSE file
17 * at the top of the source tree.
22 * \brief Named Access Control Lists
24 * \author Jonathan Rose <jrose@digium.com>
26 * \note Based on a feature proposed by
27 * Olle E. Johansson <oej@edvina.net>
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34 #include "asterisk/config.h"
35 #include "asterisk/config_options.h"
36 #include "asterisk/utils.h"
37 #include "asterisk/module.h"
38 #include "asterisk/cli.h"
39 #include "asterisk/acl.h"
40 #include "asterisk/astobj2.h"
41 #include "asterisk/paths.h"
42 #include "asterisk/stasis.h"
43 #include "asterisk/json.h"
44 #include "asterisk/security_events.h"
46 #define NACL_CONFIG "acl.conf"
47 #define ACL_FAMILY "acls"
50 <configInfo name="named_acl" language="en_US">
51 <configFile name="named_acl.conf">
52 <configObject name="named_acl">
53 <synopsis>Options for configuring a named ACL</synopsis>
54 <configOption name="permit">
55 <synopsis>An address/subnet from which to allow access</synopsis>
57 <configOption name="deny">
58 <synopsis>An address/subnet from which to disallow access</synopsis>
66 * Configuration structure - holds pointers to ao2 containers used for configuration
67 * Since there isn't a general level or any other special levels for acl.conf at this
68 * time, it's really a config options friendly wrapper for the named ACL container
70 struct named_acl_config {
71 struct ao2_container *named_acl_list;
74 static AO2_GLOBAL_OBJ_STATIC(globals);
76 /*! \note These functions are used for placing/retrieving named ACLs in their ao2_container. */
77 static void *named_acl_config_alloc(void);
78 static void *named_acl_alloc(const char *cat);
79 static void *named_acl_find(struct ao2_container *container, const char *cat);
81 /* Config type for named ACL profiles (must not be named general) */
82 static struct aco_type named_acl_type = {
83 .type = ACO_ITEM, /*!< named_acls are items stored in containers, not individual global objects */
85 .category_match = ACO_BLACKLIST,
86 .category = "^general$", /*!< Match everything but "general" */
87 .item_alloc = named_acl_alloc, /*!< A callback to allocate a new named_acl based on category */
88 .item_find = named_acl_find, /*!< A callback to find a named_acl in some container of named_acls */
89 .item_offset = offsetof(struct named_acl_config, named_acl_list), /*!< Could leave this out since 0 */
92 /* This array of aco_type structs is necessary to use aco_option_register */
93 struct aco_type *named_acl_types[] = ACO_TYPES(&named_acl_type);
95 struct aco_file named_acl_conf = {
96 .filename = "acl.conf",
97 .types = ACO_TYPES(&named_acl_type),
100 /* Create a config info struct that describes the config processing for named ACLs. */
101 CONFIG_INFO_CORE("named_acl", cfg_info, globals, named_acl_config_alloc,
102 .files = ACO_FILES(&named_acl_conf),
107 char name[ACL_NAME_LENGTH]; /* Same max length as a configuration category */
110 static int named_acl_hash_fn(const void *obj, const int flags)
112 const struct named_acl *entry = obj;
113 return ast_str_hash(entry->name);
116 static int named_acl_cmp_fn(void *obj, void *arg, const int flags)
118 struct named_acl *entry1 = obj;
119 struct named_acl *entry2 = arg;
121 return (!strcmp(entry1->name, entry2->name)) ? (CMP_MATCH | CMP_STOP) : 0;
124 /*! \brief destructor for named_acl_config */
125 static void named_acl_config_destructor(void *obj)
127 struct named_acl_config *cfg = obj;
128 ao2_cleanup(cfg->named_acl_list);
131 /*! \brief allocator callback for named_acl_config. Notice it returns void * since it is used by
132 * the backend config code
134 static void *named_acl_config_alloc(void)
136 struct named_acl_config *cfg;
138 if (!(cfg = ao2_alloc(sizeof(*cfg), named_acl_config_destructor))) {
142 if (!(cfg->named_acl_list = ao2_container_alloc(37, named_acl_hash_fn, named_acl_cmp_fn))) {
153 /*! \brief Destroy a named ACL object */
154 static void destroy_named_acl(void *obj)
156 struct named_acl *named_acl = obj;
157 ast_free_ha(named_acl->ha);
161 * \brief Create a named ACL structure
163 * \param cat name given to the ACL
164 * \retval NULL failure
165 *\retval non-NULL successfully allocated named ACL
167 static void *named_acl_alloc(const char *cat)
169 struct named_acl *named_acl;
171 named_acl = ao2_alloc(sizeof(*named_acl), destroy_named_acl);
176 ast_copy_string(named_acl->name, cat, sizeof(named_acl->name));
182 * \brief Find a named ACL in a container by its name
184 * \param container ao2container holding the named ACLs
185 * \param cat name of the ACL wanted to be found
186 * \retval pointer to the named ACL if available. Null if not found.
188 static void *named_acl_find(struct ao2_container *container, const char *cat)
190 struct named_acl tmp;
191 ast_copy_string(tmp.name, cat, sizeof(tmp.name));
192 return ao2_find(container, &tmp, OBJ_POINTER);
197 * \brief Callback function to compare the ACL order of two given categories.
198 * This function is used to sort lists of ACLs received from realtime.
200 * \param p first category being compared
201 * \param q second category being compared
207 static int acl_order_comparator(struct ast_category *p, struct ast_category *q)
209 int p_value = 0, q_value = 0;
210 struct ast_variable *p_var = ast_category_first(p);
211 struct ast_variable *q_var = ast_category_first(q);
214 if (!strcasecmp(p_var->name, "rule_order")) {
215 p_value = atoi(p_var->value);
222 if (!strcasecmp(q_var->name, "rule_order")) {
223 q_value = atoi(q_var->value);
229 if (p_value < q_value) {
231 } else if (q_value < p_value) {
240 * \brief Search for a named ACL via realtime Database and build the named_acl
243 * \param name of the ACL wanted to be found
244 * \retval pointer to the named ACL if available. Null if the ACL subsystem is unconfigured.
246 static struct named_acl *named_acl_find_realtime(const char *name)
248 struct ast_config *cfg;
250 const char *systemname = NULL;
251 struct ast_ha *built_ha = NULL;
252 struct named_acl *acl;
254 /* If we have a systemname set in the global options, we only want to retrieve entries with a matching systemname field. */
255 systemname = ast_config_AST_SYSTEM_NAME;
257 if (ast_strlen_zero(systemname)) {
258 cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, SENTINEL);
260 cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, "systemname", systemname, SENTINEL);
267 /* At this point, the configuration must be sorted by the order field. */
268 ast_config_sort_categories(cfg, 0, acl_order_comparator);
270 while ((item = ast_category_browse(cfg, item))) {
271 int append_ha_error = 0;
272 const char *order = ast_variable_retrieve(cfg, item, "rule_order");
273 const char *sense = ast_variable_retrieve(cfg, item, "sense");
274 const char *rule = ast_variable_retrieve(cfg, item, "rule");
276 built_ha = ast_append_ha(sense, rule, built_ha, &append_ha_error);
277 if (append_ha_error) {
278 /* We need to completely reject an ACL that contains any bad rules. */
279 ast_log(LOG_ERROR, "Rejecting realtime ACL due to bad ACL definition '%s': %s - %s - %s\n", name, order, sense, rule);
280 ast_free_ha(built_ha);
285 ast_config_destroy(cfg);
287 acl = named_acl_alloc(name);
289 ast_log(LOG_ERROR, "allocation error\n");
290 ast_free_ha(built_ha);
299 struct ast_ha *ast_named_acl_find(const char *name, int *is_realtime, int *is_undefined)
301 struct ast_ha *ha = NULL;
303 RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
304 RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup);
314 /* If the config or its named_acl_list hasn't been initialized, abort immediately. */
315 if ((!cfg) || (!(cfg->named_acl_list))) {
316 ast_log(LOG_ERROR, "Attempted to find named ACL '%s', but the ACL configuration isn't available.\n", name);
320 named_acl = named_acl_find(cfg->named_acl_list, name);
322 /* If a named ACL couldn't be retrieved locally, we need to try realtime storage. */
324 RAII_VAR(struct named_acl *, realtime_acl, NULL, ao2_cleanup);
326 /* Attempt to create from realtime */
327 if ((realtime_acl = named_acl_find_realtime(name))) {
331 ha = ast_duplicate_ha_list(realtime_acl->ha);
335 /* Couldn't create from realtime. Raise relevant flags and print relevant warnings. */
336 if (ast_realtime_is_mapping_defined(ACL_FAMILY) && !ast_check_realtime(ACL_FAMILY)) {
337 ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n"
338 "This ACL may exist in the configured realtime backend, but that backend hasn't been registered yet. "
339 "Fix this establishing preload for the backend in 'modules.conf'.\n", name);
341 ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n", name);
351 ha = ast_duplicate_ha_list(named_acl->ha);
354 ast_log(LOG_NOTICE, "ACL '%s' contains no rules. It is valid, but it will accept addresses unconditionally.\n", name);
360 /*! \brief Message type for named ACL changes */
361 STASIS_MESSAGE_TYPE_DEFN(ast_named_acl_change_type);
365 * \brief Sends a stasis message corresponding to a given named ACL that has changed or
366 * that all ACLs have been updated and old copies must be refreshed. Consumers of
367 * named ACLs should subscribe to the ast_security_topic and respond to messages
368 * of the ast_named_acl_change_type stasis message type in order to be able to
369 * accommodate changes to named ACLs.
371 * \param name Name of the ACL that has changed. May be an empty string (but not NULL)
372 * If name is an empty string, then all ACLs must be refreshed.
377 static int publish_acl_change(const char *name)
379 RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
380 RAII_VAR(struct ast_json_payload *, json_payload, NULL, ao2_cleanup);
381 RAII_VAR(struct ast_json *, json_object, ast_json_object_create(), ast_json_unref);
383 if (!json_object || !ast_named_acl_change_type()) {
384 goto publish_failure;
387 if (ast_json_object_set(json_object, "name", ast_json_string_create(name))) {
388 goto publish_failure;
391 if (!(json_payload = ast_json_payload_create(json_object))) {
392 goto publish_failure;
395 msg = stasis_message_create(ast_named_acl_change_type(), json_payload);
398 goto publish_failure;
401 stasis_publish(ast_security_topic(), msg);
406 ast_log(LOG_ERROR, "Failed to to issue ACL change message for %s.\n",
407 ast_strlen_zero(name) ? "all named ACLs" : name);
413 * \brief reload configuration for named ACLs
415 * \param fd file descriptor for CLI client
417 int ast_named_acl_reload(void)
419 enum aco_process_status status;
421 status = aco_process_config(&cfg_info, 1);
423 if (status == ACO_PROCESS_ERROR) {
424 ast_log(LOG_WARNING, "Could not reload ACL config\n");
428 if (status == ACO_PROCESS_UNCHANGED) {
429 /* We don't actually log anything if the config was unchanged,
430 * but we don't need to send a config change event either.
435 /* We need to push an ACL change event with no ACL name so that all subscribers update with all ACLs */
436 publish_acl_change("");
443 * \brief secondary handler for the 'acl show <name>' command (with arg)
445 * \param fd file descriptor of the cli
446 * \name name of the ACL requested for display
448 static void cli_display_named_acl(int fd, const char *name)
454 RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
455 RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup);
457 /* If the configuration or the configuration's named_acl_list is unavailable, abort. */
458 if ((!cfg) || (!cfg->named_acl_list)) {
459 ast_log(LOG_ERROR, "Attempted to show named ACL '%s', but the acl configuration isn't available.\n", name);
463 named_acl = named_acl_find(cfg->named_acl_list, name);
465 /* If the named_acl couldn't be found with the search, also abort. */
467 if (!(named_acl = named_acl_find_realtime(name))) {
468 ast_cli(fd, "\nCould not find ACL named '%s'\n", name);
475 ast_cli(fd, "\nACL: %s%s\n---------------------------------------------\n", name, is_realtime ? " (realtime)" : "");
476 for (ha = named_acl->ha; ha; ha = ha->next) {
477 char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
478 char *mask = ast_sockaddr_stringify_addr(&ha->netmask);
479 ast_cli(fd, "%3d: %s - %s/%s\n", ha_index, ha->sense == AST_SENSE_ALLOW ? "allow" : " deny", addr, mask);
486 * \brief secondary handler for the 'acl show' command (no args)
488 * \param fd file descriptor of the cli
490 static void cli_display_named_acl_list(int fd)
492 struct ao2_iterator i;
494 RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
496 ast_cli(fd, "\nacl\n---\n");
498 if (!cfg || !cfg->named_acl_list) {
499 ast_cli(fd, "ACL configuration isn't available.\n");
502 i = ao2_iterator_init(cfg->named_acl_list, 0);
504 while ((o = ao2_iterator_next(&i))) {
505 struct named_acl *named_acl = o;
506 ast_cli(fd, "%s\n", named_acl->name);
510 ao2_iterator_destroy(&i);
513 /* \brief ACL command show <name> */
514 static char *handle_show_named_acl_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
516 RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
519 struct ao2_iterator i;
520 struct named_acl *named_acl;
525 e->command = "acl show";
527 "Usage: acl show [name]\n"
528 " Shows a list of named ACLs or lists all entries in a given named ACL.\n";
534 length = strlen(a->word);
536 i = ao2_iterator_init(cfg->named_acl_list, 0);
537 while ((named_acl = ao2_iterator_next(&i))) {
538 if (!strncasecmp(a->word, named_acl->name, length) && ++which > a->n) {
539 match = ast_strdup(named_acl->name);
540 ao2_ref(named_acl, -1);
543 ao2_ref(named_acl, -1);
545 ao2_iterator_destroy(&i);
551 cli_display_named_acl_list(a->fd);
556 cli_display_named_acl(a->fd, a->argv[2]);
561 return CLI_SHOWUSAGE;
564 static struct ast_cli_entry cli_named_acl[] = {
565 AST_CLI_DEFINE(handle_show_named_acl_cmd, "Show a named ACL or list all named ACLs"),
568 static void named_acl_cleanup(void)
570 ast_cli_unregister_multiple(cli_named_acl, ARRAY_LEN(cli_named_acl));
572 STASIS_MESSAGE_TYPE_CLEANUP(ast_named_acl_change_type);
573 aco_info_destroy(&cfg_info);
574 ao2_global_obj_release(globals);
577 int ast_named_acl_init()
579 ast_cli_register_multiple(cli_named_acl, ARRAY_LEN(cli_named_acl));
581 STASIS_MESSAGE_TYPE_INIT(ast_named_acl_change_type);
583 ast_register_cleanup(named_acl_cleanup);
585 if (aco_info_init(&cfg_info)) {
589 /* Register the per level options. */
590 aco_option_register(&cfg_info, "permit", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 1, FLDSET(struct named_acl, ha));
591 aco_option_register(&cfg_info, "deny", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 0, FLDSET(struct named_acl, ha));
593 aco_process_config(&cfg_info, 0);