; STIR/SHAKEN support.
;
;stir_shaken=no
+;stir_shaken_profile=my_profile
;[6001]
;type=auth
; happens to the call if verification fails; it's up to
; you to determine what to do with the results.
; (default: no)
+;stir_shaken_profile =
+ ; If a profile is specified (defined in stir_shaken.conf),
+ ; this endpoint will follow the rules defined there.
;allow_unauthenticated_options =
; By default, chan_pjsip will challenge an incoming
; OPTIONS request for authentication credentials just
;
; Must have an attestation of A, B, or C
;attestation=C
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Profiles can be defined here which can be referenced by channel drivers.
+;[my_profile]
+;
+; type must be "profile"
+;type=profile
+;
+; Set stir_shaken to 'attest', 'verify', or 'on', which is the default
+;stir_shaken=on
+;
+; You can specify an ACL that will be used strictly for the Identity header when downloading public certificates
+;acllist=myacllist
+;
+; You can also do permit / deny lines if you want (also supports IPv6)
+;permit=0.0.0.0/0.0.0.0
+;deny=127.0.0.1
AST_STRING_FIELD(accountcode);
/*! If set, we'll push incoming MWI NOTIFYs to stasis using this mailbox */
AST_STRING_FIELD(incoming_mwi_mailbox);
+ /*! STIR/SHAKEN profile to use */
+ AST_STRING_FIELD(stir_shaken_profile);
);
/*! Configuration for extensions */
struct ast_sip_endpoint_extensions extensions;
struct ast_stir_shaken_payload;
+struct ast_acl_list;
+
struct ast_json;
/*!
*/
unsigned int ast_stir_shaken_get_signature_timeout(void);
+/*!
+ * \brief Retrieve a stir_shaken_profile by id
+ *
+ * \note The profile will need to be unref'd when not needed anymore
+ *
+ * \param id The id of the stir_shaken_profile to get
+ *
+ * \retval stir_shaken_profile on success
+ * \retval NULL on failure
+ */
+struct stir_shaken_profile *ast_stir_shaken_get_profile(const char *id);
+
+/*!
+ * \brief Check if a stir_shaken_profile supports attestation
+ *
+ * \param profile The stir_shaken_profile to test
+ *
+ * \retval 0 if not supported
+ * \retval 1 if supported
+ */
+unsigned int ast_stir_shaken_profile_supports_attestation(const struct stir_shaken_profile *profile);
+
+/*!
+ * \brief Check if a stir_shaken_profile supports verification
+ *
+ * \param profile The stir_shaken_profile to test
+ *
+ * \retval 0 if not supported
+ * \retval 1 if supported
+ */
+unsigned int ast_stir_shaken_profile_supports_verification(const struct stir_shaken_profile *profile);
+
/*!
* \brief Add a STIR/SHAKEN verification result to a channel
*
struct ast_stir_shaken_payload *ast_stir_shaken_verify2(const char *header, const char *payload, const char *signature,
const char *algorithm, const char *public_cert_url, int *failure_code);
+/*!
+ * \brief Same as ast_stir_shaken_verify2, but passes in a stir_shaken_profile with additional configuration
+ *
+ * \note failure_code will be written to in this function
+ *
+ * \param header The payload header
+ * \param payload The payload section
+ * \param signature The payload signature
+ * \param algorithm The signature algorithm
+ * \param public_cert_url The public key URL
+ * \param failure_code Additional failure information
+ * \param profile The stir_shaken_profile
+ *
+ * \retval ast_stir_shaken_payload on success
+ * \retval NULL on failure
+ */
+struct ast_stir_shaken_payload *ast_stir_shaken_verify_with_profile(const char *header, const char *payload,
+ const char *signature, const char *algorithm, const char *public_cert_url, int *failure,
+ const struct stir_shaken_profile *profile);
+
/*!
* \brief Retrieve the stir/shaken sorcery context
*
INVITEs, an Identity header will be added.</para>
</description>
</configOption>
+ <configOption name="stir_shaken_profile" default="">
+ <synopsis>STIR/SHAKEN profile containing additional configuration options</synopsis>
+ <description><para>
+ A STIR/SHAKEN profile that is defined in stir_shaken.conf. Contains
+ several options and rules used for STIR/SHAKEN.</para>
+ </description>
+ </configOption>
<configOption name="allow_unauthenticated_options" default="no">
<synopsis>Skip authentication when receiving OPTIONS requests</synopsis>
<description><para>
"prefer: pending, operation: intersect, keep: all",
codec_prefs_handler, outgoing_answer_codec_prefs_to_str, NULL, 0, 0);
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "stir_shaken", "off", stir_shaken_handler, stir_shaken_to_str, NULL, 0, 0);
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "stir_shaken_profile", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, stir_shaken_profile));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_unauthenticated_options", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allow_unauthenticated_options));
if (ast_sip_initialize_sorcery_transport()) {
int mismatch = 0;
struct ast_stir_shaken_payload *ss_payload;
int failure_code = 0;
+ RAII_VAR(struct stir_shaken_profile *, profile, NULL, ao2_cleanup);
/* Check if this is a reinvite. If it is, we don't need to do anything */
if (rdata->msg_info.to->tag.slen) {
return 0;
}
- if ((session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_VERIFY) == 0) {
+ profile = ast_stir_shaken_get_profile(session->endpoint->stir_shaken_profile);
+ if ((profile && !ast_stir_shaken_profile_supports_verification(profile))
+ && ((session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_VERIFY) == 0)) {
return 0;
}
attestation = get_attestation_from_payload(payload);
- ss_payload = ast_stir_shaken_verify2(header, payload, signature, algorithm, public_cert_url, &failure_code);
+ ss_payload = ast_stir_shaken_verify_with_profile(header, payload, signature, algorithm, public_cert_url, &failure_code, profile);
+
if (!ss_payload) {
if (failure_code == AST_STIR_SHAKEN_VERIFY_FAILED_TO_GET_CERT) {
static void stir_shaken_outgoing_request(struct ast_sip_session *session, pjsip_tx_data *tdata)
{
- if ((session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_ATTEST) == 0) {
+ RAII_VAR(struct stir_shaken_profile *, profile, NULL, ao2_cleanup);
+
+ profile = ast_stir_shaken_get_profile(session->endpoint->stir_shaken_profile);
+ if ((profile && !ast_stir_shaken_profile_supports_attestation(profile))
+ && ((session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_ATTEST) == 0)) {
return;
}
#include "asterisk/global_datastores.h"
#include "asterisk/app.h"
#include "asterisk/test.h"
+#include "asterisk/acl.h"
#include "asterisk/res_stir_shaken.h"
#include "res_stir_shaken/stir_shaken.h"
#include "res_stir_shaken/store.h"
#include "res_stir_shaken/certificate.h"
#include "res_stir_shaken/curl.h"
+#include "res_stir_shaken/profile.h"
/*** DOCUMENTATION
<configInfo name="res_stir_shaken" language="en_US">
<synopsis>The caller ID number to match on.</synopsis>
</configOption>
</configObject>
+ <configObject name="profile">
+ <synopsis>STIR/SHAKEN profile configuration options</synopsis>
+ <configOption name="type">
+ <synopsis>Must be of type 'profile'.</synopsis>
+ </configOption>
+ <configOption name="stir_shaken" default="on">
+ <synopsis>STIR/SHAKEN configuration settings</synopsis>
+ <description><para>
+ Attest, verify, or do both STIR/SHAKEN operations. On incoming
+ INVITEs, the Identity header will be checked for validity. On
+ outgoing INVITEs, an Identity header will be added.</para>
+ </description>
+ </configOption>
+ <configOption name="acllist" default="">
+ <synopsis>An existing ACL from acl.conf to use</synopsis>
+ </configOption>
+ <configOption name="permit" default="">
+ <synopsis>An IP or subnet to permit</synopsis>
+ </configOption>
+ <configOption name="deny" default="">
+ <synopsis>An IP or subnet to deny</synopsis>
+ </configOption>
+ </configObject>
</configFile>
</configInfo>
<function name="STIR_SHAKEN" language="en_US">
return ast_stir_shaken_signature_timeout(stir_shaken_general_get());
}
+struct stir_shaken_profile *ast_stir_shaken_get_profile(const char *id)
+{
+ if (ast_strlen_zero(id)) {
+ return NULL;
+ }
+
+ return ast_stir_shaken_get_profile_by_name(id);
+}
+
+unsigned int ast_stir_shaken_profile_supports_attestation(const struct stir_shaken_profile *profile)
+{
+ if (!profile) {
+ return 0;
+ }
+
+ return (profile->stir_shaken & STIR_SHAKEN_ATTEST);
+}
+
+unsigned int ast_stir_shaken_profile_supports_verification(const struct stir_shaken_profile *profile)
+{
+ if (!profile) {
+ return 0;
+ }
+
+ return (profile->stir_shaken & STIR_SHAKEN_VERIFY);
+}
+
/*!
* \brief Convert an ast_stir_shaken_verification_result to string representation
*
* \retval NULL on failure
* \retval full path filename on success
*/
-static char *run_curl(const char *public_cert_url, const char *path)
+static char *run_curl(const char *public_cert_url, const char *path, const struct ast_acl_list *acl)
{
struct curl_cb_data *data;
char *filename;
return NULL;
}
- filename = curl_public_key(public_cert_url, path, data);
+ filename = curl_public_key(public_cert_url, path, data, acl);
if (!filename) {
ast_log(LOG_ERROR, "Could not retrieve public key for '%s'\n", public_cert_url);
curl_cb_data_free(data);
* \retval NULL on failure
* \retval full path filename on success
*/
-static char *curl_and_check_expiration(const char *public_cert_url, const char *path, int *curl)
+static char *curl_and_check_expiration(const char *public_cert_url, const char *path, int *curl, const struct ast_acl_list *acl)
{
char *filename;
return NULL;
}
- filename = run_curl(public_cert_url, path);
+ filename = run_curl(public_cert_url, path, acl);
if (!filename) {
return NULL;
}
* \retval 0 on success
* \retval 1 on failure
*/
-static int stir_shaken_verify_setup_file_paths(const char *public_cert_url, char **file_path, char **dir_path, int *curl)
+static int stir_shaken_verify_setup_file_paths(const char *public_cert_url, char **file_path, char **dir_path, int *curl,
+ const struct ast_acl_list *acl)
{
*file_path = get_path_to_public_key(public_cert_url);
if (ast_asprintf(dir_path, "%s/keys/%s", ast_config_AST_DATA_DIR, STIR_SHAKEN_DIR_NAME) < 0) {
ast_free(*file_path);
/* Download to the default path */
- *file_path = run_curl(public_cert_url, *dir_path);
+ *file_path = run_curl(public_cert_url, *dir_path, acl);
if (!(*file_path)) {
return 1;
}
* \retval 1 on failure
*/
static int stir_shaken_verify_validate_cert(const char *public_cert_url, char **file_path, char *dir_path, int *curl,
- EVP_PKEY **public_key)
+ EVP_PKEY **public_key, const struct ast_acl_list *acl)
{
if (public_key_is_expired(public_cert_url)) {
/* If this fails, then there's nothing we can do */
ast_free(*file_path);
- *file_path = curl_and_check_expiration(public_cert_url, dir_path, curl);
+ *file_path = curl_and_check_expiration(public_cert_url, dir_path, curl, acl);
if (!(*file_path)) {
return 1;
}
remove_public_key_from_astdb(public_cert_url);
ast_free(*file_path);
- *file_path = curl_and_check_expiration(public_cert_url, dir_path, curl);
+ *file_path = curl_and_check_expiration(public_cert_url, dir_path, curl, acl);
if (!(*file_path)) {
return 1;
}
struct ast_stir_shaken_payload *ast_stir_shaken_verify2(const char *header, const char *payload, const char *signature,
const char *algorithm, const char *public_cert_url, int *failure_code)
+{
+ return ast_stir_shaken_verify_with_profile(header, payload, signature, algorithm, public_cert_url, failure_code, NULL);
+}
+
+struct ast_stir_shaken_payload *ast_stir_shaken_verify_with_profile(const char *header, const char *payload, const char *signature,
+ const char *algorithm, const char *public_cert_url, int *failure_code, const struct stir_shaken_profile *profile)
{
struct ast_stir_shaken_payload *ret_payload;
EVP_PKEY *public_key;
RAII_VAR(char *, dir_path, NULL, ast_free);
RAII_VAR(char *, combined_str, NULL, ast_free);
size_t combined_size;
+ const struct ast_acl_list *acl;
if (stir_shaken_verify_check_empty_strings(header, payload, signature, algorithm, public_cert_url)) {
return NULL;
}
+ acl = profile ? (const struct ast_acl_list *)profile->acl : NULL;
+
/* Check to see if we have already downloaded this public cert. The reason we
* store the file path is because:
*
* {configurable) directories, we already have the storage mechanism in place.
* The only thing that would be left to do is pull from the configuration.
*/
- if (stir_shaken_verify_setup_file_paths(public_cert_url, &file_path, &dir_path, &curl)) {
+ if (stir_shaken_verify_setup_file_paths(public_cert_url, &file_path, &dir_path, &curl, acl)) {
return NULL;
}
/* Check to see if the cert we downloaded (or already had) is expired */
- if (stir_shaken_verify_validate_cert(public_cert_url, &file_path, dir_path, &curl, &public_key)) {
+ if (stir_shaken_verify_validate_cert(public_cert_url, &file_path, dir_path, &curl, &public_key, acl)) {
*failure_code = AST_STIR_SHAKEN_VERIFY_FAILED_TO_GET_CERT;
return NULL;
}
{
int res = 0;
+ stir_shaken_profile_unload();
stir_shaken_certificate_unload();
stir_shaken_store_unload();
stir_shaken_general_unload();
return AST_MODULE_LOAD_DECLINE;
}
+ if (stir_shaken_profile_load()) {
+ unload_module();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
ast_sorcery_load(ast_stir_shaken_sorcery());
res |= ast_custom_function_register(&stir_shaken_function);
#include "asterisk/utils.h"
#include "asterisk/logger.h"
#include "asterisk/file.h"
+#include "asterisk/acl.h"
+
#include "curl.h"
#include "general.h"
#include "stir_shaken.h"
+#include "profile.h"
#include <curl/curl.h>
#include <sys/stat.h>
const char *url;
};
+struct curl_cb_open_socket {
+ const struct ast_acl_list *acl;
+ curl_socket_t *sockfd;
+};
+
struct curl_cb_data *curl_cb_data_create(void)
{
struct curl_cb_data *data;
ast_free(data);
}
+static void curl_cb_open_socket_free(struct curl_cb_open_socket *data)
+{
+ if (!data) {
+ return;
+ }
+
+ close(*data->sockfd);
+
+ /* We don't need to free the ACL since we just use a reference */
+ ast_free(data);
+}
+
char *curl_cb_data_get_cache_control(const struct curl_cb_data *data)
{
if (!data) {
return real_size;
}
-char *curl_public_key(const char *public_cert_url, const char *path, struct curl_cb_data *data)
+static curl_socket_t stir_shaken_curl_open_socket_callback(void *our_data, curlsocktype purpose, struct curl_sockaddr *address)
+{
+ struct curl_cb_open_socket *data = our_data;
+
+ if (!ast_acl_list_is_empty((struct ast_acl_list *)data->acl)) {
+ struct ast_sockaddr ast_address = { {0,} };
+
+ ast_sockaddr_copy_sockaddr(&ast_address, &address->addr, address->addrlen);
+
+ if (ast_apply_acl((struct ast_acl_list *)data->acl, &ast_address, NULL) != AST_SENSE_ALLOW) {
+ return CURLE_COULDNT_CONNECT;
+ }
+ }
+
+ *data->sockfd = socket(address->family, address->socktype, address->protocol);
+
+ return *data->sockfd;
+}
+
+char *curl_public_key(const char *public_cert_url, const char *path, struct curl_cb_data *data, const struct ast_acl_list *acl)
{
FILE *public_key_file;
char *filename;
CURL *curl;
char curl_errbuf[CURL_ERROR_SIZE + 1];
struct curl_cb_write_buf *buf;
+ struct curl_cb_open_socket *open_socket_data;
+ curl_socket_t sockfd;
- buf = ast_calloc(1, sizeof(*buf));
+ curl_errbuf[CURL_ERROR_SIZE] = '\0';
+
+ buf = ast_calloc(1, sizeof(*buf));
if (!buf) {
ast_log(LOG_ERROR, "Failed to allocate memory for CURL write buffer for %s\n", public_cert_url);
return NULL;
}
+ open_socket_data = ast_calloc(1, sizeof(*open_socket_data));
+ if (!open_socket_data) {
+ ast_log(LOG_ERROR, "Failed to allocate memory for open socket callback\n");
+ return NULL;
+ }
+ open_socket_data->acl = acl;
+ open_socket_data->sockfd = &sockfd;
+
buf->url = public_cert_url;
curl_errbuf[CURL_ERROR_SIZE] = '\0';
curl_easy_setopt(curl, CURLOPT_WRITEDATA, buf);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);
curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, MAX_BUF_SIZE_PER_WRITE);
+ curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, stir_shaken_curl_open_socket_callback);
+ curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, open_socket_data);
if (curl_easy_perform(curl)) {
ast_log(LOG_ERROR, "%s\n", curl_errbuf);
curl_easy_cleanup(curl);
ast_free(buf);
+ curl_cb_open_socket_free(open_socket_data);
return NULL;
}
+ curl_cb_open_socket_free(open_socket_data);
+
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl);
#ifndef _STIR_SHAKEN_CURL_H
#define _STIR_SHAKEN_CURL_H
+struct ast_acl_list;
+
/* Forward declaration for CURL callback data */
struct curl_cb_data;
* \param public_cert_url The public cert URL
* \param path The path to download the file to
* \param data The curl_cb_data
+ * \param acl The ACL to use for cURL (if not NULL)
*
* \retval NULL on failure
* \retval full path filename on success
*/
-char *curl_public_key(const char *public_cert_url, const char *path, struct curl_cb_data *data);
+char *curl_public_key(const char *public_cert_url, const char *path, struct curl_cb_data *data, const struct ast_acl_list *acl);
#endif /* _STIR_SHAKEN_CURL_H */
--- /dev/null
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * Ben Ford <bford@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/sorcery.h"
+
+#include "stir_shaken.h"
+#include "profile.h"
+#include "asterisk/res_stir_shaken.h"
+
+#define CONFIG_TYPE "profile"
+
+static void stir_shaken_profile_destructor(void *obj)
+{
+ struct stir_shaken_profile *cfg = obj;
+
+ ast_free_acl_list(cfg->acl);
+
+ return;
+}
+
+static void *stir_shaken_profile_alloc(const char *name)
+{
+ struct stir_shaken_profile *cfg;
+
+ cfg = ast_sorcery_generic_alloc(sizeof(*cfg), stir_shaken_profile_destructor);
+ if (!cfg) {
+ return NULL;
+ }
+
+ return cfg;
+}
+
+static struct stir_shaken_profile *stir_shaken_profile_get(const char *id)
+{
+ return ast_sorcery_retrieve_by_id(ast_stir_shaken_sorcery(), CONFIG_TYPE, id);
+}
+
+static struct ao2_container *stir_shaken_profile_get_all(void)
+{
+ return ast_sorcery_retrieve_by_fields(ast_stir_shaken_sorcery(), CONFIG_TYPE,
+ AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+}
+
+struct stir_shaken_profile *ast_stir_shaken_get_profile_by_name(const char *name)
+{
+ return ast_sorcery_retrieve_by_id(ast_stir_shaken_sorcery(), CONFIG_TYPE, name);
+}
+
+static int stir_shaken_profile_apply(const struct ast_sorcery *sorcery, void *obj)
+{
+ return 0;
+}
+
+static int stir_shaken_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct stir_shaken_profile *cfg = obj;
+
+ if (!strcasecmp("attest", var->value)) {
+ cfg->stir_shaken = STIR_SHAKEN_ATTEST;
+ } else if (!strcasecmp("verify", var->value)) {
+ cfg->stir_shaken = STIR_SHAKEN_VERIFY;
+ } else if (!strcasecmp("on", var->value)) {
+ cfg->stir_shaken = STIR_SHAKEN_ON;
+ } else {
+ ast_log(LOG_WARNING, "'%s' is not a valid value for option "
+ "'stir_shaken' for %s %s\n",
+ var->value, CONFIG_TYPE, ast_sorcery_object_get_id(cfg));
+ return -1;
+ }
+
+ return 0;
+}
+
+static const char *stir_shaken_map[] = {
+ [STIR_SHAKEN_ATTEST] = "attest",
+ [STIR_SHAKEN_VERIFY] = "verify",
+ [STIR_SHAKEN_ON] = "on",
+};
+
+static int stir_shaken_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct stir_shaken_profile *cfg = obj;
+ if (ARRAY_IN_BOUNDS(cfg->stir_shaken, stir_shaken_map)) {
+ *buf = ast_strdup(stir_shaken_map[cfg->stir_shaken]);
+ }
+ return 0;
+}
+
+static int stir_shaken_acl_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct stir_shaken_profile *cfg = obj;
+ int error = 0;
+ int ignore;
+
+ if (ast_strlen_zero(var->value)) {
+ return 0;
+ }
+
+ ast_append_acl(var->name, var->value, &cfg->acl, &error, &ignore);
+
+ return error;
+}
+
+static int acl_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct stir_shaken_profile *cfg = obj;
+ struct ast_acl_list *acl_list;
+ struct ast_acl *first_acl;
+
+ if (cfg && !ast_acl_list_is_empty(acl_list=cfg->acl)) {
+ AST_LIST_LOCK(acl_list);
+ first_acl = AST_LIST_FIRST(acl_list);
+ if (ast_strlen_zero(first_acl->name)) {
+ *buf = "deny/permit";
+ } else {
+ *buf = first_acl->name;
+ }
+ AST_LIST_UNLOCK(acl_list);
+ }
+
+ *buf = ast_strdup(*buf);
+ return 0;
+}
+
+static char *stir_shaken_profile_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct stir_shaken_profile *cfg;
+
+ switch(cmd) {
+ case CLI_INIT:
+ e->command = "stir_shaken show profile";
+ e->usage =
+ "Usage: stir_shaken show profile <id>\n"
+ " Show the stir/shaken profile settings for a given id\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 3) {
+ return stir_shaken_tab_complete_name(a->word, stir_shaken_profile_get_all());
+ } else {
+ return NULL;
+ }
+ }
+
+ if (a->argc != 4) {
+ return CLI_SHOWUSAGE;
+ }
+
+ cfg = stir_shaken_profile_get(a->argv[3]);
+ stir_shaken_cli_show(cfg, a, 0);
+ ast_acl_output(a->fd, cfg->acl, NULL);
+ ao2_cleanup(cfg);
+
+ return CLI_SUCCESS;
+}
+
+static char *stir_shaken_profile_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ao2_container *container;
+
+ switch(cmd) {
+ case CLI_INIT:
+ e->command = "stir_shaken show profiles";
+ e->usage =
+ "Usage: stir_shaken show profiles\n"
+ " Show all profiles for stir/shaken\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ container = stir_shaken_profile_get_all();
+ if (!container || ao2_container_count(container) == 0) {
+ ast_cli(a->fd, "No stir/shaken ACLs found\n");
+ ao2_cleanup(container);
+ return CLI_SUCCESS;
+ }
+
+ ao2_callback(container, OBJ_NODATA, stir_shaken_cli_show, a);
+ ao2_ref(container, -1);
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry stir_shaken_profile_cli[] = {
+ AST_CLI_DEFINE(stir_shaken_profile_show, "Show stir/shaken profile by id"),
+ AST_CLI_DEFINE(stir_shaken_profile_show_all, "Show all stir/shaken profiles"),
+};
+
+int stir_shaken_profile_unload(void)
+{
+ ast_cli_unregister_multiple(stir_shaken_profile_cli,
+ ARRAY_LEN(stir_shaken_profile_cli));
+
+ return 0;
+}
+
+int stir_shaken_profile_load(void)
+{
+ struct ast_sorcery *sorcery = ast_stir_shaken_sorcery();
+
+ ast_sorcery_apply_default(sorcery, CONFIG_TYPE, "config", "stir_shaken.conf,criteria=type=profile");
+
+ if (ast_sorcery_object_register(sorcery, CONFIG_TYPE, stir_shaken_profile_alloc,
+ NULL, stir_shaken_profile_apply)) {
+ ast_log(LOG_ERROR, "stir/shaken - failed to register '%s' sorcery object\n", CONFIG_TYPE);
+ return -1;
+ }
+
+ ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "type", "", OPT_NOOP_T, 0, 0);
+ ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "stir_shaken", "on", stir_shaken_handler, stir_shaken_to_str, NULL, 0, 0);
+ ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "deny", "", stir_shaken_acl_handler, NULL, NULL, 0, 0);
+ ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "permit", "", stir_shaken_acl_handler, NULL, NULL, 0, 0);
+ ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "acllist", "", stir_shaken_acl_handler, acl_to_str, NULL, 0, 0);
+
+ ast_cli_register_multiple(stir_shaken_profile_cli,
+ ARRAY_LEN(stir_shaken_profile_cli));
+
+ return 0;
+}
--- /dev/null
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * Ben Ford <bford@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.
+ */
+#ifndef _STIR_SHAKEN_PROFILE_H
+#define _STIR_SHAKEN_PROFILE_H
+
+#include "profile_private.h"
+
+struct stir_shaken_profile *ast_stir_shaken_get_profile_by_name(const char *name);
+
+/*!
+ * \brief Load time initialization for the stir/shaken 'profile' object
+ *
+ * \retval 0 on success, -1 on error
+ */
+int stir_shaken_profile_load(void);
+
+/*!
+ * \brief Unload time cleanup for the stir/shaken 'profile'
+ *
+ * \retval 0 on success, -1 on error
+ */
+int stir_shaken_profile_unload(void);
+
+#endif /* _STIR_SHAKEN_PROFILE_H */
--- /dev/null
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * Ben Ford <bford@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.
+ */
+#ifndef _STIR_SHAKEN_PROFILE_PRIVATE_H
+#define _STIR_SHAKEN_PROFILE_PRIVATE_H
+
+#include "asterisk/sorcery.h"
+
+#include "asterisk/acl.h"
+
+enum stir_shaken_profile_behavior {
+ /*! Only do STIR/SHAKEN attestation */
+ STIR_SHAKEN_ATTEST = 1,
+ /*! Only do STIR/SHAKEN verification */
+ STIR_SHAKEN_VERIFY = 2,
+ /*! Do STIR/SHAKEN attestation and verification */
+ STIR_SHAKEN_ON = 3,
+};
+
+struct stir_shaken_profile {
+ SORCERY_OBJECT(details);
+ unsigned int stir_shaken;
+ struct ast_acl_list *acl;
+};
+
+#endif /* _STIR_SHAKEN_PROFILE_PRIVATE_H */