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