CI: Various updates to buildAsterisk.sh
[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         cfg->named_acl_list = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 37,
135                 named_acl_hash_fn, NULL, named_acl_cmp_fn);
136         if (!cfg->named_acl_list) {
137                 goto error;
138         }
139
140         return cfg;
141
142 error:
143         ao2_ref(cfg, -1);
144         return NULL;
145 }
146
147 /*! \brief Destroy a named ACL object */
148 static void destroy_named_acl(void *obj)
149 {
150         struct named_acl *named_acl = obj;
151         ast_free_ha(named_acl->ha);
152 }
153
154 /*!
155  * \brief Create a named ACL structure
156  *
157  * \param cat name given to the ACL
158  * \retval NULL failure
159  *\retval non-NULL successfully allocated named ACL
160  */
161 static void *named_acl_alloc(const char *cat)
162 {
163         struct named_acl *named_acl;
164
165         named_acl = ao2_alloc(sizeof(*named_acl), destroy_named_acl);
166         if (!named_acl) {
167                 return NULL;
168         }
169
170         ast_copy_string(named_acl->name, cat, sizeof(named_acl->name));
171
172         return named_acl;
173 }
174
175 /*!
176  * \brief Find a named ACL in a container by its name
177  *
178  * \param container ao2container holding the named ACLs
179  * \param cat name of the ACL wanted to be found
180  * \retval pointer to the named ACL if available. Null if not found.
181  */
182 static void *named_acl_find(struct ao2_container *container, const char *cat)
183 {
184         struct named_acl tmp;
185         ast_copy_string(tmp.name, cat, sizeof(tmp.name));
186         return ao2_find(container, &tmp, OBJ_POINTER);
187 }
188
189 /*!
190  * \internal
191  * \brief Callback function to compare the ACL order of two given categories.
192  *        This function is used to sort lists of ACLs received from realtime.
193  *
194  * \param p first category being compared
195  * \param q second category being compared
196  *
197  * \retval -1 (p < q)
198  * \retval 0 (p == q)
199  * \retval 1 (p > q)
200  */
201 static int acl_order_comparator(struct ast_category *p, struct ast_category *q)
202 {
203         int p_value = 0, q_value = 0;
204         struct ast_variable *p_var = ast_category_first(p);
205         struct ast_variable *q_var = ast_category_first(q);
206
207         while (p_var) {
208                 if (!strcasecmp(p_var->name, "rule_order")) {
209                         p_value = atoi(p_var->value);
210                         break;
211                 }
212                 p_var = p_var->next;
213         }
214
215         while (q_var) {
216                 if (!strcasecmp(q_var->name, "rule_order")) {
217                         q_value = atoi(q_var->value);
218                         break;
219                 }
220                 q_var = q_var->next;
221         }
222
223         if (p_value < q_value) {
224                 return -1;
225         } else if (q_value < p_value) {
226                 return 1;
227         }
228
229         return 0;
230 }
231
232 /*!
233  * \internal
234  * \brief Search for a named ACL via realtime Database and build the named_acl
235  *        if it is valid.
236  *
237  * \param name of the ACL wanted to be found
238  * \retval pointer to the named ACL if available. Null if the ACL subsystem is unconfigured.
239  */
240 static struct named_acl *named_acl_find_realtime(const char *name)
241 {
242         struct ast_config *cfg;
243         char *item = NULL;
244         const char *systemname = NULL;
245         struct ast_ha *built_ha = NULL;
246         struct named_acl *acl;
247
248         /* If we have a systemname set in the global options, we only want to retrieve entries with a matching systemname field. */
249         systemname = ast_config_AST_SYSTEM_NAME;
250
251         if (ast_strlen_zero(systemname)) {
252                 cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, SENTINEL);
253         } else {
254                 cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, "systemname", systemname, SENTINEL);
255         }
256
257         if (!cfg) {
258                 return NULL;
259         }
260
261         /* At this point, the configuration must be sorted by the order field. */
262         ast_config_sort_categories(cfg, 0, acl_order_comparator);
263
264         while ((item = ast_category_browse(cfg, item))) {
265                 int append_ha_error = 0;
266                 const char *order = ast_variable_retrieve(cfg, item, "rule_order");
267                 const char *sense = ast_variable_retrieve(cfg, item, "sense");
268                 const char *rule = ast_variable_retrieve(cfg, item, "rule");
269
270                 built_ha = ast_append_ha(sense, rule, built_ha, &append_ha_error);
271                 if (append_ha_error) {
272                         /* We need to completely reject an ACL that contains any bad rules. */
273                         ast_log(LOG_ERROR, "Rejecting realtime ACL due to bad ACL definition '%s': %s - %s - %s\n", name, order, sense, rule);
274                         ast_free_ha(built_ha);
275                         return NULL;
276                 }
277         }
278
279         ast_config_destroy(cfg);
280
281         acl = named_acl_alloc(name);
282         if (!acl) {
283                 ast_log(LOG_ERROR, "allocation error\n");
284                 ast_free_ha(built_ha);
285                 return NULL;
286         }
287
288         acl->ha = built_ha;
289
290         return acl;
291 }
292
293 struct ast_ha *ast_named_acl_find(const char *name, int *is_realtime, int *is_undefined)
294 {
295         struct ast_ha *ha = NULL;
296
297         RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
298         RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup);
299
300         if (is_realtime) {
301                 *is_realtime = 0;
302         }
303
304         if (is_undefined) {
305                 *is_undefined = 0;
306         }
307
308         /* If the config or its named_acl_list hasn't been initialized, abort immediately. */
309         if ((!cfg) || (!(cfg->named_acl_list))) {
310                 ast_log(LOG_ERROR, "Attempted to find named ACL '%s', but the ACL configuration isn't available.\n", name);
311                 return NULL;
312         }
313
314         named_acl = named_acl_find(cfg->named_acl_list, name);
315
316         /* If a named ACL couldn't be retrieved locally, we need to try realtime storage. */
317         if (!named_acl) {
318                 RAII_VAR(struct named_acl *, realtime_acl, NULL, ao2_cleanup);
319
320                 /* Attempt to create from realtime */
321                 if ((realtime_acl = named_acl_find_realtime(name))) {
322                         if (is_realtime) {
323                                 *is_realtime = 1;
324                         }
325                         ha = ast_duplicate_ha_list(realtime_acl->ha);
326                         return ha;
327                 }
328
329                 /* Couldn't create from realtime. Raise relevant flags and print relevant warnings. */
330                 if (ast_realtime_is_mapping_defined(ACL_FAMILY) && !ast_check_realtime(ACL_FAMILY)) {
331                         ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n"
332                                 "This ACL may exist in the configured realtime backend, but that backend hasn't been registered yet. "
333                                 "Fix this establishing preload for the backend in 'modules.conf'.\n", name);
334                 } else {
335                         ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n", name);
336                 }
337
338                 if (is_undefined) {
339                         *is_undefined = 1;
340                 }
341
342                 return NULL;
343         }
344
345         ha = ast_duplicate_ha_list(named_acl->ha);
346
347         if (!ha) {
348                 ast_log(LOG_NOTICE, "ACL '%s' contains no rules. It is valid, but it will accept addresses unconditionally.\n", name);
349         }
350
351         return ha;
352 }
353
354 /*! \brief Message type for named ACL changes */
355 STASIS_MESSAGE_TYPE_DEFN(ast_named_acl_change_type);
356
357 /*!
358  * \internal
359  * \brief Sends a stasis message corresponding to a given named ACL that has changed or
360  *        that all ACLs have been updated and old copies must be refreshed. Consumers of
361  *        named ACLs should subscribe to the ast_security_topic and respond to messages
362  *        of the ast_named_acl_change_type stasis message type in order to be able to
363  *        accommodate changes to named ACLs.
364  *
365  * \param name Name of the ACL that has changed. May be an empty string (but not NULL)
366  *        If name is an empty string, then all ACLs must be refreshed.
367  *
368  * \retval 0 success
369  * \retval 1 failure
370  */
371 static int publish_acl_change(const char *name)
372 {
373         RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
374         RAII_VAR(struct ast_json_payload *, json_payload, NULL, ao2_cleanup);
375         RAII_VAR(struct ast_json *, json_object, ast_json_object_create(), ast_json_unref);
376
377         if (!json_object || !ast_named_acl_change_type()) {
378                 goto publish_failure;
379         }
380
381         if (ast_json_object_set(json_object, "name", ast_json_string_create(name))) {
382                 goto publish_failure;
383         }
384
385         if (!(json_payload = ast_json_payload_create(json_object))) {
386                 goto publish_failure;
387         }
388
389         msg = stasis_message_create(ast_named_acl_change_type(), json_payload);
390
391         if (!msg) {
392                 goto publish_failure;
393         }
394
395         stasis_publish(ast_security_topic(), msg);
396
397         return 0;
398
399 publish_failure:
400         ast_log(LOG_ERROR, "Failed to issue ACL change message for %s.\n",
401                 ast_strlen_zero(name) ? "all named ACLs" : name);
402         return -1;
403 }
404
405 /*!
406  * \internal
407  * \brief secondary handler for the 'acl show <name>' command (with arg)
408  *
409  * \param fd file descriptor of the cli
410  * \name name of the ACL requested for display
411  */
412 static void cli_display_named_acl(int fd, const char *name)
413 {
414         struct ast_ha *ha;
415         int ha_index = 0;
416         int is_realtime = 0;
417
418         RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
419         RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup);
420
421         /* If the configuration or the configuration's named_acl_list is unavailable, abort. */
422         if ((!cfg) || (!cfg->named_acl_list)) {
423                 ast_log(LOG_ERROR, "Attempted to show named ACL '%s', but the acl configuration isn't available.\n", name);
424                 return;
425         }
426
427         named_acl = named_acl_find(cfg->named_acl_list, name);
428
429         /* If the named_acl couldn't be found with the search, also abort. */
430         if (!named_acl) {
431                 if (!(named_acl = named_acl_find_realtime(name))) {
432                         ast_cli(fd, "\nCould not find ACL named '%s'\n", name);
433                         return;
434                 }
435
436                 is_realtime = 1;
437         }
438
439         ast_cli(fd, "\nACL: %s%s\n---------------------------------------------\n", name, is_realtime ? " (realtime)" : "");
440         for (ha = named_acl->ha; ha; ha = ha->next) {
441                 char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
442                 char *mask = ast_sockaddr_stringify_addr(&ha->netmask);
443                 ast_cli(fd, "%3d: %s - %s/%s\n", ha_index, ha->sense == AST_SENSE_ALLOW ? "allow" : " deny", addr, mask);
444                 ha_index++;
445         }
446 }
447
448 /*!
449  * \internal
450  * \brief secondary handler for the 'acl show' command (no args)
451  *
452  * \param fd file descriptor of the cli
453  */
454 static void cli_display_named_acl_list(int fd)
455 {
456         struct ao2_iterator i;
457         void *o;
458         RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
459
460         ast_cli(fd, "\nacl\n---\n");
461
462         if (!cfg || !cfg->named_acl_list) {
463                 ast_cli(fd, "ACL configuration isn't available.\n");
464                 return;
465         }
466         i = ao2_iterator_init(cfg->named_acl_list, 0);
467
468         while ((o = ao2_iterator_next(&i))) {
469                 struct named_acl *named_acl = o;
470                 ast_cli(fd, "%s\n", named_acl->name);
471                 ao2_ref(o, -1);
472         }
473
474         ao2_iterator_destroy(&i);
475 }
476
477 /* \brief ACL command show <name> */
478 static char *handle_show_named_acl_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
479 {
480         struct named_acl_config *cfg;
481         int length;
482         struct ao2_iterator i;
483         struct named_acl *named_acl;
484
485         switch (cmd) {
486         case CLI_INIT:
487                 e->command = "acl show";
488                 e->usage =
489                         "Usage: acl show [name]\n"
490                         "   Shows a list of named ACLs or lists all entries in a given named ACL.\n";
491                 return NULL;
492         case CLI_GENERATE:
493                 if (a->pos != 2) {
494                         return NULL;
495                 }
496
497                 cfg = ao2_global_obj_ref(globals);
498                 if (!cfg) {
499                         return NULL;
500                 }
501                 length = strlen(a->word);
502                 i = ao2_iterator_init(cfg->named_acl_list, 0);
503                 while ((named_acl = ao2_iterator_next(&i))) {
504                         if (!strncasecmp(a->word, named_acl->name, length)) {
505                                 if (ast_cli_completion_add(ast_strdup(named_acl->name))) {
506                                         ao2_ref(named_acl, -1);
507                                         break;
508                                 }
509                         }
510                         ao2_ref(named_acl, -1);
511                 }
512                 ao2_iterator_destroy(&i);
513                 ao2_ref(cfg, -1);
514
515                 return NULL;
516         }
517
518         if (a->argc == 2) {
519                 cli_display_named_acl_list(a->fd);
520                 return CLI_SUCCESS;
521         }
522
523         if (a->argc == 3) {
524                 cli_display_named_acl(a->fd, a->argv[2]);
525                 return CLI_SUCCESS;
526         }
527
528
529         return CLI_SHOWUSAGE;
530 }
531
532 static struct ast_cli_entry cli_named_acl[] = {
533         AST_CLI_DEFINE(handle_show_named_acl_cmd, "Show a named ACL or list all named ACLs"),
534 };
535
536 static int reload_module(void)
537 {
538         enum aco_process_status status;
539
540         status = aco_process_config(&cfg_info, 1);
541
542         if (status == ACO_PROCESS_ERROR) {
543                 ast_log(LOG_WARNING, "Could not reload ACL config\n");
544                 return 0;
545         }
546
547         if (status == ACO_PROCESS_UNCHANGED) {
548                 /* We don't actually log anything if the config was unchanged,
549                  * but we don't need to send a config change event either.
550                  */
551                 return 0;
552         }
553
554         /* We need to push an ACL change event with no ACL name so that all subscribers update with all ACLs */
555         publish_acl_change("");
556
557         return 0;
558 }
559
560 static int unload_module(void)
561 {
562         ast_cli_unregister_multiple(cli_named_acl, ARRAY_LEN(cli_named_acl));
563
564         STASIS_MESSAGE_TYPE_CLEANUP(ast_named_acl_change_type);
565         aco_info_destroy(&cfg_info);
566         ao2_global_obj_release(globals);
567
568         return 0;
569 }
570
571 static int load_module(void)
572 {
573         if (aco_info_init(&cfg_info)) {
574                 return AST_MODULE_LOAD_FAILURE;
575         }
576
577         STASIS_MESSAGE_TYPE_INIT(ast_named_acl_change_type);
578
579         /* Register the per level options. */
580         aco_option_register(&cfg_info, "permit", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 1, FLDSET(struct named_acl, ha));
581         aco_option_register(&cfg_info, "deny", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 0, FLDSET(struct named_acl, ha));
582
583         aco_process_config(&cfg_info, 0);
584
585         ast_cli_register_multiple(cli_named_acl, ARRAY_LEN(cli_named_acl));
586
587         return AST_MODULE_LOAD_SUCCESS;
588 }
589
590 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Named ACL system",
591         .support_level = AST_MODULE_SUPPORT_CORE,
592         .load = load_module,
593         .unload = unload_module,
594         .reload = reload_module,
595         .load_pri = AST_MODPRI_CORE,
596         .requires = "extconfig",
597 );