Named ACLs: Introduces a system for creating and sharing ACLs
[asterisk/asterisk.git] / main / named_acl.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Copyright (C) 2012, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  * Jonathan Rose <jrose@digium.com>
8  *
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.
14  *
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.
18  */
19
20 /*! \file
21  *
22  * \brief Named Access Control Lists
23  *
24  * \author Jonathan Rose <jrose@digium.com>
25  *
26  * \note Based on a feature proposed by
27  * Olle E. Johansson <oej@edvina.net>
28  */
29
30 #include "asterisk.h"
31
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33
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
43 #define NACL_CONFIG "acl.conf"
44 #define ACL_FAMILY "acls"
45
46 struct named_acl_global_config {
47         AST_DECLARE_STRING_FIELDS(
48                 AST_STRING_FIELD(systemname);
49         );
50 };
51
52 /*
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
56  */
57 struct named_acl_config {
58         struct named_acl_global_config *global;
59         struct ao2_container *named_acl_list;
60 };
61
62 static AO2_GLOBAL_OBJ_STATIC(globals);
63
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);
68
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 */
77 };
78
79 /* Config type for the general part of the ACL profile (must be named general) */
80 static struct aco_type global_option = {
81         .type = ACO_GLOBAL,
82         .item_offset = offsetof(struct named_acl_config, global),
83         .category_match = ACO_WHITELIST,
84         .category = "^general$",
85 };
86
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);
89
90 struct aco_type *global_options[] = ACO_TYPES(&global_option);
91
92 struct aco_file named_acl_conf = {
93         .filename = "acl.conf",
94         .types = ACO_TYPES(&named_acl_type, &global_option),
95 };
96
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),
100 );
101
102 struct named_acl {
103         struct ast_ha *ha;
104         char name[ACL_NAME_LENGTH]; /* Same max length as a configuration category */
105 };
106
107 static int named_acl_hash_fn(const void *obj, const int flags)
108 {
109         const struct named_acl *entry = obj;
110         return ast_str_hash(entry->name);
111 }
112
113 static int named_acl_cmp_fn(void *obj, void *arg, const int flags)
114 {
115         struct named_acl *entry1 = obj;
116         struct named_acl *entry2 = arg;
117
118         return (!strcmp(entry1->name, entry2->name)) ? (CMP_MATCH | CMP_STOP) : 0;
119 }
120
121 /*! \brief destructor for named_acl_config */
122 static void named_acl_config_destructor(void *obj)
123 {
124         struct named_acl_config *cfg = obj;
125         ao2_cleanup(cfg->named_acl_list);
126         ao2_cleanup(cfg->global);
127 }
128
129 static void named_acl_global_config_destructor(void *obj)
130 {
131         struct named_acl_global_config *global = obj;
132         ast_string_field_free_memory(global);
133 }
134
135 /*! \brief allocator callback for named_acl_config. Notice it returns void * since it is used by
136  * the backend config code
137  */
138 static void *named_acl_config_alloc(void)
139 {
140         struct named_acl_config *cfg;
141
142         if (!(cfg = ao2_alloc(sizeof(*cfg), named_acl_config_destructor))) {
143                 return NULL;
144         }
145
146         if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), named_acl_global_config_destructor))) {
147                 goto error;
148         }
149
150         if (ast_string_field_init(cfg->global, 128)) {
151                 goto error;
152         }
153
154         if (!(cfg->named_acl_list = ao2_container_alloc(37, named_acl_hash_fn, named_acl_cmp_fn))) {
155                 goto error;
156         }
157
158         return cfg;
159
160 error:
161         ao2_ref(cfg, -1);
162         return NULL;
163 }
164
165 /*! \brief Destroy a named ACL object */
166 static void destroy_named_acl(void *obj)
167 {
168         struct named_acl *named_acl = obj;
169         ast_free_ha(named_acl->ha);
170 }
171
172 /*!
173  * \brief Create a named ACL structure
174  *
175  * \param cat name given to the ACL
176  * \retval NULL failure
177  *\retval non-NULL successfully allocated named ACL
178  */
179 void *named_acl_alloc(const char *cat)
180 {
181         struct named_acl *named_acl;
182
183         named_acl = ao2_alloc(sizeof(*named_acl), destroy_named_acl);
184         if (!named_acl) {
185                 return NULL;
186         }
187
188         ast_copy_string(named_acl->name, cat, sizeof(named_acl->name));
189
190         return named_acl;
191 }
192
193 /*!
194  * \brief Find a named ACL in a container by its name
195  *
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.
199  */
200 void *named_acl_find(struct ao2_container *container, const char *cat)
201 {
202         struct named_acl tmp;
203         ast_copy_string(tmp.name, cat, sizeof(tmp.name));
204         return ao2_find(container, &tmp, OBJ_POINTER);
205 }
206
207 /*!
208  * \internal
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.
211  *
212  * \param p first category being compared
213  * \param q second category being compared
214  *
215  * \retval -1 (p < q)
216  * \retval 0 (p == q)
217  * \retval 1 (p > q)
218  */
219 static int acl_order_comparator(struct ast_category *p, struct ast_category *q)
220 {
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);
224
225         while (p_var) {
226                 if (!strcasecmp(p_var->name, "rule_order")) {
227                         p_value = atoi(p_var->value);
228                         break;
229                 }
230                 p_var = p_var->next;
231         }
232
233         while (q_var) {
234                 if (!strcasecmp(q_var->name, "rule_order")) {
235                         q_value = atoi(q_var->value);
236                         break;
237                 }
238                 q_var = q_var->next;
239         }
240
241         if (p_value < q_value) {
242                 return -1;
243         } else if (q_value < p_value) {
244                 return 1;
245         }
246
247         return 0;
248 }
249
250 /*!
251  * \internal
252  * \brief Search for a named ACL via realtime Database and build the named_acl
253  *        if it is valid.
254  *
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.
257  */
258 static struct named_acl *named_acl_find_realtime(const char *name)
259 {
260         struct ast_config *cfg;
261         char *item = NULL;
262         const char *systemname = NULL;
263         struct ast_ha *built_ha = NULL;
264         struct named_acl *acl;
265
266         RAII_VAR(struct named_acl_config *, acl_options, ao2_global_obj_ref(globals), ao2_cleanup);
267
268         /* If we have a systemname set in the global options, we only want to retrieve entries with a matching systemname field. */
269         if (acl_options) {
270                 systemname = acl_options->global->systemname;
271         }
272
273         if (ast_strlen_zero(systemname)) {
274                 cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, SENTINEL);
275         } else {
276                 cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, "systemname", systemname, SENTINEL);
277         }
278
279         if (!cfg) {
280                 return NULL;
281         }
282
283         /* At this point, the configuration must be sorted by the order field. */
284         ast_config_sort_categories(cfg, 0, acl_order_comparator);
285
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");
291
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);
297                         return NULL;
298                 }
299         }
300
301         ast_config_destroy(cfg);
302
303         acl = named_acl_alloc(name);
304         if (!acl) {
305                 ast_log(LOG_ERROR, "allocation error\n");
306                 ast_free_ha(built_ha);
307                 return NULL;
308         }
309
310         acl->ha = built_ha;
311
312         return acl;
313 }
314
315 struct ast_ha *ast_named_acl_find(const char *name, int *is_realtime, int *is_undefined) {
316         struct ast_ha *ha = NULL;
317
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);
320
321         if (is_realtime) {
322                 *is_realtime = 0;
323         }
324
325         if (is_undefined) {
326                 *is_undefined = 0;
327         }
328
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);
332                 return NULL;
333         }
334
335         named_acl = named_acl_find(cfg->named_acl_list, name);
336
337         /* If a named ACL couldn't be retrieved locally, we need to try realtime storage. */
338         if (!named_acl) {
339                 RAII_VAR(struct named_acl *, realtime_acl, NULL, ao2_cleanup);
340
341                 /* Attempt to create from realtime */
342                 if ((realtime_acl = named_acl_find_realtime(name))) {
343                         if (is_realtime) {
344                                 *is_realtime = 1;
345                         }
346                         ha = ast_duplicate_ha_list(realtime_acl->ha);
347                         return ha;
348                 }
349
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);
355                 } else {
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);
357                 }
358
359                 if (is_undefined) {
360                         *is_undefined = 1;
361                 }
362
363                 return NULL;
364         }
365
366         ha = ast_duplicate_ha_list(named_acl->ha);
367
368         if (!ha) {
369                 ast_log(LOG_NOTICE, "ACL '%s' contains no rules. It is valid, but it will accept addresses unconditionally.\n", name);
370         }
371
372         return ha;
373 }
374
375 /*!
376  * \internal
377  * \brief Sends an update event corresponding to a given named ACL that has changed.
378  *
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.
381  *
382  * \retval 0 success
383  * \retval 1 failure
384  */
385 static int push_acl_change_event(char *name)
386 {
387         struct ast_event *event = ast_event_new(AST_EVENT_ACL_CHANGE,
388                                                         AST_EVENT_IE_DESCRIPTION, AST_EVENT_IE_PLTYPE_STR, name,
389                                                         AST_EVENT_IE_END);
390         if (!event) {
391                 ast_log(LOG_ERROR, "Failed to allocate acl.conf reload event. Some modules will have out of date ACLs.\n");
392                 return -1;
393         }
394
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");
398                 return -1;
399         }
400
401         return 0;
402 }
403
404 /*!
405  * \internal
406  * \brief reload configuration for named ACLs
407  *
408  * \param fd file descriptor for CLI client
409  */
410 int ast_named_acl_reload(void)
411 {
412         enum aco_process_status status;
413
414         status = aco_process_config(&cfg_info, 1);
415
416         if (status == ACO_PROCESS_ERROR) {
417                 ast_log(LOG_WARNING, "Could not reload ACL config\n");
418                 return 0;
419         }
420
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.
424                  */
425                 return 0;
426         }
427
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("");
430
431         return 0;
432 }
433
434 /*!
435  * \internal
436  * \brief secondary handler for the 'acl show <name>' command (with arg)
437  *
438  * \param fd file descriptor of the cli
439  * \name name of the ACL requested for display
440  */
441 static void cli_display_named_acl(int fd, const char *name)
442 {
443         struct ast_ha *ha;
444         int ha_index = 0;
445         int is_realtime = 0;
446
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);
449
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);
453                 return;
454         }
455
456         named_acl = named_acl_find(cfg->named_acl_list, name);
457
458         /* If the named_acl couldn't be found with the search, also abort. */
459         if (!named_acl) {
460                 if (!(named_acl = named_acl_find_realtime(name))) {
461                         ast_cli(fd, "\nCould not find ACL named '%s'\n", name);
462                         return;
463                 }
464
465                 is_realtime = 1;
466         }
467
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);
473                 ha_index++;
474         }
475 }
476
477 /*!
478  * \internal
479  * \brief secondary handler for the 'acl show' command (no args)
480  *
481  * \param fd file descriptor of the cli
482  */
483 static void cli_display_named_acl_list(int fd)
484 {
485         struct ao2_iterator i;
486         void *o;
487         RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
488
489         ast_cli(fd, "\nacl\n---\n");
490
491         if (!cfg || !cfg->named_acl_list) {
492                 ast_cli(fd, "ACL configuration isn't available.\n");
493                 return;
494         }
495         i = ao2_iterator_init(cfg->named_acl_list, 0);
496
497         while ((o = ao2_iterator_next(&i))) {
498                 struct named_acl *named_acl = o;
499                 ast_cli(fd, "%s\n", named_acl->name);
500                 ao2_ref(o, -1);
501         }
502
503         ao2_iterator_destroy(&i);
504 }
505
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)
508 {
509         switch (cmd) {
510         case CLI_INIT:
511                 e->command = "acl show";
512                 e->usage =
513                         "Usage: acl show [name]\n"
514                         "   Shows a list of named ACLs or lists all entries in a given named ACL.\n";
515                 return NULL;
516         case CLI_GENERATE:
517                 return NULL;
518         }
519
520         if (a->argc == 2) {
521                 cli_display_named_acl_list(a->fd);
522                 return CLI_SUCCESS;
523         }
524
525         if (a->argc == 3) {
526                 cli_display_named_acl(a->fd, a->argv[2]);
527                 return CLI_SUCCESS;
528         }
529
530
531         return CLI_SHOWUSAGE;
532 }
533
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"),
536 };
537
538 int ast_named_acl_init()
539 {
540         ast_cli_register_multiple(cli_named_acl, ARRAY_LEN(cli_named_acl));
541
542         if (aco_info_init(&cfg_info)) {
543                 return 0;
544         }
545
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));
548
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));
552
553         if (aco_process_config(&cfg_info, 0)) {
554                 return 0;
555         }
556
557         return 0;
558 }