Sat Apr 26 2014 22:01:38

Asterisk developer's documentation


named_acl.c
Go to the documentation of this file.
00001 /*
00002  * Asterisk -- A telephony toolkit for Linux.
00003  *
00004  * Copyright (C) 2012, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  * Jonathan Rose <jrose@digium.com>
00008  *
00009  * See http://www.asterisk.org for more information about
00010  * the Asterisk project. Please do not directly contact
00011  * any of the maintainers of this project for assistance;
00012  * the project provides a web site, mailing lists and IRC
00013  * channels for your use.
00014  *
00015  * This program is free software, distributed under the terms of
00016  * the GNU General Public License Version 2. See the LICENSE file
00017  * at the top of the source tree.
00018  */
00019 
00020 /*! \file
00021  *
00022  * \brief Named Access Control Lists
00023  *
00024  * \author Jonathan Rose <jrose@digium.com>
00025  *
00026  * \note Based on a feature proposed by
00027  * Olle E. Johansson <oej@edvina.net>
00028  */
00029 
00030 #include "asterisk.h"
00031 
00032 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 398103 $")
00033 
00034 #include "asterisk/config.h"
00035 #include "asterisk/config_options.h"
00036 #include "asterisk/event.h"
00037 #include "asterisk/utils.h"
00038 #include "asterisk/module.h"
00039 #include "asterisk/cli.h"
00040 #include "asterisk/acl.h"
00041 #include "asterisk/astobj2.h"
00042 #include "asterisk/paths.h"
00043 
00044 #define NACL_CONFIG "acl.conf"
00045 #define ACL_FAMILY "acls"
00046 
00047 struct named_acl_global_config {
00048    AST_DECLARE_STRING_FIELDS(
00049       /* Nothing here yet. */
00050    );
00051 };
00052 
00053 /*
00054  * Configuration structure - holds pointers to ao2 containers used for configuration
00055  * Since there isn't a general level or any other special levels for acl.conf at this
00056  * time, it's really a config options friendly wrapper for the named ACL container
00057  */
00058 struct named_acl_config {
00059    struct named_acl_global_config *global;
00060    struct ao2_container *named_acl_list;
00061 };
00062 
00063 static AO2_GLOBAL_OBJ_STATIC(globals);
00064 
00065 /*! \note These functions are used for placing/retrieving named ACLs in their ao2_container. */
00066 static void *named_acl_config_alloc(void);
00067 static void *named_acl_alloc(const char *cat);
00068 static void *named_acl_find(struct ao2_container *container, const char *cat);
00069 
00070 /* Config type for named ACL profiles (must not be named general) */
00071 static struct aco_type named_acl_type = {
00072    .type = ACO_ITEM,                  /*!< named_acls are items stored in containers, not individual global objects */
00073    .category_match = ACO_BLACKLIST,
00074    .category = "^general$",           /*!< Match everything but "general" */
00075    .item_alloc = named_acl_alloc,     /*!< A callback to allocate a new named_acl based on category */
00076    .item_find = named_acl_find,       /*!< A callback to find a named_acl in some container of named_acls */
00077    .item_offset = offsetof(struct named_acl_config, named_acl_list), /*!< Could leave this out since 0 */
00078 };
00079 
00080 /* Config type for the general part of the ACL profile (must be named general) */
00081 static struct aco_type global_option = {
00082    .type = ACO_GLOBAL,
00083    .item_offset = offsetof(struct named_acl_config, global),
00084    .category_match = ACO_WHITELIST,
00085    .category = "^general$",
00086 };
00087 
00088 /* This array of aco_type structs is necessary to use aco_option_register */
00089 struct aco_type *named_acl_types[] = ACO_TYPES(&named_acl_type);
00090 
00091 struct aco_type *global_options[] = ACO_TYPES(&global_option);
00092 
00093 struct aco_file named_acl_conf = {
00094    .filename = "acl.conf",
00095    .types = ACO_TYPES(&named_acl_type, &global_option),
00096 };
00097 
00098 /* Create a config info struct that describes the config processing for named ACLs. */
00099 CONFIG_INFO_STANDARD(cfg_info, globals, named_acl_config_alloc,
00100    .files = ACO_FILES(&named_acl_conf),
00101 );
00102 
00103 struct named_acl {
00104    struct ast_ha *ha;
00105    char name[ACL_NAME_LENGTH]; /* Same max length as a configuration category */
00106 };
00107 
00108 static int named_acl_hash_fn(const void *obj, const int flags)
00109 {
00110    const struct named_acl *entry = obj;
00111    return ast_str_hash(entry->name);
00112 }
00113 
00114 static int named_acl_cmp_fn(void *obj, void *arg, const int flags)
00115 {
00116    struct named_acl *entry1 = obj;
00117    struct named_acl *entry2 = arg;
00118 
00119    return (!strcmp(entry1->name, entry2->name)) ? (CMP_MATCH | CMP_STOP) : 0;
00120 }
00121 
00122 /*! \brief destructor for named_acl_config */
00123 static void named_acl_config_destructor(void *obj)
00124 {
00125    struct named_acl_config *cfg = obj;
00126    ao2_cleanup(cfg->named_acl_list);
00127    ao2_cleanup(cfg->global);
00128 }
00129 
00130 static void named_acl_global_config_destructor(void *obj)
00131 {
00132    struct named_acl_global_config *global = obj;
00133    ast_string_field_free_memory(global);
00134 }
00135 
00136 /*! \brief allocator callback for named_acl_config. Notice it returns void * since it is used by
00137  * the backend config code
00138  */
00139 static void *named_acl_config_alloc(void)
00140 {
00141    struct named_acl_config *cfg;
00142 
00143    if (!(cfg = ao2_alloc(sizeof(*cfg), named_acl_config_destructor))) {
00144       return NULL;
00145    }
00146 
00147    if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), named_acl_global_config_destructor))) {
00148       goto error;
00149    }
00150 
00151    if (ast_string_field_init(cfg->global, 128)) {
00152       goto error;
00153    }
00154 
00155    if (!(cfg->named_acl_list = ao2_container_alloc(37, named_acl_hash_fn, named_acl_cmp_fn))) {
00156       goto error;
00157    }
00158 
00159    return cfg;
00160 
00161 error:
00162    ao2_ref(cfg, -1);
00163    return NULL;
00164 }
00165 
00166 /*! \brief Destroy a named ACL object */
00167 static void destroy_named_acl(void *obj)
00168 {
00169    struct named_acl *named_acl = obj;
00170    ast_free_ha(named_acl->ha);
00171 }
00172 
00173 /*!
00174  * \brief Create a named ACL structure
00175  *
00176  * \param cat name given to the ACL
00177  * \retval NULL failure
00178  *\retval non-NULL successfully allocated named ACL
00179  */
00180 void *named_acl_alloc(const char *cat)
00181 {
00182    struct named_acl *named_acl;
00183 
00184    named_acl = ao2_alloc(sizeof(*named_acl), destroy_named_acl);
00185    if (!named_acl) {
00186       return NULL;
00187    }
00188 
00189    ast_copy_string(named_acl->name, cat, sizeof(named_acl->name));
00190 
00191    return named_acl;
00192 }
00193 
00194 /*!
00195  * \brief Find a named ACL in a container by its name
00196  *
00197  * \param container ao2container holding the named ACLs
00198  * \param name of the ACL wanted to be found
00199  * \retval pointer to the named ACL if available. Null if not found.
00200  */
00201 void *named_acl_find(struct ao2_container *container, const char *cat)
00202 {
00203    struct named_acl tmp;
00204    ast_copy_string(tmp.name, cat, sizeof(tmp.name));
00205    return ao2_find(container, &tmp, OBJ_POINTER);
00206 }
00207 
00208 /*!
00209  * \internal
00210  * \brief Callback function to compare the ACL order of two given categories.
00211  *        This function is used to sort lists of ACLs received from realtime.
00212  *
00213  * \param p first category being compared
00214  * \param q second category being compared
00215  *
00216  * \retval -1 (p < q)
00217  * \retval 0 (p == q)
00218  * \retval 1 (p > q)
00219  */
00220 static int acl_order_comparator(struct ast_category *p, struct ast_category *q)
00221 {
00222    int p_value = 0, q_value = 0;
00223    struct ast_variable *p_var = ast_category_first(p);
00224    struct ast_variable *q_var = ast_category_first(q);
00225 
00226    while (p_var) {
00227       if (!strcasecmp(p_var->name, "rule_order")) {
00228          p_value = atoi(p_var->value);
00229          break;
00230       }
00231       p_var = p_var->next;
00232    }
00233 
00234    while (q_var) {
00235       if (!strcasecmp(q_var->name, "rule_order")) {
00236          q_value = atoi(q_var->value);
00237          break;
00238       }
00239       q_var = q_var->next;
00240    }
00241 
00242    if (p_value < q_value) {
00243       return -1;
00244    } else if (q_value < p_value) {
00245       return 1;
00246    }
00247 
00248    return 0;
00249 }
00250 
00251 /*!
00252  * \internal
00253  * \brief Search for a named ACL via realtime Database and build the named_acl
00254  *        if it is valid.
00255  *
00256  * \param name of the ACL wanted to be found
00257  * \retval pointer to the named ACL if available. Null if the ACL subsystem is unconfigured.
00258  */
00259 static struct named_acl *named_acl_find_realtime(const char *name)
00260 {
00261    struct ast_config *cfg;
00262    char *item = NULL;
00263    const char *systemname = NULL;
00264    struct ast_ha *built_ha = NULL;
00265    struct named_acl *acl;
00266 
00267    /* If we have a systemname set in the global options, we only want to retrieve entries with a matching systemname field. */
00268    systemname = ast_config_AST_SYSTEM_NAME;
00269 
00270    if (ast_strlen_zero(systemname)) {
00271       cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, SENTINEL);
00272    } else {
00273       cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, "systemname", systemname, SENTINEL);
00274    }
00275 
00276    if (!cfg) {
00277       return NULL;
00278    }
00279 
00280    /* At this point, the configuration must be sorted by the order field. */
00281    ast_config_sort_categories(cfg, 0, acl_order_comparator);
00282 
00283    while ((item = ast_category_browse(cfg, item))) {
00284       int append_ha_error = 0;
00285       const char *order = ast_variable_retrieve(cfg, item, "rule_order");
00286       const char *sense = ast_variable_retrieve(cfg, item, "sense");
00287       const char *rule = ast_variable_retrieve(cfg, item, "rule");
00288 
00289       built_ha = ast_append_ha(sense, rule, built_ha, &append_ha_error);
00290       if (append_ha_error) {
00291          /* We need to completely reject an ACL that contains any bad rules. */
00292          ast_log(LOG_ERROR, "Rejecting realtime ACL due to bad ACL definition '%s': %s - %s - %s\n", name, order, sense, rule);
00293          ast_free_ha(built_ha);
00294          return NULL;
00295       }
00296    }
00297 
00298    ast_config_destroy(cfg);
00299 
00300    acl = named_acl_alloc(name);
00301    if (!acl) {
00302       ast_log(LOG_ERROR, "allocation error\n");
00303       ast_free_ha(built_ha);
00304       return NULL;
00305    }
00306 
00307    acl->ha = built_ha;
00308 
00309    return acl;
00310 }
00311 
00312 struct ast_ha *ast_named_acl_find(const char *name, int *is_realtime, int *is_undefined) {
00313    struct ast_ha *ha = NULL;
00314 
00315    RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
00316    RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup);
00317 
00318    if (is_realtime) {
00319       *is_realtime = 0;
00320    }
00321 
00322    if (is_undefined) {
00323       *is_undefined = 0;
00324    }
00325 
00326    /* If the config or its named_acl_list hasn't been initialized, abort immediately. */
00327    if ((!cfg) || (!(cfg->named_acl_list))) {
00328       ast_log(LOG_ERROR, "Attempted to find named ACL '%s', but the ACL configuration isn't available.\n", name);
00329       return NULL;
00330    }
00331 
00332    named_acl = named_acl_find(cfg->named_acl_list, name);
00333 
00334    /* If a named ACL couldn't be retrieved locally, we need to try realtime storage. */
00335    if (!named_acl) {
00336       RAII_VAR(struct named_acl *, realtime_acl, NULL, ao2_cleanup);
00337 
00338       /* Attempt to create from realtime */
00339       if ((realtime_acl = named_acl_find_realtime(name))) {
00340          if (is_realtime) {
00341             *is_realtime = 1;
00342          }
00343          ha = ast_duplicate_ha_list(realtime_acl->ha);
00344          return ha;
00345       }
00346 
00347       /* Couldn't create from realtime. Raise relevant flags and print relevant warnings. */
00348       if (ast_realtime_is_mapping_defined(ACL_FAMILY) && !ast_check_realtime(ACL_FAMILY)) {
00349          ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n"
00350             "This ACL may exist in the configured realtime backend, but that backend hasn't been registered yet. "
00351             "Fix this establishing preload for the backend in 'modules.conf'.\n", name);
00352       } else {
00353          ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n", name);
00354       }
00355 
00356       if (is_undefined) {
00357          *is_undefined = 1;
00358       }
00359 
00360       return NULL;
00361    }
00362 
00363    ha = ast_duplicate_ha_list(named_acl->ha);
00364 
00365    if (!ha) {
00366       ast_log(LOG_NOTICE, "ACL '%s' contains no rules. It is valid, but it will accept addresses unconditionally.\n", name);
00367    }
00368 
00369    return ha;
00370 }
00371 
00372 /*!
00373  * \internal
00374  * \brief Sends an update event corresponding to a given named ACL that has changed.
00375  *
00376  * \param name Name of the ACL that has changed. May be an empty string (but not NULL)
00377  *        If name is an empty string, then all ACLs must be refreshed.
00378  *
00379  * \retval 0 success
00380  * \retval 1 failure
00381  */
00382 static int push_acl_change_event(char *name)
00383 {
00384    struct ast_event *event = ast_event_new(AST_EVENT_ACL_CHANGE,
00385                      AST_EVENT_IE_DESCRIPTION, AST_EVENT_IE_PLTYPE_STR, name,
00386                      AST_EVENT_IE_END);
00387    if (!event) {
00388       ast_log(LOG_ERROR, "Failed to allocate acl.conf reload event. Some modules will have out of date ACLs.\n");
00389       return -1;
00390    }
00391 
00392    if (ast_event_queue(event)) {
00393       ast_event_destroy(event);
00394       ast_log(LOG_ERROR, "Failed to queue acl.conf reload event. Some modules will have out of date ACLs.\n");
00395       return -1;
00396    }
00397 
00398    return 0;
00399 }
00400 
00401 /*!
00402  * \internal
00403  * \brief reload configuration for named ACLs
00404  *
00405  * \param fd file descriptor for CLI client
00406  */
00407 int ast_named_acl_reload(void)
00408 {
00409    enum aco_process_status status;
00410 
00411    status = aco_process_config(&cfg_info, 1);
00412 
00413    if (status == ACO_PROCESS_ERROR) {
00414       ast_log(LOG_WARNING, "Could not reload ACL config\n");
00415       return 0;
00416    }
00417 
00418    if (status == ACO_PROCESS_UNCHANGED) {
00419       /* We don't actually log anything if the config was unchanged,
00420        * but we don't need to send a config change event either.
00421        */
00422       return 0;
00423    }
00424 
00425    /* We need to push an ACL change event with no ACL name so that all subscribers update with all ACLs */
00426    push_acl_change_event("");
00427 
00428    return 0;
00429 }
00430 
00431 /*!
00432  * \internal
00433  * \brief secondary handler for the 'acl show <name>' command (with arg)
00434  *
00435  * \param fd file descriptor of the cli
00436  * \name name of the ACL requested for display
00437  */
00438 static void cli_display_named_acl(int fd, const char *name)
00439 {
00440    struct ast_ha *ha;
00441    int ha_index = 0;
00442    int is_realtime = 0;
00443 
00444    RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
00445    RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup);
00446 
00447    /* If the configuration or the configuration's named_acl_list is unavailable, abort. */
00448    if ((!cfg) || (!cfg->named_acl_list)) {
00449       ast_log(LOG_ERROR, "Attempted to show named ACL '%s', but the acl configuration isn't available.\n", name);
00450       return;
00451    }
00452 
00453    named_acl = named_acl_find(cfg->named_acl_list, name);
00454 
00455    /* If the named_acl couldn't be found with the search, also abort. */
00456    if (!named_acl) {
00457       if (!(named_acl = named_acl_find_realtime(name))) {
00458          ast_cli(fd, "\nCould not find ACL named '%s'\n", name);
00459          return;
00460       }
00461 
00462       is_realtime = 1;
00463    }
00464 
00465    ast_cli(fd, "\nACL: %s%s\n---------------------------------------------\n", name, is_realtime ? " (realtime)" : "");
00466    for (ha = named_acl->ha; ha; ha = ha->next) {
00467       char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
00468       char *mask = ast_sockaddr_stringify_addr(&ha->netmask);
00469       ast_cli(fd, "%3d: %s - %s/%s\n", ha_index, ha->sense == AST_SENSE_ALLOW ? "allow" : " deny", addr, mask);
00470       ha_index++;
00471    }
00472 }
00473 
00474 /*!
00475  * \internal
00476  * \brief secondary handler for the 'acl show' command (no args)
00477  *
00478  * \param fd file descriptor of the cli
00479  */
00480 static void cli_display_named_acl_list(int fd)
00481 {
00482    struct ao2_iterator i;
00483    void *o;
00484    RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
00485 
00486    ast_cli(fd, "\nacl\n---\n");
00487 
00488    if (!cfg || !cfg->named_acl_list) {
00489       ast_cli(fd, "ACL configuration isn't available.\n");
00490       return;
00491    }
00492    i = ao2_iterator_init(cfg->named_acl_list, 0);
00493 
00494    while ((o = ao2_iterator_next(&i))) {
00495       struct named_acl *named_acl = o;
00496       ast_cli(fd, "%s\n", named_acl->name);
00497       ao2_ref(o, -1);
00498    }
00499 
00500    ao2_iterator_destroy(&i);
00501 }
00502 
00503 /* \brief ACL command show <name> */
00504 static char *handle_show_named_acl_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00505 {
00506    RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
00507    int length;
00508    int which;
00509    struct ao2_iterator i;
00510    struct named_acl *named_acl;
00511    char *match = NULL;
00512 
00513    switch (cmd) {
00514    case CLI_INIT:
00515       e->command = "acl show";
00516       e->usage =
00517          "Usage: acl show [name]\n"
00518          "   Shows a list of named ACLs or lists all entries in a given named ACL.\n";
00519       return NULL;
00520    case CLI_GENERATE:
00521       if (!cfg) {
00522          return NULL;
00523       }
00524       length = strlen(a->word);
00525       which = 0;
00526       i = ao2_iterator_init(cfg->named_acl_list, 0);
00527       while ((named_acl = ao2_iterator_next(&i))) {
00528          if (!strncasecmp(a->word, named_acl->name, length) && ++which > a->n) {
00529             match = ast_strdup(named_acl->name);
00530             ao2_ref(named_acl, -1);
00531             break;
00532          }
00533          ao2_ref(named_acl, -1);
00534       }
00535       ao2_iterator_destroy(&i);
00536       return match;
00537 
00538    }
00539 
00540    if (a->argc == 2) {
00541       cli_display_named_acl_list(a->fd);
00542       return CLI_SUCCESS;
00543    }
00544 
00545    if (a->argc == 3) {
00546       cli_display_named_acl(a->fd, a->argv[2]);
00547       return CLI_SUCCESS;
00548    }
00549 
00550 
00551    return CLI_SHOWUSAGE;
00552 }
00553 
00554 static struct ast_cli_entry cli_named_acl[] = {
00555    AST_CLI_DEFINE(handle_show_named_acl_cmd, "Show a named ACL or list all named ACLs"),
00556 };
00557 
00558 static void named_acl_cleanup(void)
00559 {
00560    ast_cli_unregister_multiple(cli_named_acl, ARRAY_LEN(cli_named_acl));
00561 
00562    aco_info_destroy(&cfg_info);
00563    ao2_global_obj_release(globals);
00564 }
00565 
00566 int ast_named_acl_init()
00567 {
00568    ast_cli_register_multiple(cli_named_acl, ARRAY_LEN(cli_named_acl));
00569 
00570    ast_register_atexit(named_acl_cleanup);
00571 
00572    if (aco_info_init(&cfg_info)) {
00573       return 0;
00574    }
00575 
00576    /* Register the per level options. */
00577    aco_option_register(&cfg_info, "permit", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 1, FLDSET(struct named_acl, ha));
00578    aco_option_register(&cfg_info, "deny", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 0, FLDSET(struct named_acl, ha));
00579 
00580    if (aco_process_config(&cfg_info, 0)) {
00581       aco_info_destroy(&cfg_info);
00582       return 0;
00583    }
00584 
00585    return 0;
00586 }