2 * Asterisk -- A telephony toolkit for Linux.
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/event.h"
37 #include "asterisk/utils.h"
38 #include "asterisk/module.h"
39 #include "asterisk/cli.h"
40 #include "asterisk/acl.h"
41 #include "asterisk/astobj2.h"
42 #include "asterisk/paths.h"
44 #define NACL_CONFIG "acl.conf"
45 #define ACL_FAMILY "acls"
47 struct named_acl_global_config {
48 AST_DECLARE_STRING_FIELDS(
49 /* Nothing here yet. */
54 * Configuration structure - holds pointers to ao2 containers used for configuration
55 * Since there isn't a general level or any other special levels for acl.conf at this
56 * time, it's really a config options friendly wrapper for the named ACL container
58 struct named_acl_config {
59 struct named_acl_global_config *global;
60 struct ao2_container *named_acl_list;
63 static AO2_GLOBAL_OBJ_STATIC(globals);
65 /*! \note These functions are used for placing/retrieving named ACLs in their ao2_container. */
66 static void *named_acl_config_alloc(void);
67 static void *named_acl_alloc(const char *cat);
68 static void *named_acl_find(struct ao2_container *container, const char *cat);
70 /* Config type for named ACL profiles (must not be named general) */
71 static struct aco_type named_acl_type = {
72 .type = ACO_ITEM, /*!< named_acls are items stored in containers, not individual global objects */
73 .category_match = ACO_BLACKLIST,
74 .category = "^general$", /*!< Match everything but "general" */
75 .item_alloc = named_acl_alloc, /*!< A callback to allocate a new named_acl based on category */
76 .item_find = named_acl_find, /*!< A callback to find a named_acl in some container of named_acls */
77 .item_offset = offsetof(struct named_acl_config, named_acl_list), /*!< Could leave this out since 0 */
80 /* Config type for the general part of the ACL profile (must be named general) */
81 static struct aco_type global_option = {
83 .item_offset = offsetof(struct named_acl_config, global),
84 .category_match = ACO_WHITELIST,
85 .category = "^general$",
88 /* This array of aco_type structs is necessary to use aco_option_register */
89 struct aco_type *named_acl_types[] = ACO_TYPES(&named_acl_type);
91 struct aco_type *global_options[] = ACO_TYPES(&global_option);
93 struct aco_file named_acl_conf = {
94 .filename = "acl.conf",
95 .types = ACO_TYPES(&named_acl_type, &global_option),
98 /* Create a config info struct that describes the config processing for named ACLs. */
99 CONFIG_INFO_STANDARD(cfg_info, globals, named_acl_config_alloc,
100 .files = ACO_FILES(&named_acl_conf),
105 char name[ACL_NAME_LENGTH]; /* Same max length as a configuration category */
108 static int named_acl_hash_fn(const void *obj, const int flags)
110 const struct named_acl *entry = obj;
111 return ast_str_hash(entry->name);
114 static int named_acl_cmp_fn(void *obj, void *arg, const int flags)
116 struct named_acl *entry1 = obj;
117 struct named_acl *entry2 = arg;
119 return (!strcmp(entry1->name, entry2->name)) ? (CMP_MATCH | CMP_STOP) : 0;
122 /*! \brief destructor for named_acl_config */
123 static void named_acl_config_destructor(void *obj)
125 struct named_acl_config *cfg = obj;
126 ao2_cleanup(cfg->named_acl_list);
127 ao2_cleanup(cfg->global);
130 static void named_acl_global_config_destructor(void *obj)
132 struct named_acl_global_config *global = obj;
133 ast_string_field_free_memory(global);
136 /*! \brief allocator callback for named_acl_config. Notice it returns void * since it is used by
137 * the backend config code
139 static void *named_acl_config_alloc(void)
141 struct named_acl_config *cfg;
143 if (!(cfg = ao2_alloc(sizeof(*cfg), named_acl_config_destructor))) {
147 if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), named_acl_global_config_destructor))) {
151 if (ast_string_field_init(cfg->global, 128)) {
155 if (!(cfg->named_acl_list = ao2_container_alloc(37, named_acl_hash_fn, named_acl_cmp_fn))) {
166 /*! \brief Destroy a named ACL object */
167 static void destroy_named_acl(void *obj)
169 struct named_acl *named_acl = obj;
170 ast_free_ha(named_acl->ha);
174 * \brief Create a named ACL structure
176 * \param cat name given to the ACL
177 * \retval NULL failure
178 *\retval non-NULL successfully allocated named ACL
180 void *named_acl_alloc(const char *cat)
182 struct named_acl *named_acl;
184 named_acl = ao2_alloc(sizeof(*named_acl), destroy_named_acl);
189 ast_copy_string(named_acl->name, cat, sizeof(named_acl->name));
195 * \brief Find a named ACL in a container by its name
197 * \param container ao2container holding the named ACLs
198 * \param name of the ACL wanted to be found
199 * \retval pointer to the named ACL if available. Null if not found.
201 void *named_acl_find(struct ao2_container *container, const char *cat)
203 struct named_acl tmp;
204 ast_copy_string(tmp.name, cat, sizeof(tmp.name));
205 return ao2_find(container, &tmp, OBJ_POINTER);
210 * \brief Callback function to compare the ACL order of two given categories.
211 * This function is used to sort lists of ACLs received from realtime.
213 * \param p first category being compared
214 * \param q second category being compared
220 static int acl_order_comparator(struct ast_category *p, struct ast_category *q)
222 int p_value = 0, q_value = 0;
223 struct ast_variable *p_var = ast_category_first(p);
224 struct ast_variable *q_var = ast_category_first(q);
227 if (!strcasecmp(p_var->name, "rule_order")) {
228 p_value = atoi(p_var->value);
235 if (!strcasecmp(q_var->name, "rule_order")) {
236 q_value = atoi(q_var->value);
242 if (p_value < q_value) {
244 } else if (q_value < p_value) {
253 * \brief Search for a named ACL via realtime Database and build the named_acl
256 * \param name of the ACL wanted to be found
257 * \retval pointer to the named ACL if available. Null if the ACL subsystem is unconfigured.
259 static struct named_acl *named_acl_find_realtime(const char *name)
261 struct ast_config *cfg;
263 const char *systemname = NULL;
264 struct ast_ha *built_ha = NULL;
265 struct named_acl *acl;
267 /* If we have a systemname set in the global options, we only want to retrieve entries with a matching systemname field. */
268 systemname = ast_config_AST_SYSTEM_NAME;
270 if (ast_strlen_zero(systemname)) {
271 cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, SENTINEL);
273 cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, "systemname", systemname, SENTINEL);
280 /* At this point, the configuration must be sorted by the order field. */
281 ast_config_sort_categories(cfg, 0, acl_order_comparator);
283 while ((item = ast_category_browse(cfg, item))) {
284 int append_ha_error = 0;
285 const char *order = ast_variable_retrieve(cfg, item, "rule_order");
286 const char *sense = ast_variable_retrieve(cfg, item, "sense");
287 const char *rule = ast_variable_retrieve(cfg, item, "rule");
289 built_ha = ast_append_ha(sense, rule, built_ha, &append_ha_error);
290 if (append_ha_error) {
291 /* We need to completely reject an ACL that contains any bad rules. */
292 ast_log(LOG_ERROR, "Rejecting realtime ACL due to bad ACL definition '%s': %s - %s - %s\n", name, order, sense, rule);
293 ast_free_ha(built_ha);
298 ast_config_destroy(cfg);
300 acl = named_acl_alloc(name);
302 ast_log(LOG_ERROR, "allocation error\n");
303 ast_free_ha(built_ha);
312 struct ast_ha *ast_named_acl_find(const char *name, int *is_realtime, int *is_undefined) {
313 struct ast_ha *ha = NULL;
315 RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
316 RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup);
326 /* If the config or its named_acl_list hasn't been initialized, abort immediately. */
327 if ((!cfg) || (!(cfg->named_acl_list))) {
328 ast_log(LOG_ERROR, "Attempted to find named ACL '%s', but the ACL configuration isn't available.\n", name);
332 named_acl = named_acl_find(cfg->named_acl_list, name);
334 /* If a named ACL couldn't be retrieved locally, we need to try realtime storage. */
336 RAII_VAR(struct named_acl *, realtime_acl, NULL, ao2_cleanup);
338 /* Attempt to create from realtime */
339 if ((realtime_acl = named_acl_find_realtime(name))) {
343 ha = ast_duplicate_ha_list(realtime_acl->ha);
347 /* Couldn't create from realtime. Raise relevant flags and print relevant warnings. */
348 if (ast_realtime_is_mapping_defined(ACL_FAMILY) && !ast_check_realtime(ACL_FAMILY)) {
349 ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n"
350 "This ACL may exist in the configured realtime backend, but that backend hasn't been registered yet. "
351 "Fix this establishing preload for the backend in 'modules.conf'.\n", name);
353 ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n", name);
363 ha = ast_duplicate_ha_list(named_acl->ha);
366 ast_log(LOG_NOTICE, "ACL '%s' contains no rules. It is valid, but it will accept addresses unconditionally.\n", name);
374 * \brief Sends an update event corresponding to a given named ACL that has changed.
376 * \param name Name of the ACL that has changed. May be an empty string (but not NULL)
377 * If name is an empty string, then all ACLs must be refreshed.
382 static int push_acl_change_event(char *name)
384 struct ast_event *event = ast_event_new(AST_EVENT_ACL_CHANGE,
385 AST_EVENT_IE_DESCRIPTION, AST_EVENT_IE_PLTYPE_STR, name,
388 ast_log(LOG_ERROR, "Failed to allocate acl.conf reload event. Some modules will have out of date ACLs.\n");
392 if (ast_event_queue(event)) {
393 ast_event_destroy(event);
394 ast_log(LOG_ERROR, "Failed to queue acl.conf reload event. Some modules will have out of date ACLs.\n");
403 * \brief reload configuration for named ACLs
405 * \param fd file descriptor for CLI client
407 int ast_named_acl_reload(void)
409 enum aco_process_status status;
411 status = aco_process_config(&cfg_info, 1);
413 if (status == ACO_PROCESS_ERROR) {
414 ast_log(LOG_WARNING, "Could not reload ACL config\n");
418 if (status == ACO_PROCESS_UNCHANGED) {
419 /* We don't actually log anything if the config was unchanged,
420 * but we don't need to send a config change event either.
425 /* We need to push an ACL change event with no ACL name so that all subscribers update with all ACLs */
426 push_acl_change_event("");
433 * \brief secondary handler for the 'acl show <name>' command (with arg)
435 * \param fd file descriptor of the cli
436 * \name name of the ACL requested for display
438 static void cli_display_named_acl(int fd, const char *name)
444 RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
445 RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup);
447 /* If the configuration or the configuration's named_acl_list is unavailable, abort. */
448 if ((!cfg) || (!cfg->named_acl_list)) {
449 ast_log(LOG_ERROR, "Attempted to show named ACL '%s', but the acl configuration isn't available.\n", name);
453 named_acl = named_acl_find(cfg->named_acl_list, name);
455 /* If the named_acl couldn't be found with the search, also abort. */
457 if (!(named_acl = named_acl_find_realtime(name))) {
458 ast_cli(fd, "\nCould not find ACL named '%s'\n", name);
465 ast_cli(fd, "\nACL: %s%s\n---------------------------------------------\n", name, is_realtime ? " (realtime)" : "");
466 for (ha = named_acl->ha; ha; ha = ha->next) {
467 char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
468 char *mask = ast_sockaddr_stringify_addr(&ha->netmask);
469 ast_cli(fd, "%3d: %s - %s/%s\n", ha_index, ha->sense == AST_SENSE_ALLOW ? "allow" : " deny", addr, mask);
476 * \brief secondary handler for the 'acl show' command (no args)
478 * \param fd file descriptor of the cli
480 static void cli_display_named_acl_list(int fd)
482 struct ao2_iterator i;
484 RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
486 ast_cli(fd, "\nacl\n---\n");
488 if (!cfg || !cfg->named_acl_list) {
489 ast_cli(fd, "ACL configuration isn't available.\n");
492 i = ao2_iterator_init(cfg->named_acl_list, 0);
494 while ((o = ao2_iterator_next(&i))) {
495 struct named_acl *named_acl = o;
496 ast_cli(fd, "%s\n", named_acl->name);
500 ao2_iterator_destroy(&i);
503 /* \brief ACL command show <name> */
504 static char *handle_show_named_acl_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
508 e->command = "acl show";
510 "Usage: acl show [name]\n"
511 " Shows a list of named ACLs or lists all entries in a given named ACL.\n";
518 cli_display_named_acl_list(a->fd);
523 cli_display_named_acl(a->fd, a->argv[2]);
528 return CLI_SHOWUSAGE;
531 static struct ast_cli_entry cli_named_acl[] = {
532 AST_CLI_DEFINE(handle_show_named_acl_cmd, "Show a named ACL or list all named ACLs"),
535 int ast_named_acl_init()
537 ast_cli_register_multiple(cli_named_acl, ARRAY_LEN(cli_named_acl));
539 if (aco_info_init(&cfg_info)) {
543 /* Register the per level options. */
544 aco_option_register(&cfg_info, "permit", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 1, FLDSET(struct named_acl, ha));
545 aco_option_register(&cfg_info, "deny", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 0, FLDSET(struct named_acl, ha));
547 if (aco_process_config(&cfg_info, 0)) {