Doxygen Updates - Title update
[asterisk/asterisk.git] / main / named_acl.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
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 #include "asterisk/paths.h"
43
44 #define NACL_CONFIG "acl.conf"
45 #define ACL_FAMILY "acls"
46
47 struct named_acl_global_config {
48         AST_DECLARE_STRING_FIELDS(
49                 /* Nothing here yet. */
50         );
51 };
52
53 /*
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
57  */
58 struct named_acl_config {
59         struct named_acl_global_config *global;
60         struct ao2_container *named_acl_list;
61 };
62
63 static AO2_GLOBAL_OBJ_STATIC(globals);
64
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);
69
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 */
78 };
79
80 /* Config type for the general part of the ACL profile (must be named general) */
81 static struct aco_type global_option = {
82         .type = ACO_GLOBAL,
83         .item_offset = offsetof(struct named_acl_config, global),
84         .category_match = ACO_WHITELIST,
85         .category = "^general$",
86 };
87
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);
90
91 struct aco_type *global_options[] = ACO_TYPES(&global_option);
92
93 struct aco_file named_acl_conf = {
94         .filename = "acl.conf",
95         .types = ACO_TYPES(&named_acl_type, &global_option),
96 };
97
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),
101 );
102
103 struct named_acl {
104         struct ast_ha *ha;
105         char name[ACL_NAME_LENGTH]; /* Same max length as a configuration category */
106 };
107
108 static int named_acl_hash_fn(const void *obj, const int flags)
109 {
110         const struct named_acl *entry = obj;
111         return ast_str_hash(entry->name);
112 }
113
114 static int named_acl_cmp_fn(void *obj, void *arg, const int flags)
115 {
116         struct named_acl *entry1 = obj;
117         struct named_acl *entry2 = arg;
118
119         return (!strcmp(entry1->name, entry2->name)) ? (CMP_MATCH | CMP_STOP) : 0;
120 }
121
122 /*! \brief destructor for named_acl_config */
123 static void named_acl_config_destructor(void *obj)
124 {
125         struct named_acl_config *cfg = obj;
126         ao2_cleanup(cfg->named_acl_list);
127         ao2_cleanup(cfg->global);
128 }
129
130 static void named_acl_global_config_destructor(void *obj)
131 {
132         struct named_acl_global_config *global = obj;
133         ast_string_field_free_memory(global);
134 }
135
136 /*! \brief allocator callback for named_acl_config. Notice it returns void * since it is used by
137  * the backend config code
138  */
139 static void *named_acl_config_alloc(void)
140 {
141         struct named_acl_config *cfg;
142
143         if (!(cfg = ao2_alloc(sizeof(*cfg), named_acl_config_destructor))) {
144                 return NULL;
145         }
146
147         if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), named_acl_global_config_destructor))) {
148                 goto error;
149         }
150
151         if (ast_string_field_init(cfg->global, 128)) {
152                 goto error;
153         }
154
155         if (!(cfg->named_acl_list = ao2_container_alloc(37, named_acl_hash_fn, named_acl_cmp_fn))) {
156                 goto error;
157         }
158
159         return cfg;
160
161 error:
162         ao2_ref(cfg, -1);
163         return NULL;
164 }
165
166 /*! \brief Destroy a named ACL object */
167 static void destroy_named_acl(void *obj)
168 {
169         struct named_acl *named_acl = obj;
170         ast_free_ha(named_acl->ha);
171 }
172
173 /*!
174  * \brief Create a named ACL structure
175  *
176  * \param cat name given to the ACL
177  * \retval NULL failure
178  *\retval non-NULL successfully allocated named ACL
179  */
180 void *named_acl_alloc(const char *cat)
181 {
182         struct named_acl *named_acl;
183
184         named_acl = ao2_alloc(sizeof(*named_acl), destroy_named_acl);
185         if (!named_acl) {
186                 return NULL;
187         }
188
189         ast_copy_string(named_acl->name, cat, sizeof(named_acl->name));
190
191         return named_acl;
192 }
193
194 /*!
195  * \brief Find a named ACL in a container by its name
196  *
197  * \param container ao2container holding the named ACLs
198  * \param cat name of the ACL wanted to be found
199  * \retval pointer to the named ACL if available. Null if not found.
200  */
201 void *named_acl_find(struct ao2_container *container, const char *cat)
202 {
203         struct named_acl tmp;
204         ast_copy_string(tmp.name, cat, sizeof(tmp.name));
205         return ao2_find(container, &tmp, OBJ_POINTER);
206 }
207
208 /*!
209  * \internal
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.
212  *
213  * \param p first category being compared
214  * \param q second category being compared
215  *
216  * \retval -1 (p < q)
217  * \retval 0 (p == q)
218  * \retval 1 (p > q)
219  */
220 static int acl_order_comparator(struct ast_category *p, struct ast_category *q)
221 {
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);
225
226         while (p_var) {
227                 if (!strcasecmp(p_var->name, "rule_order")) {
228                         p_value = atoi(p_var->value);
229                         break;
230                 }
231                 p_var = p_var->next;
232         }
233
234         while (q_var) {
235                 if (!strcasecmp(q_var->name, "rule_order")) {
236                         q_value = atoi(q_var->value);
237                         break;
238                 }
239                 q_var = q_var->next;
240         }
241
242         if (p_value < q_value) {
243                 return -1;
244         } else if (q_value < p_value) {
245                 return 1;
246         }
247
248         return 0;
249 }
250
251 /*!
252  * \internal
253  * \brief Search for a named ACL via realtime Database and build the named_acl
254  *        if it is valid.
255  *
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.
258  */
259 static struct named_acl *named_acl_find_realtime(const char *name)
260 {
261         struct ast_config *cfg;
262         char *item = NULL;
263         const char *systemname = NULL;
264         struct ast_ha *built_ha = NULL;
265         struct named_acl *acl;
266
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;
269
270         if (ast_strlen_zero(systemname)) {
271                 cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, SENTINEL);
272         } else {
273                 cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, "systemname", systemname, SENTINEL);
274         }
275
276         if (!cfg) {
277                 return NULL;
278         }
279
280         /* At this point, the configuration must be sorted by the order field. */
281         ast_config_sort_categories(cfg, 0, acl_order_comparator);
282
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");
288
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);
294                         return NULL;
295                 }
296         }
297
298         ast_config_destroy(cfg);
299
300         acl = named_acl_alloc(name);
301         if (!acl) {
302                 ast_log(LOG_ERROR, "allocation error\n");
303                 ast_free_ha(built_ha);
304                 return NULL;
305         }
306
307         acl->ha = built_ha;
308
309         return acl;
310 }
311
312 struct ast_ha *ast_named_acl_find(const char *name, int *is_realtime, int *is_undefined) {
313         struct ast_ha *ha = NULL;
314
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);
317
318         if (is_realtime) {
319                 *is_realtime = 0;
320         }
321
322         if (is_undefined) {
323                 *is_undefined = 0;
324         }
325
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);
329                 return NULL;
330         }
331
332         named_acl = named_acl_find(cfg->named_acl_list, name);
333
334         /* If a named ACL couldn't be retrieved locally, we need to try realtime storage. */
335         if (!named_acl) {
336                 RAII_VAR(struct named_acl *, realtime_acl, NULL, ao2_cleanup);
337
338                 /* Attempt to create from realtime */
339                 if ((realtime_acl = named_acl_find_realtime(name))) {
340                         if (is_realtime) {
341                                 *is_realtime = 1;
342                         }
343                         ha = ast_duplicate_ha_list(realtime_acl->ha);
344                         return ha;
345                 }
346
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);
352                 } else {
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);
354                 }
355
356                 if (is_undefined) {
357                         *is_undefined = 1;
358                 }
359
360                 return NULL;
361         }
362
363         ha = ast_duplicate_ha_list(named_acl->ha);
364
365         if (!ha) {
366                 ast_log(LOG_NOTICE, "ACL '%s' contains no rules. It is valid, but it will accept addresses unconditionally.\n", name);
367         }
368
369         return ha;
370 }
371
372 /*!
373  * \internal
374  * \brief Sends an update event corresponding to a given named ACL that has changed.
375  *
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.
378  *
379  * \retval 0 success
380  * \retval 1 failure
381  */
382 static int push_acl_change_event(char *name)
383 {
384         struct ast_event *event = ast_event_new(AST_EVENT_ACL_CHANGE,
385                                                         AST_EVENT_IE_DESCRIPTION, AST_EVENT_IE_PLTYPE_STR, name,
386                                                         AST_EVENT_IE_END);
387         if (!event) {
388                 ast_log(LOG_ERROR, "Failed to allocate acl.conf reload event. Some modules will have out of date ACLs.\n");
389                 return -1;
390         }
391
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");
395                 return -1;
396         }
397
398         return 0;
399 }
400
401 /*!
402  * \internal
403  * \brief reload configuration for named ACLs
404  *
405  * \param fd file descriptor for CLI client
406  */
407 int ast_named_acl_reload(void)
408 {
409         enum aco_process_status status;
410
411         status = aco_process_config(&cfg_info, 1);
412
413         if (status == ACO_PROCESS_ERROR) {
414                 ast_log(LOG_WARNING, "Could not reload ACL config\n");
415                 return 0;
416         }
417
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.
421                  */
422                 return 0;
423         }
424
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("");
427
428         return 0;
429 }
430
431 /*!
432  * \internal
433  * \brief secondary handler for the 'acl show <name>' command (with arg)
434  *
435  * \param fd file descriptor of the cli
436  * \name name of the ACL requested for display
437  */
438 static void cli_display_named_acl(int fd, const char *name)
439 {
440         struct ast_ha *ha;
441         int ha_index = 0;
442         int is_realtime = 0;
443
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);
446
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);
450                 return;
451         }
452
453         named_acl = named_acl_find(cfg->named_acl_list, name);
454
455         /* If the named_acl couldn't be found with the search, also abort. */
456         if (!named_acl) {
457                 if (!(named_acl = named_acl_find_realtime(name))) {
458                         ast_cli(fd, "\nCould not find ACL named '%s'\n", name);
459                         return;
460                 }
461
462                 is_realtime = 1;
463         }
464
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);
470                 ha_index++;
471         }
472 }
473
474 /*!
475  * \internal
476  * \brief secondary handler for the 'acl show' command (no args)
477  *
478  * \param fd file descriptor of the cli
479  */
480 static void cli_display_named_acl_list(int fd)
481 {
482         struct ao2_iterator i;
483         void *o;
484         RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
485
486         ast_cli(fd, "\nacl\n---\n");
487
488         if (!cfg || !cfg->named_acl_list) {
489                 ast_cli(fd, "ACL configuration isn't available.\n");
490                 return;
491         }
492         i = ao2_iterator_init(cfg->named_acl_list, 0);
493
494         while ((o = ao2_iterator_next(&i))) {
495                 struct named_acl *named_acl = o;
496                 ast_cli(fd, "%s\n", named_acl->name);
497                 ao2_ref(o, -1);
498         }
499
500         ao2_iterator_destroy(&i);
501 }
502
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)
505 {
506         switch (cmd) {
507         case CLI_INIT:
508                 e->command = "acl show";
509                 e->usage =
510                         "Usage: acl show [name]\n"
511                         "   Shows a list of named ACLs or lists all entries in a given named ACL.\n";
512                 return NULL;
513         case CLI_GENERATE:
514                 return NULL;
515         }
516
517         if (a->argc == 2) {
518                 cli_display_named_acl_list(a->fd);
519                 return CLI_SUCCESS;
520         }
521
522         if (a->argc == 3) {
523                 cli_display_named_acl(a->fd, a->argv[2]);
524                 return CLI_SUCCESS;
525         }
526
527
528         return CLI_SHOWUSAGE;
529 }
530
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"),
533 };
534
535 int ast_named_acl_init()
536 {
537         ast_cli_register_multiple(cli_named_acl, ARRAY_LEN(cli_named_acl));
538
539         if (aco_info_init(&cfg_info)) {
540                 return 0;
541         }
542
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));
546
547         if (aco_process_config(&cfg_info, 0)) {
548                 aco_info_destroy(&cfg_info);
549                 return 0;
550         }
551
552         return 0;
553 }