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"
43 #define NACL_CONFIG "acl.conf"
44 #define ACL_FAMILY "acls"
46 struct named_acl_global_config {
47 AST_DECLARE_STRING_FIELDS(
48 AST_STRING_FIELD(systemname);
53 * Configuration structure - holds pointers to ao2 containers used for configuration
54 * Since there isn't a general level or any other special levels for acl.conf at this
55 * time, it's really a config options friendly wrapper for the named ACL container
57 struct named_acl_config {
58 struct named_acl_global_config *global;
59 struct ao2_container *named_acl_list;
62 static AO2_GLOBAL_OBJ_STATIC(globals);
64 /*! \note These functions are used for placing/retrieving named ACLs in their ao2_container. */
65 static void *named_acl_config_alloc(void);
66 static void *named_acl_alloc(const char *cat);
67 static void *named_acl_find(struct ao2_container *container, const char *cat);
69 /* Config type for named ACL profiles (must not be named general) */
70 static struct aco_type named_acl_type = {
71 .type = ACO_ITEM, /*!< named_acls are items stored in containers, not individual global objects */
72 .category_match = ACO_BLACKLIST,
73 .category = "^general$", /*!< Match everything but "general" */
74 .item_alloc = named_acl_alloc, /*!< A callback to allocate a new named_acl based on category */
75 .item_find = named_acl_find, /*!< A callback to find a named_acl in some container of named_acls */
76 .item_offset = offsetof(struct named_acl_config, named_acl_list), /*!< Could leave this out since 0 */
79 /* Config type for the general part of the ACL profile (must be named general) */
80 static struct aco_type global_option = {
82 .item_offset = offsetof(struct named_acl_config, global),
83 .category_match = ACO_WHITELIST,
84 .category = "^general$",
87 /* This array of aco_type structs is necessary to use aco_option_register */
88 struct aco_type *named_acl_types[] = ACO_TYPES(&named_acl_type);
90 struct aco_type *global_options[] = ACO_TYPES(&global_option);
92 struct aco_file named_acl_conf = {
93 .filename = "acl.conf",
94 .types = ACO_TYPES(&named_acl_type, &global_option),
97 /* Create a config info struct that describes the config processing for named ACLs. */
98 CONFIG_INFO_STANDARD(cfg_info, globals, named_acl_config_alloc,
99 .files = ACO_FILES(&named_acl_conf),
104 char name[ACL_NAME_LENGTH]; /* Same max length as a configuration category */
107 static int named_acl_hash_fn(const void *obj, const int flags)
109 const struct named_acl *entry = obj;
110 return ast_str_hash(entry->name);
113 static int named_acl_cmp_fn(void *obj, void *arg, const int flags)
115 struct named_acl *entry1 = obj;
116 struct named_acl *entry2 = arg;
118 return (!strcmp(entry1->name, entry2->name)) ? (CMP_MATCH | CMP_STOP) : 0;
121 /*! \brief destructor for named_acl_config */
122 static void named_acl_config_destructor(void *obj)
124 struct named_acl_config *cfg = obj;
125 ao2_cleanup(cfg->named_acl_list);
126 ao2_cleanup(cfg->global);
129 static void named_acl_global_config_destructor(void *obj)
131 struct named_acl_global_config *global = obj;
132 ast_string_field_free_memory(global);
135 /*! \brief allocator callback for named_acl_config. Notice it returns void * since it is used by
136 * the backend config code
138 static void *named_acl_config_alloc(void)
140 struct named_acl_config *cfg;
142 if (!(cfg = ao2_alloc(sizeof(*cfg), named_acl_config_destructor))) {
146 if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), named_acl_global_config_destructor))) {
150 if (ast_string_field_init(cfg->global, 128)) {
154 if (!(cfg->named_acl_list = ao2_container_alloc(37, named_acl_hash_fn, named_acl_cmp_fn))) {
165 /*! \brief Destroy a named ACL object */
166 static void destroy_named_acl(void *obj)
168 struct named_acl *named_acl = obj;
169 ast_free_ha(named_acl->ha);
173 * \brief Create a named ACL structure
175 * \param cat name given to the ACL
176 * \retval NULL failure
177 *\retval non-NULL successfully allocated named ACL
179 void *named_acl_alloc(const char *cat)
181 struct named_acl *named_acl;
183 named_acl = ao2_alloc(sizeof(*named_acl), destroy_named_acl);
188 ast_copy_string(named_acl->name, cat, sizeof(named_acl->name));
194 * \brief Find a named ACL in a container by its name
196 * \param container ao2container holding the named ACLs
197 * \param name of the ACL wanted to be found
198 * \retval pointer to the named ACL if available. Null if not found.
200 void *named_acl_find(struct ao2_container *container, const char *cat)
202 struct named_acl tmp;
203 ast_copy_string(tmp.name, cat, sizeof(tmp.name));
204 return ao2_find(container, &tmp, OBJ_POINTER);
209 * \brief Callback function to compare the ACL order of two given categories.
210 * This function is used to sort lists of ACLs received from realtime.
212 * \param p first category being compared
213 * \param q second category being compared
219 static int acl_order_comparator(struct ast_category *p, struct ast_category *q)
221 int p_value = 0, q_value = 0;
222 struct ast_variable *p_var = ast_category_first(p);
223 struct ast_variable *q_var = ast_category_first(q);
226 if (!strcasecmp(p_var->name, "rule_order")) {
227 p_value = atoi(p_var->value);
234 if (!strcasecmp(q_var->name, "rule_order")) {
235 q_value = atoi(q_var->value);
241 if (p_value < q_value) {
243 } else if (q_value < p_value) {
252 * \brief Search for a named ACL via realtime Database and build the named_acl
255 * \param name of the ACL wanted to be found
256 * \retval pointer to the named ACL if available. Null if the ACL subsystem is unconfigured.
258 static struct named_acl *named_acl_find_realtime(const char *name)
260 struct ast_config *cfg;
262 const char *systemname = NULL;
263 struct ast_ha *built_ha = NULL;
264 struct named_acl *acl;
266 RAII_VAR(struct named_acl_config *, acl_options, ao2_global_obj_ref(globals), ao2_cleanup);
268 /* If we have a systemname set in the global options, we only want to retrieve entries with a matching systemname field. */
270 systemname = acl_options->global->systemname;
273 if (ast_strlen_zero(systemname)) {
274 cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, SENTINEL);
276 cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, "systemname", systemname, SENTINEL);
283 /* At this point, the configuration must be sorted by the order field. */
284 ast_config_sort_categories(cfg, 0, acl_order_comparator);
286 while ((item = ast_category_browse(cfg, item))) {
287 int append_ha_error = 0;
288 const char *order = ast_variable_retrieve(cfg, item, "rule_order");
289 const char *sense = ast_variable_retrieve(cfg, item, "sense");
290 const char *rule = ast_variable_retrieve(cfg, item, "rule");
292 built_ha = ast_append_ha(sense, rule, built_ha, &append_ha_error);
293 if (append_ha_error) {
294 /* We need to completely reject an ACL that contains any bad rules. */
295 ast_log(LOG_ERROR, "Rejecting realtime ACL due to bad ACL definition '%s': %s - %s - %s\n", name, order, sense, rule);
296 ast_free_ha(built_ha);
301 ast_config_destroy(cfg);
303 acl = named_acl_alloc(name);
305 ast_log(LOG_ERROR, "allocation error\n");
306 ast_free_ha(built_ha);
315 struct ast_ha *ast_named_acl_find(const char *name, int *is_realtime, int *is_undefined) {
316 struct ast_ha *ha = NULL;
318 RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
319 RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup);
329 /* If the config or its named_acl_list hasn't been initialized, abort immediately. */
330 if ((!cfg) || (!(cfg->named_acl_list))) {
331 ast_log(LOG_ERROR, "Attempted to find named ACL '%s', but the ACL configuration isn't available.\n", name);
335 named_acl = named_acl_find(cfg->named_acl_list, name);
337 /* If a named ACL couldn't be retrieved locally, we need to try realtime storage. */
339 RAII_VAR(struct named_acl *, realtime_acl, NULL, ao2_cleanup);
341 /* Attempt to create from realtime */
342 if ((realtime_acl = named_acl_find_realtime(name))) {
346 ha = ast_duplicate_ha_list(realtime_acl->ha);
350 /* Couldn't create from realtime. Raise relevant flags and print relevant warnings. */
351 if (ast_realtime_is_mapping_defined(ACL_FAMILY) && !ast_check_realtime(ACL_FAMILY)) {
352 ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n"
353 "This ACL may exist in the configured realtime backend, but that backend hasn't been registered yet. "
354 "Fix this establishing preload for the backend in 'modules.conf'.\n", name);
356 ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n", name);
366 ha = ast_duplicate_ha_list(named_acl->ha);
369 ast_log(LOG_NOTICE, "ACL '%s' contains no rules. It is valid, but it will accept addresses unconditionally.\n", name);
377 * \brief Sends an update event corresponding to a given named ACL that has changed.
379 * \param name Name of the ACL that has changed. May be an empty string (but not NULL)
380 * If name is an empty string, then all ACLs must be refreshed.
385 static int push_acl_change_event(char *name)
387 struct ast_event *event = ast_event_new(AST_EVENT_ACL_CHANGE,
388 AST_EVENT_IE_DESCRIPTION, AST_EVENT_IE_PLTYPE_STR, name,
391 ast_log(LOG_ERROR, "Failed to allocate acl.conf reload event. Some modules will have out of date ACLs.\n");
395 if (ast_event_queue(event)) {
396 ast_event_destroy(event);
397 ast_log(LOG_ERROR, "Failed to queue acl.conf reload event. Some modules will have out of date ACLs.\n");
406 * \brief reload configuration for named ACLs
408 * \param fd file descriptor for CLI client
410 int ast_named_acl_reload(void)
412 enum aco_process_status status;
414 status = aco_process_config(&cfg_info, 1);
416 if (status == ACO_PROCESS_ERROR) {
417 ast_log(LOG_WARNING, "Could not reload ACL config\n");
421 if (status == ACO_PROCESS_UNCHANGED) {
422 /* We don't actually log anything if the config was unchanged,
423 * but we don't need to send a config change event either.
428 /* We need to push an ACL change event with no ACL name so that all subscribers update with all ACLs */
429 push_acl_change_event("");
436 * \brief secondary handler for the 'acl show <name>' command (with arg)
438 * \param fd file descriptor of the cli
439 * \name name of the ACL requested for display
441 static void cli_display_named_acl(int fd, const char *name)
447 RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
448 RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup);
450 /* If the configuration or the configuration's named_acl_list is unavailable, abort. */
451 if ((!cfg) || (!cfg->named_acl_list)) {
452 ast_log(LOG_ERROR, "Attempted to show named ACL '%s', but the acl configuration isn't available.\n", name);
456 named_acl = named_acl_find(cfg->named_acl_list, name);
458 /* If the named_acl couldn't be found with the search, also abort. */
460 if (!(named_acl = named_acl_find_realtime(name))) {
461 ast_cli(fd, "\nCould not find ACL named '%s'\n", name);
468 ast_cli(fd, "\nACL: %s%s\n---------------------------------------------\n", name, is_realtime ? " (realtime)" : "");
469 for (ha = named_acl->ha; ha; ha = ha->next) {
470 char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
471 char *mask = ast_sockaddr_stringify_addr(&ha->netmask);
472 ast_cli(fd, "%3d: %s - %s/%s\n", ha_index, ha->sense == AST_SENSE_ALLOW ? "allow" : " deny", addr, mask);
479 * \brief secondary handler for the 'acl show' command (no args)
481 * \param fd file descriptor of the cli
483 static void cli_display_named_acl_list(int fd)
485 struct ao2_iterator i;
487 RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
489 ast_cli(fd, "\nacl\n---\n");
491 if (!cfg || !cfg->named_acl_list) {
492 ast_cli(fd, "ACL configuration isn't available.\n");
495 i = ao2_iterator_init(cfg->named_acl_list, 0);
497 while ((o = ao2_iterator_next(&i))) {
498 struct named_acl *named_acl = o;
499 ast_cli(fd, "%s\n", named_acl->name);
503 ao2_iterator_destroy(&i);
506 /* \brief ACL command show <name> */
507 static char *handle_show_named_acl_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
511 e->command = "acl show";
513 "Usage: acl show [name]\n"
514 " Shows a list of named ACLs or lists all entries in a given named ACL.\n";
521 cli_display_named_acl_list(a->fd);
526 cli_display_named_acl(a->fd, a->argv[2]);
531 return CLI_SHOWUSAGE;
534 static struct ast_cli_entry cli_named_acl[] = {
535 AST_CLI_DEFINE(handle_show_named_acl_cmd, "Show a named ACL or list all named ACLs"),
538 int ast_named_acl_init()
540 ast_cli_register_multiple(cli_named_acl, ARRAY_LEN(cli_named_acl));
542 if (aco_info_init(&cfg_info)) {
546 /* Register the global options */
547 aco_option_register(&cfg_info, "systemname", ACO_EXACT, global_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct named_acl_global_config, systemname));
549 /* Register the per level options. */
550 aco_option_register(&cfg_info, "permit", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 1, FLDSET(struct named_acl, ha));
551 aco_option_register(&cfg_info, "deny", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 0, FLDSET(struct named_acl, ha));
553 if (aco_process_config(&cfg_info, 0)) {