diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 507267a1f7..81386c7d7b 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -1714,7 +1714,7 @@ - Must be of type 'system'. + Must be of type 'system' UNLESS the object name is 'system'. @@ -1761,7 +1761,7 @@ twice the unidentified_request_period are pruned. - Must be of type 'global'. + Must be of type 'global' UNLESS the object name is 'global'. Value used in User-Agent header for SIP requests and Server header for SIP responses. diff --git a/res/res_pjsip/config_global.c b/res/res_pjsip/config_global.c index fc1227d252..2a803128d9 100644 --- a/res/res_pjsip/config_global.c +++ b/res/res_pjsip/config_global.c @@ -491,7 +491,7 @@ int ast_sip_initialize_sorcery_global(void) snprintf(default_useragent, sizeof(default_useragent), "%s %s", DEFAULT_USERAGENT_PREFIX, ast_get_version()); - ast_sorcery_apply_default(sorcery, "global", "config", "pjsip.conf,criteria=type=global"); + ast_sorcery_apply_default(sorcery, "global", "config", "pjsip.conf,criteria=type=global,single_object=yes,explicit_name=global"); if (ast_sorcery_object_register(sorcery, "global", global_alloc, NULL, global_apply)) { return -1; diff --git a/res/res_pjsip/config_system.c b/res/res_pjsip/config_system.c index 65e4e2c05e..2ff0a65efe 100644 --- a/res/res_pjsip/config_system.c +++ b/res/res_pjsip/config_system.c @@ -175,7 +175,7 @@ int ast_sip_initialize_system(void) return -1; } - ast_sorcery_apply_default(system_sorcery, "system", "config", "pjsip.conf,criteria=type=system"); + ast_sorcery_apply_default(system_sorcery, "system", "config", "pjsip.conf,criteria=type=system,single_object=yes,explicit_name=system"); if (ast_sorcery_object_register_no_reload(system_sorcery, "system", system_alloc, NULL, system_apply)) { ast_log(LOG_ERROR, "Failed to register with sorcery (is res_sorcery_config loaded?)\n"); diff --git a/res/res_sorcery_config.c b/res/res_sorcery_config.c index 20178883b0..8e5ee205a8 100644 --- a/res/res_sorcery_config.c +++ b/res/res_sorcery_config.c @@ -50,12 +50,18 @@ struct sorcery_config { /*! \brief Any specific variable criteria for considering a defined category for this object */ struct ast_variable *criteria; + /*! \brief An explicit name for the configuration section, with it there can be only one */ + char *explicit_name; + /*! \brief Number of buckets to use for objects */ unsigned int buckets; /*! \brief Enable file level integrity instead of object level */ unsigned int file_integrity:1; + /*! \brief Enable enforcement of a single configuration object of this type */ + unsigned int single_object:1; + /*! \brief Filename of the configuration file */ char filename[]; }; @@ -113,6 +119,7 @@ static void sorcery_config_destructor(void *obj) ao2_global_obj_release(config->objects); ast_rwlock_destroy(&config->objects.lock); ast_variables_destroy(config->criteria); + ast_free(config->explicit_name); } static int sorcery_config_fields_cmp(void *obj, void *arg, int flags) @@ -237,12 +244,66 @@ static void sorcery_config_retrieve_prefix(const struct ast_sorcery *sorcery, vo ao2_callback(config_objects, OBJ_NODATA | OBJ_MULTIPLE, sorcery_config_fields_cmp, ¶ms); } -/*! \brief Internal function which determines if criteria has been met for considering an object set applicable */ -static int sorcery_is_criteria_met(struct ast_variable *objset, struct ast_variable *criteria) +/*! \brief Internal function which determines if a category matches based on explicit name */ +static int sorcery_is_explicit_name_met(const struct ast_sorcery *sorcery, const char *type, + struct ast_category *category, struct sorcery_config *config) +{ + struct ast_sorcery_object_type *object_type; + struct ast_variable *field; + int met = 1; + + if (ast_strlen_zero(config->explicit_name) || strcmp(ast_category_get_name(category), config->explicit_name)) { + return 0; + } + + object_type = ast_sorcery_get_object_type(sorcery, type); + if (!object_type) { + return 0; + } + + /* We iterate the configured fields to see if we don't know any, if we don't then + * this is likely not for the given type and we skip it. If it actually is then criteria + * may pick it up in which case it would just get rejected as an invalid configuration later. + */ + for (field = ast_category_first(category); field; field = field->next) { + if (!ast_sorcery_is_object_field_registered(object_type, field->name)) { + met = 0; + break; + } + } + + ao2_ref(object_type, -1); + + return met; +} + +/*! \brief Internal function which determines if a category matches based on criteria */ +static int sorcery_is_criteria_met(struct ast_category *category, struct sorcery_config *config) { RAII_VAR(struct ast_variable *, diff, NULL, ast_variables_destroy); - return (!criteria || (!ast_sorcery_changeset_create(objset, criteria, &diff) && !diff)) ? 1 : 0; + if (!config->criteria) { + return 0; + } + + return (!ast_sorcery_changeset_create(ast_category_first(category), config->criteria, &diff) && !diff) ? 1 : 0; +} + +/*! \brief Internal function which determines if criteria has been met for considering an object set applicable */ +static int sorcery_is_configuration_met(const struct ast_sorcery *sorcery, const char *type, + struct ast_category *category, struct sorcery_config *config) +{ + if (!config->criteria && ast_strlen_zero(config->explicit_name)) { + /* Nothing is configured to allow specific matching, so accept it! */ + return 1; + } else if (sorcery_is_explicit_name_met(sorcery, type, category, config)) { + return 1; + } else if (sorcery_is_criteria_met(category, config)) { + return 1; + } else { + /* Nothing explicitly matched so reject */ + return 0; + } } static void sorcery_config_internal_load(void *data, const struct ast_sorcery *sorcery, const char *type, unsigned int reload) @@ -269,8 +330,8 @@ static void sorcery_config_internal_load(void *data, const struct ast_sorcery *s if (!config->buckets) { while ((category = ast_category_browse_filtered(cfg, NULL, category, NULL))) { - /* If given criteria has not been met skip the category, it is not applicable */ - if (!sorcery_is_criteria_met(ast_category_first(category), config->criteria)) { + /* If given configuration has not been met skip the category, it is not applicable */ + if (!sorcery_is_configuration_met(sorcery, type, category, config)) { continue; } @@ -292,6 +353,16 @@ static void sorcery_config_internal_load(void *data, const struct ast_sorcery *s buckets = config->buckets; } + /* For single object configurations there can only ever be one bucket, if there's more than the single + * object requirement has been violated. + */ + if (config->single_object && buckets > 1) { + ast_log(LOG_ERROR, "Config file '%s' could not be loaded; configuration contains more than one object of type '%s'\n", + config->filename, type); + ast_config_destroy(cfg); + return; + } + ast_debug(2, "Using bucket size of '%d' for objects of type '%s' from '%s'\n", buckets, type, config->filename); @@ -308,8 +379,8 @@ static void sorcery_config_internal_load(void *data, const struct ast_sorcery *s RAII_VAR(void *, obj, NULL, ao2_cleanup); id = ast_category_get_name(category); - /* If given criteria has not been met skip the category, it is not applicable */ - if (!sorcery_is_criteria_met(ast_category_first(category), config->criteria)) { + /* If given configurationhas not been met skip the category, it is not applicable */ + if (!sorcery_is_configuration_met(sorcery, type, category, config)) { continue; } @@ -418,6 +489,24 @@ static void *sorcery_config_open(const char *data) ao2_ref(config, -1); return NULL; } + } else if (!strcasecmp(name, "explicit_name")) { + ast_free(config->explicit_name); + config->explicit_name = ast_strdup(value); + if (ast_strlen_zero(config->explicit_name)) { + /* This is fatal since it could stop a configuration section from getting applied */ + ast_log(LOG_ERROR, "Could not create explicit name entry of '%s' for configuration file '%s'\n", + value, filename); + ao2_ref(config, -1); + return NULL; + } + } else if (!strcasecmp(name, "single_object")) { + if (ast_strlen_zero(value)) { + ast_log(LOG_ERROR, "Could not set single object value for configuration file '%s' as the value is empty\n", + filename); + ao2_ref(config, -1); + return NULL; + } + config->single_object = ast_true(value); } else { ast_log(LOG_ERROR, "Unsupported option '%s' used for configuration file '%s'\n", name, filename); }