354 lines
9.6 KiB
C
354 lines
9.6 KiB
C
|
/*
|
||
|
* Asterisk -- An open source telephony toolkit.
|
||
|
*
|
||
|
* Copyright (C) 2023, Sangoma Technologies Corporation
|
||
|
*
|
||
|
* George Joseph <gjoseph@sangoma.com>
|
||
|
*
|
||
|
* See http://www.asterisk.org for more information about
|
||
|
* the Asterisk project. Please do not directly contact
|
||
|
* any of the maintainers of this project for assistance;
|
||
|
* the project provides a web site, mailing lists and IRC
|
||
|
* channels for your use.
|
||
|
*
|
||
|
* This program is free software, distributed under the terms of
|
||
|
* the GNU General Public License Version 2. See the LICENSE file
|
||
|
* at the top of the source tree.
|
||
|
*/
|
||
|
|
||
|
#include "asterisk.h"
|
||
|
#include "asterisk/cli.h"
|
||
|
#include "asterisk/cli.h"
|
||
|
#include "asterisk/logger.h"
|
||
|
#include "asterisk/module.h"
|
||
|
#include "asterisk/utils.h"
|
||
|
#include "asterisk/stasis.h"
|
||
|
#include "asterisk/security_events.h"
|
||
|
|
||
|
#define AST_API_MODULE
|
||
|
#include "stir_shaken.h"
|
||
|
|
||
|
static struct ast_sorcery *sorcery;
|
||
|
struct stasis_subscription *named_acl_changed_sub = NULL;
|
||
|
|
||
|
struct ast_sorcery *get_sorcery(void)
|
||
|
{
|
||
|
return sorcery;
|
||
|
}
|
||
|
|
||
|
#define generate_bool_handler_functions(param_name) \
|
||
|
static const char *param_name ## _map[] = { \
|
||
|
[ param_name ## _NOT_SET ] = "not_set", \
|
||
|
[ param_name ## _YES ] = "yes", \
|
||
|
[ param_name ## _NO ] = "no", \
|
||
|
}; \
|
||
|
enum param_name ## _enum \
|
||
|
param_name ## _from_str(const char *value) \
|
||
|
{ \
|
||
|
if (!strcasecmp(value, param_name ## _map[param_name ## _NOT_SET])) { \
|
||
|
return param_name ## _NOT_SET; \
|
||
|
} else if (ast_true(value)) { \
|
||
|
return param_name ## _YES; \
|
||
|
} else if (ast_false(value)) { \
|
||
|
return param_name ## _NO; \
|
||
|
} \
|
||
|
ast_log(LOG_WARNING, "Unknown " #param_name " response value '%s'\n", value); \
|
||
|
return param_name ## _UNKNOWN; \
|
||
|
}\
|
||
|
const char *param_name ## _to_str(enum param_name ## _enum value) \
|
||
|
{ \
|
||
|
return ARRAY_IN_BOUNDS(value, param_name ## _map) ? \
|
||
|
param_name ## _map[value] : NULL; \
|
||
|
}
|
||
|
|
||
|
generate_bool_handler_functions(use_rfc9410_responses);
|
||
|
generate_bool_handler_functions(send_mky);
|
||
|
generate_bool_handler_functions(check_tn_cert_public_url);
|
||
|
generate_bool_handler_functions(relax_x5u_port_scheme_restrictions);
|
||
|
generate_bool_handler_functions(relax_x5u_path_restrictions);
|
||
|
|
||
|
generate_bool_handler_functions(load_system_certs);
|
||
|
|
||
|
struct enum_name_xref_entry {
|
||
|
int value;
|
||
|
const char *name;
|
||
|
};
|
||
|
|
||
|
#define generate_enum_string_functions(param_name, default_value, ...)\
|
||
|
static struct enum_name_xref_entry param_name ## _map[] = { \
|
||
|
__VA_ARGS__ \
|
||
|
} ; \
|
||
|
enum param_name ## _enum param_name ## _from_str( \
|
||
|
const char *value) \
|
||
|
{ \
|
||
|
int i; \
|
||
|
for (i = 0; i < ARRAY_LEN(param_name ## _map); i++) { \
|
||
|
if (strcasecmp(value, param_name ##_map[i].name) == 0) { \
|
||
|
return param_name ##_map[i].value; \
|
||
|
} \
|
||
|
} \
|
||
|
return param_name ## _ ## default_value; \
|
||
|
} \
|
||
|
const char *param_name ## _to_str( \
|
||
|
enum param_name ## _enum value) \
|
||
|
{ \
|
||
|
int i; \
|
||
|
for (i = 0; i < ARRAY_LEN(param_name ## _map); i++) { \
|
||
|
if (value == param_name ## _map[i].value) return param_name ## _map[i].name; \
|
||
|
} \
|
||
|
return NULL; \
|
||
|
}
|
||
|
|
||
|
generate_enum_string_functions(attest_level, UNKNOWN,
|
||
|
{attest_level_A, "A"},
|
||
|
{attest_level_B, "B"},
|
||
|
{attest_level_C, "C"},
|
||
|
);
|
||
|
|
||
|
generate_enum_string_functions(endpoint_behavior, OFF,
|
||
|
{endpoint_behavior_OFF, "off"},
|
||
|
{endpoint_behavior_OFF, "none"},
|
||
|
{endpoint_behavior_ATTEST, "attest"},
|
||
|
{endpoint_behavior_VERIFY, "verify"},
|
||
|
{endpoint_behavior_ON, "on"},
|
||
|
{endpoint_behavior_ON, "both"}
|
||
|
);
|
||
|
|
||
|
generate_enum_string_functions(stir_shaken_failure_action, CONTINUE,
|
||
|
{stir_shaken_failure_action_CONTINUE, "continue"},
|
||
|
{stir_shaken_failure_action_REJECT_REQUEST, "reject_request"},
|
||
|
{stir_shaken_failure_action_CONTINUE_RETURN_REASON, "continue_return_reason"},
|
||
|
);
|
||
|
|
||
|
static const char *translate_value(const char *val)
|
||
|
{
|
||
|
if (val[0] == '0'
|
||
|
|| val[0] == '\0'
|
||
|
|| strcmp(val, "not_set") == 0) {
|
||
|
return "";
|
||
|
}
|
||
|
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
static void print_acl(int fd, struct ast_acl_list *acl_list, const char *prefix)
|
||
|
{
|
||
|
struct ast_acl *acl;
|
||
|
|
||
|
AST_LIST_LOCK(acl_list);
|
||
|
AST_LIST_TRAVERSE(acl_list, acl, list) {
|
||
|
if (ast_strlen_zero(acl->name)) {
|
||
|
ast_cli(fd, "%s(permit/deny)\n", prefix);
|
||
|
} else {
|
||
|
ast_cli(fd, "%s%s\n", prefix, acl->name);
|
||
|
}
|
||
|
ast_ha_output(fd, acl->acl, prefix);
|
||
|
}
|
||
|
AST_LIST_UNLOCK(acl_list);
|
||
|
}
|
||
|
|
||
|
#define print_acl_cert_store(cfg, a, max_name_len) \
|
||
|
({ \
|
||
|
if (cfg->vcfg_common.acl) { \
|
||
|
ast_cli(a->fd, "x5u_acl:\n"); \
|
||
|
print_acl(a->fd, cfg->vcfg_common.acl, " "); \
|
||
|
} else { \
|
||
|
ast_cli(a->fd, "%-*s: (none)\n", max_name_len, "x5u_acl"); \
|
||
|
}\
|
||
|
if (cfg->vcfg_common.tcs) { \
|
||
|
int count = 0; \
|
||
|
ast_cli(a->fd, "%-*s:\n", max_name_len, "Verification CA certificate store"); \
|
||
|
count = crypto_show_cli_store(cfg->vcfg_common.tcs, a->fd); \
|
||
|
if (count == 0 && (!ast_strlen_zero(cfg->vcfg_common.ca_path) \
|
||
|
|| !ast_strlen_zero(cfg->vcfg_common.crl_path))) { \
|
||
|
ast_cli(a->fd, " Note: Certs in ca_path or crl_path won't show until used.\n"); \
|
||
|
} \
|
||
|
} else { \
|
||
|
ast_cli(a->fd, "%-*s: (none)\n", max_name_len, "Verification CA certificate store"); \
|
||
|
} \
|
||
|
})
|
||
|
|
||
|
int config_object_cli_show(void *obj, void *arg, void *data, int flags)
|
||
|
{
|
||
|
struct ast_cli_args *a = arg;
|
||
|
struct config_object_cli_data *cli_data = data;
|
||
|
struct ast_variable *options;
|
||
|
struct ast_variable *i;
|
||
|
const char *title = NULL;
|
||
|
const char *cfg_name = NULL;
|
||
|
int max_name_len = 0;
|
||
|
|
||
|
if (!obj) {
|
||
|
ast_cli(a->fd, "No stir/shaken configuration found\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (!ast_strlen_zero(cli_data->title)) {
|
||
|
title = cli_data->title;
|
||
|
} else {
|
||
|
title = ast_sorcery_object_get_type(obj);
|
||
|
}
|
||
|
max_name_len = strlen(title);
|
||
|
|
||
|
if (cli_data->object_type == config_object_type_profile
|
||
|
|| cli_data->object_type == config_object_type_tn) {
|
||
|
cfg_name = ast_sorcery_object_get_id(obj);
|
||
|
max_name_len += strlen(cfg_name) + 2 /* ": " */;
|
||
|
}
|
||
|
|
||
|
options = ast_variable_list_sort(ast_sorcery_objectset_create2(
|
||
|
get_sorcery(), obj, AST_HANDLER_ONLY_STRING));
|
||
|
if (!options) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
for (i = options; i; i = i->next) {
|
||
|
int nlen = strlen(i->name);
|
||
|
max_name_len = (nlen > max_name_len) ? nlen : max_name_len;
|
||
|
}
|
||
|
|
||
|
ast_cli(a->fd, "\n==============================================================================\n");
|
||
|
if (ast_strlen_zero(cfg_name)) {
|
||
|
ast_cli(a->fd, "%s\n", title);
|
||
|
} else {
|
||
|
ast_cli(a->fd, "%s: %s\n", title, cfg_name);
|
||
|
}
|
||
|
ast_cli(a->fd, "------------------------------------------------------------------------------\n");
|
||
|
|
||
|
for (i = options; i; i = i->next) {
|
||
|
if (!ast_strings_equal(i->name, "x5u_acl")) {
|
||
|
ast_cli(a->fd, "%-*s: %s\n", max_name_len, i->name,
|
||
|
translate_value(i->value));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ast_variables_destroy(options);
|
||
|
|
||
|
if (cli_data->object_type == config_object_type_profile) {
|
||
|
struct profile_cfg *cfg = obj;
|
||
|
print_acl_cert_store(cfg, a, max_name_len);
|
||
|
} else if (cli_data->object_type == config_object_type_verification) {
|
||
|
struct verification_cfg *cfg = obj;
|
||
|
print_acl_cert_store(cfg, a, max_name_len);
|
||
|
}
|
||
|
ast_cli(a->fd, "---------------------------------------------\n\n"); \
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
char *config_object_tab_complete_name(const char *word, struct ao2_container *container)
|
||
|
{
|
||
|
void *obj;
|
||
|
struct ao2_iterator it;
|
||
|
int wordlen = strlen(word);
|
||
|
int ret;
|
||
|
|
||
|
it = ao2_iterator_init(container, 0);
|
||
|
while ((obj = ao2_iterator_next(&it))) {
|
||
|
if (!strncasecmp(word, ast_sorcery_object_get_id(obj), wordlen)) {
|
||
|
ret = ast_cli_completion_add(ast_strdup(ast_sorcery_object_get_id(obj)));
|
||
|
if (ret) {
|
||
|
ao2_ref(obj, -1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
ao2_ref(obj, -1);
|
||
|
}
|
||
|
ao2_iterator_destroy(&it);
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
int common_config_reload(void)
|
||
|
{
|
||
|
SCOPE_ENTER(2, "Stir Shaken Reload\n");
|
||
|
if (vs_reload()) {
|
||
|
SCOPE_EXIT_RTN_VALUE(AST_MODULE_LOAD_DECLINE, "Stir Shaken VS Reload failed\n");
|
||
|
}
|
||
|
|
||
|
if (as_reload()) {
|
||
|
SCOPE_EXIT_RTN_VALUE(AST_MODULE_LOAD_DECLINE, "Stir Shaken AS Reload failed\n");
|
||
|
}
|
||
|
|
||
|
if (tn_config_reload()) {
|
||
|
SCOPE_EXIT_RTN_VALUE(AST_MODULE_LOAD_DECLINE, "Stir Shaken TN Reload failed\n");
|
||
|
}
|
||
|
|
||
|
if (profile_reload()) {
|
||
|
SCOPE_EXIT_RTN_VALUE(AST_MODULE_LOAD_DECLINE, "Stir Shaken Profile Reload failed\n");
|
||
|
}
|
||
|
|
||
|
SCOPE_EXIT_RTN_VALUE(AST_MODULE_LOAD_SUCCESS, "Stir Shaken Reload Done\n");
|
||
|
}
|
||
|
|
||
|
int common_config_unload(void)
|
||
|
{
|
||
|
profile_unload();
|
||
|
tn_config_unload();
|
||
|
as_unload();
|
||
|
vs_unload();
|
||
|
|
||
|
if (named_acl_changed_sub) {
|
||
|
stasis_unsubscribe(named_acl_changed_sub);
|
||
|
named_acl_changed_sub = NULL;
|
||
|
}
|
||
|
ast_sorcery_unref(sorcery);
|
||
|
sorcery = NULL;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void named_acl_changed_cb(void *data,
|
||
|
struct stasis_subscription *sub, struct stasis_message *message)
|
||
|
{
|
||
|
if (stasis_message_type(message) != ast_named_acl_change_type()) {
|
||
|
return;
|
||
|
}
|
||
|
ast_log(LOG_NOTICE, "Named acl changed. Reloading verification and profile\n");
|
||
|
common_config_reload();
|
||
|
}
|
||
|
|
||
|
int common_config_load(void)
|
||
|
{
|
||
|
SCOPE_ENTER(2, "Stir Shaken Load\n");
|
||
|
|
||
|
if (!(sorcery = ast_sorcery_open())) {
|
||
|
common_config_unload();
|
||
|
SCOPE_EXIT_RTN_VALUE(AST_MODULE_LOAD_DECLINE, "Stir Shaken sorcery load failed\n");
|
||
|
}
|
||
|
|
||
|
if (vs_load()) {
|
||
|
common_config_unload();
|
||
|
SCOPE_EXIT_RTN_VALUE(AST_MODULE_LOAD_DECLINE, "Stir Shaken VS load failed\n");
|
||
|
}
|
||
|
|
||
|
if (as_load()) {
|
||
|
common_config_unload();
|
||
|
SCOPE_EXIT_RTN_VALUE(AST_MODULE_LOAD_DECLINE, "Stir Shaken AS load failed\n");
|
||
|
}
|
||
|
|
||
|
if (tn_config_load()) {
|
||
|
common_config_unload();
|
||
|
SCOPE_EXIT_RTN_VALUE(AST_MODULE_LOAD_DECLINE, "Stir Shaken TN load failed\n");
|
||
|
}
|
||
|
|
||
|
if (profile_load()) {
|
||
|
common_config_unload();
|
||
|
SCOPE_EXIT_RTN_VALUE(AST_MODULE_LOAD_DECLINE, "Stir Shaken profile load failed\n");
|
||
|
}
|
||
|
|
||
|
if (!named_acl_changed_sub) {
|
||
|
named_acl_changed_sub = stasis_subscribe(ast_security_topic(),
|
||
|
named_acl_changed_cb, NULL);
|
||
|
if (!named_acl_changed_sub) {
|
||
|
common_config_unload();
|
||
|
SCOPE_EXIT_RTN_VALUE(AST_MODULE_LOAD_DECLINE, "Stir Shaken acl change subscribe failed\n");
|
||
|
}
|
||
|
stasis_subscription_accept_message_type(
|
||
|
named_acl_changed_sub, ast_named_acl_change_type());
|
||
|
}
|
||
|
|
||
|
SCOPE_EXIT_RTN_VALUE(AST_MODULE_LOAD_SUCCESS, "Stir Shaken Load Done\n");
|
||
|
}
|
||
|
|