Merge "stasis: No need to keep a stasis type ref in a stasis msg or cache object."
[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 /* This maintains the original "module reload acl" CLI command instead
31  * of replacing it with "module reload named_acl". */
32 #undef AST_MODULE
33 #define AST_MODULE "acl"
34
35 #include "asterisk.h"
36
37 #include "asterisk/config.h"
38 #include "asterisk/config_options.h"
39 #include "asterisk/utils.h"
40 #include "asterisk/module.h"
41 #include "asterisk/cli.h"
42 #include "asterisk/acl.h"
43 #include "asterisk/astobj2.h"
44 #include "asterisk/paths.h"
45 #include "asterisk/stasis.h"
46 #include "asterisk/json.h"
47 #include "asterisk/security_events.h"
48
49 #define NACL_CONFIG "acl.conf"
50 #define ACL_FAMILY "acls"
51
52 /*** DOCUMENTATION
53         <configInfo name="named_acl" language="en_US">
54                 <configFile name="named_acl.conf">
55                         <configObject name="named_acl">
56                                 <synopsis>Options for configuring a named ACL</synopsis>
57                                 <configOption name="permit">
58                                         <synopsis>An address/subnet from which to allow access</synopsis>
59                                 </configOption>
60                                 <configOption name="deny">
61                                         <synopsis>An address/subnet from which to disallow access</synopsis>
62                                 </configOption>
63                         </configObject>
64                 </configFile>
65         </configInfo>
66 ***/
67
68 /*
69  * Configuration structure - holds pointers to ao2 containers used for configuration
70  * Since there isn't a general level or any other special levels for acl.conf at this
71  * time, it's really a config options friendly wrapper for the named ACL container
72  */
73 struct named_acl_config {
74         struct ao2_container *named_acl_list;
75 };
76
77 static AO2_GLOBAL_OBJ_STATIC(globals);
78
79 /*! \note These functions are used for placing/retrieving named ACLs in their ao2_container. */
80 static void *named_acl_config_alloc(void);
81 static void *named_acl_alloc(const char *cat);
82 static void *named_acl_find(struct ao2_container *container, const char *cat);
83
84 /* Config type for named ACL profiles (must not be named general) */
85 static struct aco_type named_acl_type = {
86         .type = ACO_ITEM,                  /*!< named_acls are items stored in containers, not individual global objects */
87         .name = "named_acl",
88         .category_match = ACO_BLACKLIST_EXACT,
89         .category = "general",           /*!< Match everything but "general" */
90         .item_alloc = named_acl_alloc,     /*!< A callback to allocate a new named_acl based on category */
91         .item_find = named_acl_find,       /*!< A callback to find a named_acl in some container of named_acls */
92         .item_offset = offsetof(struct named_acl_config, named_acl_list), /*!< Could leave this out since 0 */
93 };
94
95 /* This array of aco_type structs is necessary to use aco_option_register */
96 struct aco_type *named_acl_types[] = ACO_TYPES(&named_acl_type);
97
98 struct aco_file named_acl_conf = {
99         .filename = "acl.conf",
100         .types = ACO_TYPES(&named_acl_type),
101 };
102
103 /* Create a config info struct that describes the config processing for named ACLs. */
104 CONFIG_INFO_CORE("named_acl", cfg_info, globals, named_acl_config_alloc,
105         .files = ACO_FILES(&named_acl_conf),
106 );
107
108 struct named_acl {
109         struct ast_ha *ha;
110         char name[ACL_NAME_LENGTH]; /* Same max length as a configuration category */
111 };
112
113 AO2_STRING_FIELD_HASH_FN(named_acl, name)
114 AO2_STRING_FIELD_CMP_FN(named_acl, name)
115
116 /*! \brief destructor for named_acl_config */
117 static void named_acl_config_destructor(void *obj)
118 {
119         struct named_acl_config *cfg = obj;
120         ao2_cleanup(cfg->named_acl_list);
121 }
122
123 /*! \brief allocator callback for named_acl_config. Notice it returns void * since it is used by
124  * the backend config code
125  */
126 static void *named_acl_config_alloc(void)
127 {
128         struct named_acl_config *cfg;
129
130         if (!(cfg = ao2_alloc(sizeof(*cfg), named_acl_config_destructor))) {
131                 return NULL;
132         }
133
134         if (!(cfg->named_acl_list = ao2_container_alloc(37, named_acl_hash_fn, named_acl_cmp_fn))) {
135                 goto error;
136         }
137
138         return cfg;
139
140 error:
141         ao2_ref(cfg, -1);
142         return NULL;
143 }
144
145 /*! \brief Destroy a named ACL object */
146 static void destroy_named_acl(void *obj)
147 {
148         struct named_acl *named_acl = obj;
149         ast_free_ha(named_acl->ha);
150 }
151
152 /*!
153  * \brief Create a named ACL structure
154  *
155  * \param cat name given to the ACL
156  * \retval NULL failure
157  *\retval non-NULL successfully allocated named ACL
158  */
159 static void *named_acl_alloc(const char *cat)
160 {
161         struct named_acl *named_acl;
162
163         named_acl = ao2_alloc(sizeof(*named_acl), destroy_named_acl);
164         if (!named_acl) {
165                 return NULL;
166         }
167
168         ast_copy_string(named_acl->name, cat, sizeof(named_acl->name));
169
170         return named_acl;
171 }
172
173 /*!
174  * \brief Find a named ACL in a container by its name
175  *
176  * \param container ao2container holding the named ACLs
177  * \param cat name of the ACL wanted to be found
178  * \retval pointer to the named ACL if available. Null if not found.
179  */
180 static void *named_acl_find(struct ao2_container *container, const char *cat)
181 {
182         struct named_acl tmp;
183         ast_copy_string(tmp.name, cat, sizeof(tmp.name));
184         return ao2_find(container, &tmp, OBJ_POINTER);
185 }
186
187 /*!
188  * \internal
189  * \brief Callback function to compare the ACL order of two given categories.
190  *        This function is used to sort lists of ACLs received from realtime.
191  *
192  * \param p first category being compared
193  * \param q second category being compared
194  *
195  * \retval -1 (p < q)
196  * \retval 0 (p == q)
197  * \retval 1 (p > q)
198  */
199 static int acl_order_comparator(struct ast_category *p, struct ast_category *q)
200 {
201         int p_value = 0, q_value = 0;
202         struct ast_variable *p_var = ast_category_first(p);
203         struct ast_variable *q_var = ast_category_first(q);
204
205         while (p_var) {
206                 if (!strcasecmp(p_var->name, "rule_order")) {
207                         p_value = atoi(p_var->value);
208                         break;
209                 }
210                 p_var = p_var->next;
211         }
212
213         while (q_var) {
214                 if (!strcasecmp(q_var->name, "rule_order")) {
215                         q_value = atoi(q_var->value);
216                         break;
217                 }
218                 q_var = q_var->next;
219         }
220
221         if (p_value < q_value) {
222                 return -1;
223         } else if (q_value < p_value) {
224                 return 1;
225         }
226
227         return 0;
228 }
229
230 /*!
231  * \internal
232  * \brief Search for a named ACL via realtime Database and build the named_acl
233  *        if it is valid.
234  *
235  * \param name of the ACL wanted to be found
236  * \retval pointer to the named ACL if available. Null if the ACL subsystem is unconfigured.
237  */
238 static struct named_acl *named_acl_find_realtime(const char *name)
239 {
240         struct ast_config *cfg;
241         char *item = NULL;
242         const char *systemname = NULL;
243         struct ast_ha *built_ha = NULL;
244         struct named_acl *acl;
245
246         /* If we have a systemname set in the global options, we only want to retrieve entries with a matching systemname field. */
247         systemname = ast_config_AST_SYSTEM_NAME;
248
249         if (ast_strlen_zero(systemname)) {
250                 cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, SENTINEL);
251         } else {
252                 cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, "systemname", systemname, SENTINEL);
253         }
254
255         if (!cfg) {
256                 return NULL;
257         }
258
259         /* At this point, the configuration must be sorted by the order field. */
260         ast_config_sort_categories(cfg, 0, acl_order_comparator);
261
262         while ((item = ast_category_browse(cfg, item))) {
263                 int append_ha_error = 0;
264                 const char *order = ast_variable_retrieve(cfg, item, "rule_order");
265                 const char *sense = ast_variable_retrieve(cfg, item, "sense");
266                 const char *rule = ast_variable_retrieve(cfg, item, "rule");
267
268                 built_ha = ast_append_ha(sense, rule, built_ha, &append_ha_error);
269                 if (append_ha_error) {
270                         /* We need to completely reject an ACL that contains any bad rules. */
271                         ast_log(LOG_ERROR, "Rejecting realtime ACL due to bad ACL definition '%s': %s - %s - %s\n", name, order, sense, rule);
272                         ast_free_ha(built_ha);
273                         return NULL;
274                 }
275         }
276
277         ast_config_destroy(cfg);
278
279         acl = named_acl_alloc(name);
280         if (!acl) {
281                 ast_log(LOG_ERROR, "allocation error\n");
282                 ast_free_ha(built_ha);
283                 return NULL;
284         }
285
286         acl->ha = built_ha;
287
288         return acl;
289 }
290
291 struct ast_ha *ast_named_acl_find(const char *name, int *is_realtime, int *is_undefined)
292 {
293         struct ast_ha *ha = NULL;
294
295         RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
296         RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup);
297
298         if (is_realtime) {
299                 *is_realtime = 0;
300         }
301
302         if (is_undefined) {
303                 *is_undefined = 0;
304         }
305
306         /* If the config or its named_acl_list hasn't been initialized, abort immediately. */
307         if ((!cfg) || (!(cfg->named_acl_list))) {
308                 ast_log(LOG_ERROR, "Attempted to find named ACL '%s', but the ACL configuration isn't available.\n", name);
309                 return NULL;
310         }
311
312         named_acl = named_acl_find(cfg->named_acl_list, name);
313
314         /* If a named ACL couldn't be retrieved locally, we need to try realtime storage. */
315         if (!named_acl) {
316                 RAII_VAR(struct named_acl *, realtime_acl, NULL, ao2_cleanup);
317
318                 /* Attempt to create from realtime */
319                 if ((realtime_acl = named_acl_find_realtime(name))) {
320                         if (is_realtime) {
321                                 *is_realtime = 1;
322                         }
323                         ha = ast_duplicate_ha_list(realtime_acl->ha);
324                         return ha;
325                 }
326
327                 /* Couldn't create from realtime. Raise relevant flags and print relevant warnings. */
328                 if (ast_realtime_is_mapping_defined(ACL_FAMILY) && !ast_check_realtime(ACL_FAMILY)) {
329                         ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n"
330                                 "This ACL may exist in the configured realtime backend, but that backend hasn't been registered yet. "
331                                 "Fix this establishing preload for the backend in 'modules.conf'.\n", name);
332                 } else {
333                         ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n", name);
334                 }
335
336                 if (is_undefined) {
337                         *is_undefined = 1;
338                 }
339
340                 return NULL;
341         }
342
343         ha = ast_duplicate_ha_list(named_acl->ha);
344
345         if (!ha) {
346                 ast_log(LOG_NOTICE, "ACL '%s' contains no rules. It is valid, but it will accept addresses unconditionally.\n", name);
347         }
348
349         return ha;
350 }
351
352 /*! \brief Message type for named ACL changes */
353 STASIS_MESSAGE_TYPE_DEFN(ast_named_acl_change_type);
354
355 /*!
356  * \internal
357  * \brief Sends a stasis message corresponding to a given named ACL that has changed or
358  *        that all ACLs have been updated and old copies must be refreshed. Consumers of
359  *        named ACLs should subscribe to the ast_security_topic and respond to messages
360  *        of the ast_named_acl_change_type stasis message type in order to be able to
361  *        accommodate changes to named ACLs.
362  *
363  * \param name Name of the ACL that has changed. May be an empty string (but not NULL)
364  *        If name is an empty string, then all ACLs must be refreshed.
365  *
366  * \retval 0 success
367  * \retval 1 failure
368  */
369 static int publish_acl_change(const char *name)
370 {
371         RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
372         RAII_VAR(struct ast_json_payload *, json_payload, NULL, ao2_cleanup);
373         RAII_VAR(struct ast_json *, json_object, ast_json_object_create(), ast_json_unref);
374
375         if (!json_object || !ast_named_acl_change_type()) {
376                 goto publish_failure;
377         }
378
379         if (ast_json_object_set(json_object, "name", ast_json_string_create(name))) {
380                 goto publish_failure;
381         }
382
383         if (!(json_payload = ast_json_payload_create(json_object))) {
384                 goto publish_failure;
385         }
386
387         msg = stasis_message_create(ast_named_acl_change_type(), json_payload);
388
389         if (!msg) {
390                 goto publish_failure;
391         }
392
393         stasis_publish(ast_security_topic(), msg);
394
395         return 0;
396
397 publish_failure:
398         ast_log(LOG_ERROR, "Failed to issue ACL change message for %s.\n",
399                 ast_strlen_zero(name) ? "all named ACLs" : name);
400         return -1;
401 }
402
403 /*!
404  * \internal
405  * \brief secondary handler for the 'acl show <name>' command (with arg)
406  *
407  * \param fd file descriptor of the cli
408  * \name name of the ACL requested for display
409  */
410 static void cli_display_named_acl(int fd, const char *name)
411 {
412         struct ast_ha *ha;
413         int ha_index = 0;
414         int is_realtime = 0;
415
416         RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
417         RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup);
418
419         /* If the configuration or the configuration's named_acl_list is unavailable, abort. */
420         if ((!cfg) || (!cfg->named_acl_list)) {
421                 ast_log(LOG_ERROR, "Attempted to show named ACL '%s', but the acl configuration isn't available.\n", name);
422                 return;
423         }
424
425         named_acl = named_acl_find(cfg->named_acl_list, name);
426
427         /* If the named_acl couldn't be found with the search, also abort. */
428         if (!named_acl) {
429                 if (!(named_acl = named_acl_find_realtime(name))) {
430                         ast_cli(fd, "\nCould not find ACL named '%s'\n", name);
431                         return;
432                 }
433
434                 is_realtime = 1;
435         }
436
437         ast_cli(fd, "\nACL: %s%s\n---------------------------------------------\n", name, is_realtime ? " (realtime)" : "");
438         for (ha = named_acl->ha; ha; ha = ha->next) {
439                 char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
440                 char *mask = ast_sockaddr_stringify_addr(&ha->netmask);
441                 ast_cli(fd, "%3d: %s - %s/%s\n", ha_index, ha->sense == AST_SENSE_ALLOW ? "allow" : " deny", addr, mask);
442                 ha_index++;
443         }
444 }
445
446 /*!
447  * \internal
448  * \brief secondary handler for the 'acl show' command (no args)
449  *
450  * \param fd file descriptor of the cli
451  */
452 static void cli_display_named_acl_list(int fd)
453 {
454         struct ao2_iterator i;
455         void *o;
456         RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
457
458         ast_cli(fd, "\nacl\n---\n");
459
460         if (!cfg || !cfg->named_acl_list) {
461                 ast_cli(fd, "ACL configuration isn't available.\n");
462                 return;
463         }
464         i = ao2_iterator_init(cfg->named_acl_list, 0);
465
466         while ((o = ao2_iterator_next(&i))) {
467                 struct named_acl *named_acl = o;
468                 ast_cli(fd, "%s\n", named_acl->name);
469                 ao2_ref(o, -1);
470         }
471
472         ao2_iterator_destroy(&i);
473 }
474
475 /* \brief ACL command show <name> */
476 static char *handle_show_named_acl_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
477 {
478         struct named_acl_config *cfg;
479         int length;
480         struct ao2_iterator i;
481         struct named_acl *named_acl;
482
483         switch (cmd) {
484         case CLI_INIT:
485                 e->command = "acl show";
486                 e->usage =
487                         "Usage: acl show [name]\n"
488                         "   Shows a list of named ACLs or lists all entries in a given named ACL.\n";
489                 return NULL;
490         case CLI_GENERATE:
491                 if (a->pos != 2) {
492                         return NULL;
493                 }
494
495                 cfg = ao2_global_obj_ref(globals);
496                 if (!cfg) {
497                         return NULL;
498                 }
499                 length = strlen(a->word);
500                 i = ao2_iterator_init(cfg->named_acl_list, 0);
501                 while ((named_acl = ao2_iterator_next(&i))) {
502                         if (!strncasecmp(a->word, named_acl->name, length)) {
503                                 if (ast_cli_completion_add(ast_strdup(named_acl->name))) {
504                                         ao2_ref(named_acl, -1);
505                                         break;
506                                 }
507                         }
508                         ao2_ref(named_acl, -1);
509                 }
510                 ao2_iterator_destroy(&i);
511                 ao2_ref(cfg, -1);
512
513                 return NULL;
514         }
515
516         if (a->argc == 2) {
517                 cli_display_named_acl_list(a->fd);
518                 return CLI_SUCCESS;
519         }
520
521         if (a->argc == 3) {
522                 cli_display_named_acl(a->fd, a->argv[2]);
523                 return CLI_SUCCESS;
524         }
525
526
527         return CLI_SHOWUSAGE;
528 }
529
530 static struct ast_cli_entry cli_named_acl[] = {
531         AST_CLI_DEFINE(handle_show_named_acl_cmd, "Show a named ACL or list all named ACLs"),
532 };
533
534 static int reload_module(void)
535 {
536         enum aco_process_status status;
537
538         status = aco_process_config(&cfg_info, 1);
539
540         if (status == ACO_PROCESS_ERROR) {
541                 ast_log(LOG_WARNING, "Could not reload ACL config\n");
542                 return 0;
543         }
544
545         if (status == ACO_PROCESS_UNCHANGED) {
546                 /* We don't actually log anything if the config was unchanged,
547                  * but we don't need to send a config change event either.
548                  */
549                 return 0;
550         }
551
552         /* We need to push an ACL change event with no ACL name so that all subscribers update with all ACLs */
553         publish_acl_change("");
554
555         return 0;
556 }
557
558 static int unload_module(void)
559 {
560         ast_cli_unregister_multiple(cli_named_acl, ARRAY_LEN(cli_named_acl));
561
562         STASIS_MESSAGE_TYPE_CLEANUP(ast_named_acl_change_type);
563         aco_info_destroy(&cfg_info);
564         ao2_global_obj_release(globals);
565
566         return 0;
567 }
568
569 static int load_module(void)
570 {
571         if (aco_info_init(&cfg_info)) {
572                 return AST_MODULE_LOAD_FAILURE;
573         }
574
575         STASIS_MESSAGE_TYPE_INIT(ast_named_acl_change_type);
576
577         /* Register the per level options. */
578         aco_option_register(&cfg_info, "permit", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 1, FLDSET(struct named_acl, ha));
579         aco_option_register(&cfg_info, "deny", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 0, FLDSET(struct named_acl, ha));
580
581         aco_process_config(&cfg_info, 0);
582
583         ast_cli_register_multiple(cli_named_acl, ARRAY_LEN(cli_named_acl));
584
585         return AST_MODULE_LOAD_SUCCESS;
586 }
587
588 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Named ACL system",
589         .support_level = AST_MODULE_SUPPORT_CORE,
590         .load = load_module,
591         .unload = unload_module,
592         .reload = reload_module,
593         .load_pri = AST_MODPRI_CORE,
594         .requires = "extconfig",
595 );