diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index 8507b4ed8caa143c40535e5b5ce342a50ef245c5..b1c1657ff7007e925ef231bedec4844cfa3c2fa7 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -349,6 +349,7 @@ ; STIR/SHAKEN support. ; ;stir_shaken=no +;stir_shaken_profile=my_profile ;[6001] ;type=auth @@ -930,6 +931,9 @@ ; 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 diff --git a/configs/samples/stir_shaken.conf.sample b/configs/samples/stir_shaken.conf.sample index c39bc9726530e0366e4f978ae72b8dd3d2c61658..677d3bb3ba987e5d9e58196f87bdeb9fec82fb2f 100644 --- a/configs/samples/stir_shaken.conf.sample +++ b/configs/samples/stir_shaken.conf.sample @@ -83,3 +83,21 @@ ; ; 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 diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 9f353936ca3d9a695fcb27040cd28840eabc1f24..209cdbfba28e7b01797a511693c51a07cc98b73f 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -878,6 +878,8 @@ struct ast_sip_endpoint { 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; diff --git a/include/asterisk/res_stir_shaken.h b/include/asterisk/res_stir_shaken.h index 92eb0ec70b81ca2eb3066f4080ad5f8609ac44ef..ece99b52a04ebb25596613c6342d5326d6dcaea5 100644 --- a/include/asterisk/res_stir_shaken.h +++ b/include/asterisk/res_stir_shaken.h @@ -38,6 +38,8 @@ enum ast_stir_shaken_verify_failure_reason { struct ast_stir_shaken_payload; +struct ast_acl_list; + struct ast_json; /*! @@ -65,6 +67,38 @@ char *ast_stir_shaken_payload_get_public_cert_url(const struct ast_stir_shaken_p */ 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 * @@ -112,6 +146,26 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const 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 * diff --git a/res/res_pjsip/pjsip_config.xml b/res/res_pjsip/pjsip_config.xml index 3e0a6e7083a3ae383d4ca6c9e2107d3b280e0fce..ca5a266849ee626d4d564bfc693de09206492aa8 100644 --- a/res/res_pjsip/pjsip_config.xml +++ b/res/res_pjsip/pjsip_config.xml @@ -1424,6 +1424,13 @@ 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> diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index e32a3d20dc8ab3bb2a62ed9732d430f329b771c1..39c3daba5a80cc3bdc477a95f9e0f23b328f3c2a 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -2192,6 +2192,7 @@ int ast_res_pjsip_initialize_configuration(void) "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()) { diff --git a/res/res_pjsip_stir_shaken.c b/res/res_pjsip_stir_shaken.c index 27aa4d8caf233268f6b8cc870081bb4cb0cb4a7a..19e846ff73d4bdd156fe91d7e19ba2d6d108aa8e 100644 --- a/res/res_pjsip_stir_shaken.c +++ b/res/res_pjsip_stir_shaken.c @@ -217,13 +217,16 @@ static int stir_shaken_incoming_request(struct ast_sip_session *session, pjsip_r 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; } @@ -309,7 +312,8 @@ static int stir_shaken_incoming_request(struct ast_sip_session *session, pjsip_r 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) { @@ -471,7 +475,11 @@ static void add_date_header(const struct ast_sip_session *session, pjsip_tx_data 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; } diff --git a/res/res_stir_shaken.c b/res/res_stir_shaken.c index 373a1a1a926c3d66659509174eca8095a6bc9291..157500a537ca71aa3ac0293d2d2be69eb3257cf2 100644 --- a/res/res_stir_shaken.c +++ b/res/res_stir_shaken.c @@ -38,6 +38,7 @@ #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" @@ -45,6 +46,7 @@ #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"> @@ -108,6 +110,29 @@ <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"> @@ -205,6 +230,33 @@ unsigned int ast_stir_shaken_get_signature_timeout(void) 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 * @@ -554,7 +606,7 @@ static int stir_shaken_verify_signature(const char *msg, const char *signature, * \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; @@ -565,7 +617,7 @@ static char *run_curl(const char *public_cert_url, const char *path) 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); @@ -591,7 +643,7 @@ static char *run_curl(const char *public_cert_url, const char *path) * \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; @@ -600,7 +652,7 @@ static char *curl_and_check_expiration(const char *public_cert_url, const char * return NULL; } - filename = run_curl(public_cert_url, path); + filename = run_curl(public_cert_url, path, acl); if (!filename) { return NULL; } @@ -662,7 +714,8 @@ static int stir_shaken_verify_check_empty_strings(const char *header, const char * \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) { @@ -680,7 +733,7 @@ static int stir_shaken_verify_setup_file_paths(const char *public_cert_url, char 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; } @@ -704,7 +757,7 @@ static int stir_shaken_verify_setup_file_paths(const char *public_cert_url, char * \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)) { @@ -714,7 +767,7 @@ static int stir_shaken_verify_validate_cert(const char *public_cert_url, char ** /* 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; } @@ -730,7 +783,7 @@ static int stir_shaken_verify_validate_cert(const char *public_cert_url, char ** 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; } @@ -756,6 +809,12 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const 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; @@ -764,11 +823,14 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify2(const char *header, cons 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: * @@ -779,12 +841,12 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify2(const char *header, cons * {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; } @@ -1677,6 +1739,7 @@ static int unload_module(void) { int res = 0; + stir_shaken_profile_unload(); stir_shaken_certificate_unload(); stir_shaken_store_unload(); stir_shaken_general_unload(); @@ -1716,6 +1779,11 @@ static int load_module(void) 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); diff --git a/res/res_stir_shaken/curl.c b/res/res_stir_shaken/curl.c index cd784614d87de083b1b28991c797535fcdfbbd3c..fb06de59ca9c19bf21e33e78f17b6dfdda7f55ca 100644 --- a/res/res_stir_shaken/curl.c +++ b/res/res_stir_shaken/curl.c @@ -21,9 +21,12 @@ #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> @@ -52,6 +55,11 @@ struct curl_cb_write_buf { 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; @@ -73,6 +81,18 @@ void curl_cb_data_free(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) { @@ -200,7 +220,26 @@ static size_t curl_write_cb(void *curl_data, size_t size, size_t actual_size, vo 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; @@ -209,13 +248,25 @@ char *curl_public_key(const char *public_cert_url, const char *path, struct curl 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'; @@ -231,14 +282,19 @@ char *curl_public_key(const char *public_cert_url, const char *path, struct curl 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); diff --git a/res/res_stir_shaken/curl.h b/res/res_stir_shaken/curl.h index ae8fedaa57d446b5422297062e326672f3fd2796..2dbd5d2346a1373344dabf261fa96056feee889e 100644 --- a/res/res_stir_shaken/curl.h +++ b/res/res_stir_shaken/curl.h @@ -18,6 +18,8 @@ #ifndef _STIR_SHAKEN_CURL_H #define _STIR_SHAKEN_CURL_H +struct ast_acl_list; + /* Forward declaration for CURL callback data */ struct curl_cb_data; @@ -66,10 +68,11 @@ char *curl_cb_data_get_expires(const struct curl_cb_data *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 */ diff --git a/res/res_stir_shaken/profile.c b/res/res_stir_shaken/profile.c new file mode 100644 index 0000000000000000000000000000000000000000..5d4fa9b8387514553861ba061ea8ebed22bdb03e --- /dev/null +++ b/res/res_stir_shaken/profile.c @@ -0,0 +1,241 @@ +/* + * 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; +} diff --git a/res/res_stir_shaken/profile.h b/res/res_stir_shaken/profile.h new file mode 100644 index 0000000000000000000000000000000000000000..5617e9ad1e6a5327b76f01be92927df4f004e609 --- /dev/null +++ b/res/res_stir_shaken/profile.h @@ -0,0 +1,39 @@ +/* + * 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 */ diff --git a/res/res_stir_shaken/profile_private.h b/res/res_stir_shaken/profile_private.h new file mode 100644 index 0000000000000000000000000000000000000000..536a0fe1a236c4b57fc13ddfa1944046cab2f37a --- /dev/null +++ b/res/res_stir_shaken/profile_private.h @@ -0,0 +1,40 @@ +/* + * 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 */