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