diff options
Diffstat (limited to 'res/res_pjsip')
-rw-r--r-- | res/res_pjsip/config_auth.c | 127 | ||||
-rw-r--r-- | res/res_pjsip/config_domain_aliases.c | 65 | ||||
-rw-r--r-- | res/res_pjsip/config_global.c | 90 | ||||
-rw-r--r-- | res/res_pjsip/config_security.c | 88 | ||||
-rw-r--r-- | res/res_pjsip/config_system.c | 112 | ||||
-rw-r--r-- | res/res_pjsip/config_transport.c | 338 | ||||
-rw-r--r-- | res/res_pjsip/include/res_pjsip_private.h | 74 | ||||
-rw-r--r-- | res/res_pjsip/location.c | 328 | ||||
-rw-r--r-- | res/res_pjsip/pjsip_configuration.c | 892 | ||||
-rw-r--r-- | res/res_pjsip/pjsip_distributor.c | 322 | ||||
-rw-r--r-- | res/res_pjsip/pjsip_global_headers.c | 171 | ||||
-rw-r--r-- | res/res_pjsip/pjsip_options.c | 783 | ||||
-rw-r--r-- | res/res_pjsip/pjsip_outbound_auth.c | 94 | ||||
-rw-r--r-- | res/res_pjsip/security_events.c | 234 |
14 files changed, 3718 insertions, 0 deletions
diff --git a/res/res_pjsip/config_auth.c b/res/res_pjsip/config_auth.c new file mode 100644 index 000000000..e5deb2d89 --- /dev/null +++ b/res/res_pjsip/config_auth.c @@ -0,0 +1,127 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Mark Michelson <mmichelson@digium.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 <pjsip.h> +#include <pjlib.h> +#include "asterisk/res_pjsip.h" +#include "asterisk/logger.h" +#include "asterisk/sorcery.h" + +static void auth_destroy(void *obj) +{ + struct ast_sip_auth *auth = obj; + ast_string_field_free_memory(auth); +} + +static void *auth_alloc(const char *name) +{ + struct ast_sip_auth *auth = ast_sorcery_generic_alloc(sizeof(*auth), auth_destroy); + + if (!auth) { + return NULL; + } + + if (ast_string_field_init(auth, 64)) { + ao2_cleanup(auth); + return NULL; + } + + return auth; +} + +static int auth_type_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_auth *auth = obj; + if (!strcasecmp(var->value, "userpass")) { + auth->type = AST_SIP_AUTH_TYPE_USER_PASS; + } else if (!strcasecmp(var->value, "md5")) { + auth->type = AST_SIP_AUTH_TYPE_MD5; + } else { + ast_log(LOG_WARNING, "Unknown authentication storage type '%s' specified for %s\n", + var->value, var->name); + return -1; + } + return 0; +} + +static int auth_apply(const struct ast_sorcery *sorcery, void *obj) +{ + struct ast_sip_auth *auth = obj; + int res = 0; + + if (ast_strlen_zero(auth->auth_user)) { + ast_log(LOG_ERROR, "No authentication username for auth '%s'\n", + ast_sorcery_object_get_id(auth)); + return -1; + } + + switch (auth->type) { + case AST_SIP_AUTH_TYPE_USER_PASS: + if (ast_strlen_zero(auth->auth_pass)) { + ast_log(LOG_ERROR, "'userpass' authentication specified but no" + "password specified for auth '%s'\n", ast_sorcery_object_get_id(auth)); + res = -1; + } + break; + case AST_SIP_AUTH_TYPE_MD5: + if (ast_strlen_zero(auth->md5_creds)) { + ast_log(LOG_ERROR, "'md5' authentication specified but no md5_cred" + "specified for auth '%s'\n", ast_sorcery_object_get_id(auth)); + res = -1; + } else if (strlen(auth->md5_creds) != PJSIP_MD5STRLEN) { + ast_log(LOG_ERROR, "'md5' authentication requires digest of size '%d', but" + "digest is '%d' in size for auth '%s'\n", PJSIP_MD5STRLEN, (int)strlen(auth->md5_creds), + ast_sorcery_object_get_id(auth)); + res = -1; + } + break; + case AST_SIP_AUTH_TYPE_ARTIFICIAL: + break; + } + + return res; +} + +/*! \brief Initialize sorcery with auth support */ +int ast_sip_initialize_sorcery_auth(struct ast_sorcery *sorcery) +{ + ast_sorcery_apply_default(sorcery, SIP_SORCERY_AUTH_TYPE, "config", "pjsip.conf,criteria=type=auth"); + + if (ast_sorcery_object_register(sorcery, SIP_SORCERY_AUTH_TYPE, auth_alloc, NULL, auth_apply)) { + return -1; + } + + ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "type", "", + OPT_NOOP_T, 0, 0); + ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "username", + "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, auth_user)); + ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "password", + "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, auth_pass)); + ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "md5_cred", + "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, md5_creds)); + ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "realm", + "asterisk", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, realm)); + ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "nonce_lifetime", + "32", OPT_UINT_T, 0, FLDSET(struct ast_sip_auth, nonce_lifetime)); + ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "auth_type", + "userpass", auth_type_handler, NULL, 0, 0); + + return 0; +} diff --git a/res/res_pjsip/config_domain_aliases.c b/res/res_pjsip/config_domain_aliases.c new file mode 100644 index 000000000..6ca3736a0 --- /dev/null +++ b/res/res_pjsip/config_domain_aliases.c @@ -0,0 +1,65 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Joshua Colp <jcolp@digium.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 "pjsip.h" +#include "pjlib.h" +#include "asterisk/res_pjsip.h" +#include "asterisk/logger.h" +#include "asterisk/sorcery.h" + +static void domain_alias_destroy(void *obj) +{ + struct ast_sip_domain_alias *alias = obj; + + ast_string_field_free_memory(alias); +} + +static void *domain_alias_alloc(const char *name) +{ + struct ast_sip_domain_alias *alias = ast_sorcery_generic_alloc(sizeof(*alias), domain_alias_destroy); + + if (!alias) { + return NULL; + } + + if (ast_string_field_init(alias, 256)) { + ao2_cleanup(alias); + return NULL; + } + + return alias; +} + +/*! \brief Initialize sorcery with domain alias support */ +int ast_sip_initialize_sorcery_domain_alias(struct ast_sorcery *sorcery) +{ + ast_sorcery_apply_default(sorcery, SIP_SORCERY_DOMAIN_ALIAS_TYPE, "config", "pjsip.conf,criteria=type=domain_alias"); + + if (ast_sorcery_object_register(sorcery, SIP_SORCERY_DOMAIN_ALIAS_TYPE, domain_alias_alloc, NULL, NULL)) { + return -1; + } + + ast_sorcery_object_field_register(sorcery, SIP_SORCERY_DOMAIN_ALIAS_TYPE, "type", "", + OPT_NOOP_T, 0, 0); + ast_sorcery_object_field_register(sorcery, SIP_SORCERY_DOMAIN_ALIAS_TYPE, "domain", + "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_domain_alias, domain)); + + return 0; +} diff --git a/res/res_pjsip/config_global.c b/res/res_pjsip/config_global.c new file mode 100644 index 000000000..2b2c021ee --- /dev/null +++ b/res/res_pjsip/config_global.c @@ -0,0 +1,90 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Mark Michelson <mmichelson@digium.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 <pjsip.h> +#include <pjlib.h> + +#include "asterisk/res_pjsip.h" +#include "asterisk/sorcery.h" +#include "asterisk/ast_version.h" + +#define DEFAULT_MAX_FORWARDS 70 +#define DEFAULT_USERAGENT_PREFIX "Asterisk PBX" + +static char default_useragent[128]; + +struct global_config { + SORCERY_OBJECT(details); + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(useragent); + ); + /* Value to put in Max-Forwards header */ + unsigned int max_forwards; +}; + +static void global_destructor(void *obj) +{ + struct global_config *cfg = obj; + + ast_string_field_free_memory(cfg); +} + +static void *global_alloc(const char *name) +{ + struct global_config *cfg = ast_sorcery_generic_alloc(sizeof(*cfg), global_destructor); + + if (!cfg || ast_string_field_init(cfg, 64)) { + return NULL; + } + + return cfg; +} + +static int global_apply(const struct ast_sorcery *sorcery, void *obj) +{ + struct global_config *cfg = obj; + char max_forwards[10]; + + snprintf(max_forwards, sizeof(max_forwards), "%u", cfg->max_forwards); + + ast_sip_add_global_request_header("Max-Forwards", max_forwards, 1); + ast_sip_add_global_request_header("User-Agent", cfg->useragent, 1); + ast_sip_add_global_response_header("Server", cfg->useragent, 1); + return 0; +} + +int ast_sip_initialize_sorcery_global(struct ast_sorcery *sorcery) +{ + 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"); + + if (ast_sorcery_object_register(sorcery, "global", global_alloc, NULL, global_apply)) { + return -1; + } + + ast_sorcery_object_field_register(sorcery, "global", "type", "", OPT_NOOP_T, 0, 0); + ast_sorcery_object_field_register(sorcery, "global", "maxforwards", __stringify(DEFAULT_MAX_FORWARDS), + OPT_UINT_T, 0, FLDSET(struct global_config, max_forwards)); + ast_sorcery_object_field_register(sorcery, "global", "useragent", default_useragent, + OPT_STRINGFIELD_T, 0, STRFLDSET(struct global_config, useragent)); + + return 0; +} diff --git a/res/res_pjsip/config_security.c b/res/res_pjsip/config_security.c new file mode 100644 index 000000000..3caff2b56 --- /dev/null +++ b/res/res_pjsip/config_security.c @@ -0,0 +1,88 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Mark Michelson <mmichelson@digium.com> + * Kevin Harwell <kharwell@digium.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. + */ + +/*** MODULEINFO + <depend>pjproject</depend> + <depend>res_pjsip</depend> + <support_level>core</support_level> + ***/ +#include "asterisk.h" + +#include <pjsip.h> + +#include "asterisk/res_pjsip.h" +#include "asterisk/logger.h" +#include "asterisk/sorcery.h" +#include "asterisk/acl.h" + +static int acl_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_security *security = obj; + int error = 0; + int ignore; + if (!strncmp(var->name, "contact", 7)) { + ast_append_acl(var->name + 7, var->value, &security->contact_acl, &error, &ignore); + } else { + ast_append_acl(var->name, var->value, &security->acl, &error, &ignore); + } + + return error; +} + +static void security_destroy(void *obj) +{ + struct ast_sip_security *security = obj; + security->acl = ast_free_acl_list(security->acl); + security->contact_acl = ast_free_acl_list(security->contact_acl); +} + +static void *security_alloc(const char *name) +{ + struct ast_sip_security *security = + ast_sorcery_generic_alloc(sizeof(*security), security_destroy); + + if (!security) { + return NULL; + } + + return security; +} + +int ast_sip_initialize_sorcery_security(struct ast_sorcery *sorcery) +{ + ast_sorcery_apply_default(sorcery, SIP_SORCERY_SECURITY_TYPE, + "config", "pjsip.conf,criteria=type=security"); + + if (ast_sorcery_object_register(sorcery, SIP_SORCERY_SECURITY_TYPE, + security_alloc, NULL, NULL)) { + + ast_log(LOG_ERROR, "Failed to register SIP %s object with sorcery\n", + SIP_SORCERY_SECURITY_TYPE); + return -1; + } + + ast_sorcery_object_field_register(sorcery, SIP_SORCERY_SECURITY_TYPE, "type", "", OPT_NOOP_T, 0, 0); + ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_SECURITY_TYPE, "permit", "", acl_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_SECURITY_TYPE, "deny", "", acl_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_SECURITY_TYPE, "acl", "", acl_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_SECURITY_TYPE, "contactpermit", "", acl_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_SECURITY_TYPE, "contactdeny", "", acl_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_SECURITY_TYPE, "contactacl", "", acl_handler, NULL, 0, 0); + return 0; +} diff --git a/res/res_pjsip/config_system.c b/res/res_pjsip/config_system.c new file mode 100644 index 000000000..b1f13898b --- /dev/null +++ b/res/res_pjsip/config_system.c @@ -0,0 +1,112 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Mark Michelson <mmichelson@digium.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 <pjsip.h> +#include <pjlib.h> + +#include "asterisk/res_pjsip.h" +#include "asterisk/sorcery.h" +#include "include/res_pjsip_private.h" + +#define TIMER_T1_MIN 100 +#define DEFAULT_TIMER_T1 500 +#define DEFAULT_TIMER_B 32000 + +struct system_config { + SORCERY_OBJECT(details); + /*! Transaction Timer T1 value */ + unsigned int timert1; + /*! Transaction Timer B value */ + unsigned int timerb; + /*! Should we use short forms for headers? */ + unsigned int compactheaders; +}; + +static struct ast_sorcery *system_sorcery; + +static void *system_alloc(const char *name) +{ + struct system_config *system = ast_sorcery_generic_alloc(sizeof(*system), NULL); + + if (!system) { + return NULL; + } + + return system; +} + +static int system_apply(const struct ast_sorcery *system_sorcery, void *obj) +{ + struct system_config *system = obj; + int min_timerb; + + if (system->timert1 < TIMER_T1_MIN) { + ast_log(LOG_WARNING, "Timer T1 setting is too low. Setting to %d\n", TIMER_T1_MIN); + system->timert1 = TIMER_T1_MIN; + } + + min_timerb = 64 * system->timert1; + + if (system->timerb < min_timerb) { + ast_log(LOG_WARNING, "Timer B setting is too low. Setting to %d\n", min_timerb); + system->timerb = min_timerb; + } + + pjsip_cfg()->tsx.t1 = system->timert1; + pjsip_cfg()->tsx.td = system->timerb; + + if (system->compactheaders) { + extern pj_bool_t pjsip_use_compact_form; + pjsip_use_compact_form = PJ_TRUE; + } + + return 0; +} + +int ast_sip_initialize_system(void) +{ + system_sorcery = ast_sorcery_open(); + if (!system_sorcery) { + ast_log(LOG_ERROR, "Failed to open SIP system sorcery\n"); + return -1; + } + + ast_sorcery_apply_config(system_sorcery, "res_pjsip"); + + ast_sorcery_apply_default(system_sorcery, "system", "config", "pjsip.conf,criteria=type=system"); + + if (ast_sorcery_object_register(system_sorcery, "system", system_alloc, NULL, system_apply)) { + ast_sorcery_unref(system_sorcery); + system_sorcery = NULL; + return -1; + } + + ast_sorcery_object_field_register(system_sorcery, "system", "type", "", OPT_NOOP_T, 0, 0); + ast_sorcery_object_field_register(system_sorcery, "system", "timert1", __stringify(DEFAULT_TIMER_T1), + OPT_UINT_T, 0, FLDSET(struct system_config, timert1)); + ast_sorcery_object_field_register(system_sorcery, "system", "timerb", __stringify(DEFAULT_TIMER_B), + OPT_UINT_T, 0, FLDSET(struct system_config, timerb)); + ast_sorcery_object_field_register(system_sorcery, "system", "compactheaders", "no", + OPT_BOOL_T, 1, FLDSET(struct system_config, compactheaders)); + + ast_sorcery_load(system_sorcery); + + return 0; +} diff --git a/res/res_pjsip/config_transport.c b/res/res_pjsip/config_transport.c new file mode 100644 index 000000000..82a995cb6 --- /dev/null +++ b/res/res_pjsip/config_transport.c @@ -0,0 +1,338 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Joshua Colp <jcolp@digium.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 <pjsip.h> +#include <pjlib.h> + +#include "asterisk/res_pjsip.h" +#include "asterisk/logger.h" +#include "asterisk/astobj2.h" +#include "asterisk/sorcery.h" +#include "asterisk/acl.h" + +static int destroy_transport_state(void *data) +{ + pjsip_transport *transport = data; + pjsip_transport_shutdown(transport); + return 0; +} + +/*! \brief Destructor for transport state information */ +static void transport_state_destroy(void *obj) +{ + struct ast_sip_transport_state *state = obj; + + if (state->transport) { + ast_sip_push_task_synchronous(NULL, destroy_transport_state, state->transport); + } +} + +/*! \brief Destructor for transport */ +static void transport_destroy(void *obj) +{ + struct ast_sip_transport *transport = obj; + + ast_string_field_free_memory(transport); + ast_free_ha(transport->localnet); + + if (transport->external_address_refresher) { + ast_dnsmgr_release(transport->external_address_refresher); + } + + ao2_cleanup(transport->state); +} + +/*! \brief Allocator for transport */ +static void *transport_alloc(const char *name) +{ + struct ast_sip_transport *transport = ast_sorcery_generic_alloc(sizeof(*transport), transport_destroy); + + if (!transport) { + return NULL; + } + + if (ast_string_field_init(transport, 256)) { + ao2_cleanup(transport); + return NULL; + } + + pjsip_tls_setting_default(&transport->tls); + transport->tls.ciphers = transport->ciphers; + + return transport; +} + +static void set_qos(struct ast_sip_transport *transport, pj_qos_params *qos) +{ + if (transport->tos) { + qos->flags |= PJ_QOS_PARAM_HAS_DSCP; + qos->dscp_val = transport->tos; + } + if (transport->cos) { + qos->flags |= PJ_QOS_PARAM_HAS_SO_PRIO; + qos->so_prio = transport->cos; + } +} + +/*! \brief Apply handler for transports */ +static int transport_apply(const struct ast_sorcery *sorcery, void *obj) +{ + struct ast_sip_transport *transport = obj; + RAII_VAR(struct ast_sip_transport *, existing, ast_sorcery_retrieve_by_id(sorcery, "transport", ast_sorcery_object_get_id(obj)), ao2_cleanup); + pj_status_t res = -1; + + if (!existing || !existing->state) { + if (!(transport->state = ao2_alloc(sizeof(*transport->state), transport_state_destroy))) { + ast_log(LOG_ERROR, "Transport state for '%s' could not be allocated\n", ast_sorcery_object_get_id(obj)); + return -1; + } + } else { + transport->state = existing->state; + ao2_ref(transport->state, +1); + } + + /* Once active a transport can not be reconfigured */ + if (transport->state->transport || transport->state->factory) { + return -1; + } + + if (transport->host.addr.sa_family != PJ_AF_INET && transport->host.addr.sa_family != PJ_AF_INET6) { + ast_log(LOG_ERROR, "Transport '%s' could not be started as binding not specified\n", ast_sorcery_object_get_id(obj)); + return -1; + } + + /* Set default port if not present */ + if (!pj_sockaddr_get_port(&transport->host)) { + pj_sockaddr_set_port(&transport->host, (transport->type == AST_TRANSPORT_TLS) ? 5061 : 5060); + } + + /* Now that we know what address family we can set up a dnsmgr refresh for the external media address if present */ + if (!ast_strlen_zero(transport->external_signaling_address)) { + if (transport->host.addr.sa_family == pj_AF_INET()) { + transport->external_address.ss.ss_family = AF_INET; + } else if (transport->host.addr.sa_family == pj_AF_INET6()) { + transport->external_address.ss.ss_family = AF_INET6; + } else { + ast_log(LOG_ERROR, "Unknown address family for transport '%s', could not get external signaling address\n", + ast_sorcery_object_get_id(obj)); + return -1; + } + + if (ast_dnsmgr_lookup(transport->external_signaling_address, &transport->external_address, &transport->external_address_refresher, NULL) < 0) { + ast_log(LOG_ERROR, "Could not create dnsmgr for external signaling address on '%s'\n", ast_sorcery_object_get_id(obj)); + return -1; + } + } + + if (transport->type == AST_TRANSPORT_UDP) { + if (transport->host.addr.sa_family == pj_AF_INET()) { + res = pjsip_udp_transport_start(ast_sip_get_pjsip_endpoint(), &transport->host.ipv4, NULL, transport->async_operations, &transport->state->transport); + } else if (transport->host.addr.sa_family == pj_AF_INET6()) { + res = pjsip_udp_transport_start6(ast_sip_get_pjsip_endpoint(), &transport->host.ipv6, NULL, transport->async_operations, &transport->state->transport); + } + + if (res == PJ_SUCCESS && (transport->tos || transport->cos)) { + pj_sock_t sock; + pj_qos_params qos_params; + + sock = pjsip_udp_transport_get_socket(transport->state->transport); + pj_sock_get_qos_params(sock, &qos_params); + set_qos(transport, &qos_params); + pj_sock_set_qos_params(sock, &qos_params); + } + } else if (transport->type == AST_TRANSPORT_TCP) { + pjsip_tcp_transport_cfg cfg; + + pjsip_tcp_transport_cfg_default(&cfg, transport->host.addr.sa_family); + cfg.bind_addr = transport->host; + cfg.async_cnt = transport->async_operations; + set_qos(transport, &cfg.qos_params); + + res = pjsip_tcp_transport_start3(ast_sip_get_pjsip_endpoint(), &cfg, &transport->state->factory); + } else if (transport->type == AST_TRANSPORT_TLS) { + transport->tls.ca_list_file = pj_str((char*)transport->ca_list_file); + transport->tls.cert_file = pj_str((char*)transport->cert_file); + transport->tls.privkey_file = pj_str((char*)transport->privkey_file); + transport->tls.password = pj_str((char*)transport->password); + set_qos(transport, &transport->tls.qos_params); + + res = pjsip_tls_transport_start2(ast_sip_get_pjsip_endpoint(), &transport->tls, &transport->host, NULL, transport->async_operations, &transport->state->factory); + } else if ((transport->type == AST_TRANSPORT_WS) || (transport->type == AST_TRANSPORT_WSS)) { + if (transport->cos || transport->tos) { + ast_log(LOG_WARNING, "TOS and COS values ignored for websocket transport\n"); + } + res = PJ_SUCCESS; + } + + if (res != PJ_SUCCESS) { + char msg[PJ_ERR_MSG_SIZE]; + + pjsip_strerror(res, msg, sizeof(msg)); + ast_log(LOG_ERROR, "Transport '%s' could not be started: %s\n", ast_sorcery_object_get_id(obj), msg); + return -1; + } + return 0; +} + +/*! \brief Custom handler for turning a string protocol into an enum */ +static int transport_protocol_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_transport *transport = obj; + + if (!strcasecmp(var->value, "udp")) { + transport->type = AST_TRANSPORT_UDP; + } else if (!strcasecmp(var->value, "tcp")) { + transport->type = AST_TRANSPORT_TCP; + } else if (!strcasecmp(var->value, "tls")) { + transport->type = AST_TRANSPORT_TLS; + } else if (!strcasecmp(var->value, "ws")) { + transport->type = AST_TRANSPORT_WS; + } else if (!strcasecmp(var->value, "wss")) { + transport->type = AST_TRANSPORT_WSS; + } else { + return -1; + } + + return 0; +} + +/*! \brief Custom handler for turning a string bind into a pj_sockaddr */ +static int transport_bind_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_transport *transport = obj; + pj_str_t buf; + + return (pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&buf, var->value), &transport->host) != PJ_SUCCESS) ? -1 : 0; +} + +/*! \brief Custom handler for TLS boolean settings */ +static int transport_tls_bool_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_transport *transport = obj; + + if (!strcasecmp(var->name, "verify_server")) { + transport->tls.verify_server = ast_true(var->value) ? PJ_TRUE : PJ_FALSE; + } else if (!strcasecmp(var->name, "verify_client")) { + transport->tls.verify_client = ast_true(var->value) ? PJ_TRUE : PJ_FALSE; + } else if (!strcasecmp(var->name, "require_client_cert")) { + transport->tls.require_client_cert = ast_true(var->value) ? PJ_TRUE : PJ_FALSE; + } else { + return -1; + } + + return 0; +} + +/*! \brief Custom handler for TLS method setting */ +static int transport_tls_method_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_transport *transport = obj; + + if (!strcasecmp(var->value, "default")) { + transport->tls.method = PJSIP_SSL_DEFAULT_METHOD; + } else if (!strcasecmp(var->value, "unspecified")) { + transport->tls.method = PJSIP_SSL_UNSPECIFIED_METHOD; + } else if (!strcasecmp(var->value, "tlsv1")) { + transport->tls.method = PJSIP_TLSV1_METHOD; + } else if (!strcasecmp(var->value, "sslv2")) { + transport->tls.method = PJSIP_SSLV2_METHOD; + } else if (!strcasecmp(var->value, "sslv3")) { + transport->tls.method = PJSIP_SSLV3_METHOD; + } else if (!strcasecmp(var->value, "sslv23")) { + transport->tls.method = PJSIP_SSLV23_METHOD; + } else { + return -1; + } + + return 0; +} + +/*! \brief Custom handler for TLS cipher setting */ +static int transport_tls_cipher_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_transport *transport = obj; + pj_ssl_cipher cipher; + + if (transport->tls.ciphers_num == (SIP_TLS_MAX_CIPHERS - 1)) { + return -1; + } + + /* TODO: Check this over/tweak - it's taken from pjsua for now */ + if (!strnicmp(var->value, "0x", 2)) { + pj_str_t cipher_st = pj_str((char*)var->value + 2); + cipher = pj_strtoul2(&cipher_st, NULL, 16); + } else { + cipher = atoi(var->value); + } + + if (pj_ssl_cipher_is_supported(cipher)) { + transport->ciphers[transport->tls.ciphers_num++] = cipher; + return 0; + } else { + ast_log(LOG_ERROR, "Cipher '%s' is unsupported\n", var->value); + return -1; + } +} + +/*! \brief Custom handler for localnet setting */ +static int transport_localnet_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_transport *transport = obj; + int error = 0; + + if (!(transport->localnet = ast_append_ha("d", var->value, transport->localnet, &error))) { + return -1; + } + + return error; +} + +/*! \brief Initialize sorcery with transport support */ +int ast_sip_initialize_sorcery_transport(struct ast_sorcery *sorcery) +{ + ast_sorcery_apply_default(sorcery, "transport", "config", "pjsip.conf,criteria=type=transport"); + + if (ast_sorcery_object_register(sorcery, "transport", transport_alloc, NULL, transport_apply)) { + return -1; + } + + ast_sorcery_object_field_register(sorcery, "transport", "type", "", OPT_NOOP_T, 0, 0); + ast_sorcery_object_field_register_custom(sorcery, "transport", "protocol", "udp", transport_protocol_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sorcery, "transport", "bind", "", transport_bind_handler, NULL, 0, 0); + ast_sorcery_object_field_register(sorcery, "transport", "async_operations", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, async_operations)); + ast_sorcery_object_field_register(sorcery, "transport", "ca_list_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, ca_list_file)); + ast_sorcery_object_field_register(sorcery, "transport", "cert_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, cert_file)); + ast_sorcery_object_field_register(sorcery, "transport", "privkey_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, privkey_file)); + ast_sorcery_object_field_register(sorcery, "transport", "password", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, password)); + ast_sorcery_object_field_register(sorcery, "transport", "external_signaling_address", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, external_signaling_address)); + ast_sorcery_object_field_register(sorcery, "transport", "external_signaling_port", "0", OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_transport, external_signaling_port), 0, 65535); + ast_sorcery_object_field_register(sorcery, "transport", "external_media_address", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, external_media_address)); + ast_sorcery_object_field_register(sorcery, "transport", "domain", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, domain)); + ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_server", "", transport_tls_bool_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_client", "", transport_tls_bool_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sorcery, "transport", "require_client_cert", "", transport_tls_bool_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sorcery, "transport", "method", "", transport_tls_method_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sorcery, "transport", "cipher", "", transport_tls_cipher_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sorcery, "transport", "localnet", "", transport_localnet_handler, NULL, 0, 0); + ast_sorcery_object_field_register(sorcery, "transport", "tos", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, tos)); + ast_sorcery_object_field_register(sorcery, "transport", "cos", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, cos)); + + return 0; +} diff --git a/res/res_pjsip/include/res_pjsip_private.h b/res/res_pjsip/include/res_pjsip_private.h new file mode 100644 index 000000000..ee9cc06ef --- /dev/null +++ b/res/res_pjsip/include/res_pjsip_private.h @@ -0,0 +1,74 @@ +/* + * res_pjsip.h + * + * Created on: Jan 25, 2013 + * Author: mjordan + */ + +#ifndef RES_SIP_PRIVATE_H_ +#define RES_SIP_PRIVATE_H_ + +struct ao2_container; + +/*! + * \brief Initialize the configuration for res_pjsip + */ +int ast_res_pjsip_initialize_configuration(void); + +/*! + * \brief Annihilate the configuration objects + */ +void ast_res_pjsip_destroy_configuration(void); + +/*! + * \brief Reload the configuration + */ +int ast_res_pjsip_reload_configuration(void); + +/*! + * \brief Initialize OPTIONS request handling. + * + * XXX This currently includes qualifying peers. It shouldn't. + * That should go into a registrar. When that occurs, we won't + * need the reload stuff. + * + * \param reload Reload options handling + * + * \retval 0 on success + * \retval other on failure + */ +int ast_res_pjsip_init_options_handling(int reload); + +/*! + * \brief Initialize transport storage for contacts. + * + * \retval 0 on success + * \retval other on failure + */ +int ast_res_pjsip_init_contact_transports(void); + +/*! + * \brief Initialize outbound authentication support + * + * \retval 0 Success + * \retval non-zero Failure + */ +int ast_sip_initialize_outbound_authentication(void); + +/*! + * \brief Initialize system configuration + * + * \retval 0 Success + * \retval non-zero Failure + */ +int ast_sip_initialize_system(void); + +/*! + * \brief Initialize global configuration + * + * \retval 0 Success + * \retval non-zero Failure + */ +int ast_sip_initialize_global(void); + +#endif /* RES_SIP_PRIVATE_H_ */ diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c new file mode 100644 index 000000000..4ba945fc9 --- /dev/null +++ b/res/res_pjsip/location.c @@ -0,0 +1,328 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Joshua Colp <jcolp@digium.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 "pjsip.h" +#include "pjlib.h" + +#include "asterisk/res_pjsip.h" +#include "asterisk/logger.h" +#include "asterisk/astobj2.h" +#include "asterisk/sorcery.h" +#include "include/res_pjsip_private.h" + +#define CONTACT_TRANSPORTS_BUCKETS 7 +static struct ao2_container *contact_transports; + +/*! \brief Destructor for AOR */ +static void aor_destroy(void *obj) +{ + struct ast_sip_aor *aor = obj; + + ao2_cleanup(aor->permanent_contacts); + ast_string_field_free_memory(aor); +} + +/*! \brief Allocator for AOR */ +static void *aor_alloc(const char *name) +{ + struct ast_sip_aor *aor = ast_sorcery_generic_alloc(sizeof(struct ast_sip_aor), aor_destroy); + if (!aor) { + return NULL; + } + ast_string_field_init(aor, 128); + return aor; +} + +/*! \brief Destructor for contact */ +static void contact_destroy(void *obj) +{ + struct ast_sip_contact *contact = obj; + + ast_string_field_free_memory(contact); +} + +/*! \brief Allocator for contact */ +static void *contact_alloc(const char *name) +{ + struct ast_sip_contact *contact = ast_sorcery_generic_alloc(sizeof(*contact), contact_destroy); + + if (!contact) { + return NULL; + } + + if (ast_string_field_init(contact, 256)) { + ao2_cleanup(contact); + return NULL; + } + + return contact; +} + +/*! \brief Callback function for finding a contact_transport by URI */ +static int contact_transport_find_by_uri(void *obj, void *arg, int flags) +{ + struct ast_sip_contact_transport *ct = obj; + const char *contact_uri = arg; + + return (!strcmp(ct->uri, contact_uri)) ? CMP_MATCH | CMP_STOP : 0; +} + +/*! \brief Callback function for finding a contact_transport by transport */ +static int contact_transport_find_by_transport(void *obj, void *arg, int flags) +{ + struct ast_sip_contact_transport *ct = obj; + pjsip_transport *transport = arg; + + return (ct->transport == transport) ? CMP_MATCH | CMP_STOP : 0; +} + +void ast_sip_location_add_contact_transport(struct ast_sip_contact_transport *ct) +{ + ao2_link(contact_transports, ct); + + return; +} + +void ast_sip_location_delete_contact_transport(struct ast_sip_contact_transport *ct) +{ + ao2_unlink(contact_transports, ct); + + return; +} + +struct ast_sip_contact_transport *ast_sip_location_retrieve_contact_transport_by_uri(const char *contact_uri) +{ + return ao2_callback(contact_transports, 0, contact_transport_find_by_uri, (void *)contact_uri); +} + +struct ast_sip_contact_transport *ast_sip_location_retrieve_contact_transport_by_transport(pjsip_transport *transport) +{ + return ao2_callback(contact_transports, 0, contact_transport_find_by_transport, transport); +} + +struct ast_sip_aor *ast_sip_location_retrieve_aor(const char *aor_name) +{ + return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "aor", aor_name); +} + +/*! \brief Internal callback function which deletes and unlinks any expired contacts */ +static int contact_expire(void *obj, void *arg, int flags) +{ + struct ast_sip_contact *contact = obj; + + /* If the contact has not yet expired it is valid */ + if (ast_tvdiff_ms(contact->expiration_time, ast_tvnow()) > 0) { + return 0; + } + + ast_sip_location_delete_contact(contact); + + return CMP_MATCH; +} + +/*! \brief Internal callback function which links static contacts into another container */ +static int contact_link_static(void *obj, void *arg, int flags) +{ + struct ao2_container *dest = arg; + + ao2_link_flags(dest, obj, OBJ_NOLOCK); + return 0; +} + +/*! \brief Simple callback function which returns immediately, used to grab the first contact of an AOR */ +static int contact_find_first(void *obj, void *arg, int flags) +{ + return CMP_MATCH | CMP_STOP; +} + +struct ast_sip_contact *ast_sip_location_retrieve_first_aor_contact(const struct ast_sip_aor *aor) +{ + RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup); + struct ast_sip_contact *contact; + + contacts = ast_sip_location_retrieve_aor_contacts(aor); + if (!contacts || (ao2_container_count(contacts) == 0)) { + return NULL; + } + + contact = ao2_callback(contacts, OBJ_NOLOCK, contact_find_first, NULL); + return contact; +} + +struct ao2_container *ast_sip_location_retrieve_aor_contacts(const struct ast_sip_aor *aor) +{ + /* Give enough space for ^ at the beginning and ;@ at the end, since that is our object naming scheme */ + char regex[strlen(ast_sorcery_object_get_id(aor)) + 4]; + struct ao2_container *contacts; + + snprintf(regex, sizeof(regex), "^%s;@", ast_sorcery_object_get_id(aor)); + + if (!(contacts = ast_sorcery_retrieve_by_regex(ast_sip_get_sorcery(), "contact", regex))) { + return NULL; + } + + /* Prune any expired contacts and delete them, we do this first because static contacts can never expire */ + ao2_callback(contacts, OBJ_NOLOCK | OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, contact_expire, NULL); + + /* Add any permanent contacts from the AOR */ + if (aor->permanent_contacts) { + ao2_callback(aor->permanent_contacts, OBJ_NOLOCK | OBJ_NODATA, contact_link_static, contacts); + } + + return contacts; +} + +struct ast_sip_contact *ast_sip_location_retrieve_contact_from_aor_list(const char *aor_list) +{ + char *aor_name; + char *rest; + struct ast_sip_contact *contact = NULL; + + /* If the location is still empty we have nowhere to go */ + if (ast_strlen_zero(aor_list) || !(rest = ast_strdupa(aor_list))) { + ast_log(LOG_WARNING, "Unable to determine contacts from empty aor list\n"); + return NULL; + } + + while ((aor_name = strsep(&rest, ","))) { + RAII_VAR(struct ast_sip_aor *, aor, ast_sip_location_retrieve_aor(aor_name), ao2_cleanup); + RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup); + + if (!aor) { + continue; + } + contact = ast_sip_location_retrieve_first_aor_contact(aor); + /* If a valid contact is available use its URI for dialing */ + if (contact) { + break; + } + } + + return contact; +} + +struct ast_sip_contact *ast_sip_location_retrieve_contact(const char *contact_name) +{ + return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "contact", contact_name); +} + +int ast_sip_location_add_contact(struct ast_sip_aor *aor, const char *uri, struct timeval expiration_time) +{ + char name[AST_UUID_STR_LEN]; + RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup); + + snprintf(name, sizeof(name), "%s;@%s", ast_sorcery_object_get_id(aor), uri); + + if (!(contact = ast_sorcery_alloc(ast_sip_get_sorcery(), "contact", name))) { + return -1; + } + + ast_string_field_set(contact, uri, uri); + contact->expiration_time = expiration_time; + contact->qualify_frequency = aor->qualify_frequency; + contact->authenticate_qualify = aor->authenticate_qualify; + + return ast_sorcery_create(ast_sip_get_sorcery(), contact); +} + +int ast_sip_location_update_contact(struct ast_sip_contact *contact) +{ + return ast_sorcery_update(ast_sip_get_sorcery(), contact); +} + +int ast_sip_location_delete_contact(struct ast_sip_contact *contact) +{ + return ast_sorcery_delete(ast_sip_get_sorcery(), contact); +} + +/*! \brief Custom handler for translating from a string timeval to actual structure */ +static int expiration_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_contact *contact = obj; + return ast_get_timeval(var->value, &contact->expiration_time, ast_tv(0, 0), NULL); +} + +/*! \brief Custom handler for translating from an actual structure timeval to string */ +static int expiration_struct2str(const void *obj, const intptr_t *args, char **buf) +{ + const struct ast_sip_contact *contact = obj; + return (ast_asprintf(buf, "%lu", contact->expiration_time.tv_sec) < 0) ? -1 : 0; +} + +/*! \brief Custom handler for permanent URIs */ +static int permanent_uri_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_aor *aor = obj; + RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup); + + if ((!aor->permanent_contacts && !(aor->permanent_contacts = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 1, NULL, NULL))) || + !(contact = ast_sorcery_alloc(ast_sip_get_sorcery(), "contact", NULL))) { + return -1; + } + + ast_string_field_set(contact, uri, var->value); + ao2_link_flags(aor->permanent_contacts, contact, OBJ_NOLOCK); + + return 0; +} + +/*! \brief Initialize sorcery with location support */ +int ast_sip_initialize_sorcery_location(struct ast_sorcery *sorcery) +{ + ast_sorcery_apply_default(sorcery, "contact", "astdb", "registrar"); + ast_sorcery_apply_default(sorcery, "aor", "config", "pjsip.conf,criteria=type=aor"); + + if (ast_sorcery_object_register(sorcery, "contact", contact_alloc, NULL, NULL) || + ast_sorcery_object_register(sorcery, "aor", aor_alloc, NULL, NULL)) { + return -1; + } + + ast_sorcery_object_field_register(sorcery, "contact", "type", "", OPT_NOOP_T, 0, 0); + ast_sorcery_object_field_register(sorcery, "contact", "uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, uri)); + ast_sorcery_object_field_register_custom(sorcery, "contact", "expiration_time", "", expiration_str2struct, expiration_struct2str, 0, 0); + ast_sorcery_object_field_register(sorcery, "contact", "qualify_frequency", 0, OPT_UINT_T, + PARSE_IN_RANGE, FLDSET(struct ast_sip_contact, qualify_frequency), 0, 86400); + + ast_sorcery_object_field_register(sorcery, "aor", "type", "", OPT_NOOP_T, 0, 0); + ast_sorcery_object_field_register(sorcery, "aor", "minimum_expiration", "60", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, minimum_expiration)); + ast_sorcery_object_field_register(sorcery, "aor", "maximum_expiration", "7200", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, maximum_expiration)); + ast_sorcery_object_field_register(sorcery, "aor", "default_expiration", "3600", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, default_expiration)); + ast_sorcery_object_field_register(sorcery, "aor", "qualify_frequency", 0, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_aor, qualify_frequency), 0, 86400); + ast_sorcery_object_field_register(sorcery, "aor", "authenticate_qualify", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, authenticate_qualify)); + ast_sorcery_object_field_register(sorcery, "aor", "max_contacts", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, max_contacts)); + ast_sorcery_object_field_register(sorcery, "aor", "remove_existing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, remove_existing)); + ast_sorcery_object_field_register_custom(sorcery, "aor", "contact", "", permanent_uri_handler, NULL, 0, 0); + ast_sorcery_object_field_register(sorcery, "aor", "mailboxes", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_aor, mailboxes)); + + return 0; +} + +int ast_res_pjsip_init_contact_transports(void) +{ + if (contact_transports) { + ao2_t_ref(contact_transports, -1, "Remove old contact transports"); + } + + contact_transports = ao2_t_container_alloc_options(AO2_ALLOC_OPT_LOCK_RWLOCK, CONTACT_TRANSPORTS_BUCKETS, NULL, NULL, "Create container for contact transports"); + if (!contact_transports) { + return -1; + } + + return 0; +} diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c new file mode 100644 index 000000000..4d703e54b --- /dev/null +++ b/res/res_pjsip/pjsip_configuration.c @@ -0,0 +1,892 @@ +/* + * sip_cli_commands.c + * + * Created on: Jan 25, 2013 + * Author: mjordan + */ + +#include "asterisk.h" + +#include <pjsip.h> +#include <pjsip_ua.h> + +#include "asterisk/res_pjsip.h" +#include "include/res_pjsip_private.h" +#include "asterisk/cli.h" +#include "asterisk/astobj2.h" +#include "asterisk/utils.h" +#include "asterisk/sorcery.h" +#include "asterisk/callerid.h" +#include "asterisk/stasis_endpoints.h" + +/*! \brief Number of buckets for persistent endpoint information */ +#define PERSISTENT_BUCKETS 53 + +/*! \brief Persistent endpoint information */ +struct sip_persistent_endpoint { + /*! \brief Asterisk endpoint itself */ + struct ast_endpoint *endpoint; + /*! \brief AORs that we should react to */ + char *aors; +}; + +/*! \brief Container for persistent endpoint information */ +static struct ao2_container *persistent_endpoints; + +static struct ast_sorcery *sip_sorcery; + +/*! \brief Hashing function for persistent endpoint information */ +static int persistent_endpoint_hash(const void *obj, const int flags) +{ + const struct sip_persistent_endpoint *persistent = obj; + const char *id = (flags & OBJ_KEY ? obj : ast_endpoint_get_resource(persistent->endpoint)); + + return ast_str_hash(id); +} + +/*! \brief Comparison function for persistent endpoint information */ +static int persistent_endpoint_cmp(void *obj, void *arg, int flags) +{ + const struct sip_persistent_endpoint *persistent1 = obj; + const struct sip_persistent_endpoint *persistent2 = arg; + const char *id = (flags & OBJ_KEY ? arg : ast_endpoint_get_resource(persistent2->endpoint)); + + return !strcmp(ast_endpoint_get_resource(persistent1->endpoint), id) ? CMP_MATCH | CMP_STOP : 0; +} + +/*! \brief Callback function for changing the state of an endpoint */ +static int persistent_endpoint_update_state(void *obj, void *arg, int flags) +{ + struct sip_persistent_endpoint *persistent = obj; + char *aor = arg; + RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup); + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + + if (!ast_strlen_zero(aor) && !strstr(persistent->aors, aor)) { + return 0; + } + + if ((contact = ast_sip_location_retrieve_contact_from_aor_list(persistent->aors))) { + ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_ONLINE); + blob = ast_json_pack("{s: s}", "peer_status", "Reachable"); + } else { + ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_OFFLINE); + blob = ast_json_pack("{s: s}", "peer_status", "Unreachable"); + } + + ast_endpoint_blob_publish(persistent->endpoint, ast_endpoint_state_type(), blob); + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_endpoint_get_resource(persistent->endpoint)); + + return 0; +} + +/*! \brief Function called when stuff relating to a contact happens (created/deleted) */ +static void persistent_endpoint_contact_observer(const void *object) +{ + char *id = ast_strdupa(ast_sorcery_object_get_id(object)), *aor = NULL; + + aor = strsep(&id, ";@"); + + ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, aor); +} + +/*! \brief Observer for contacts so state can be updated on respective endpoints */ +static struct ast_sorcery_observer state_contact_observer = { + .created = persistent_endpoint_contact_observer, + .deleted = persistent_endpoint_contact_observer, +}; + +static char *handle_cli_show_endpoints(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup); + struct ao2_iterator it_endpoints; + struct ast_sip_endpoint *endpoint; + + switch (cmd) { + case CLI_INIT: + e->command = "pjsip show endpoints"; + e->usage = + "Usage: pjsip show endpoints\n" + " Show the registered PJSIP endpoints\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + endpoints = ast_sip_get_endpoints(); + if (!endpoints) { + return CLI_FAILURE; + } + + if (!ao2_container_count(endpoints)) { + ast_cli(a->fd, "No endpoints found\n"); + return CLI_SUCCESS; + } + + ast_cli(a->fd, "Endpoints:\n"); + it_endpoints = ao2_iterator_init(endpoints, 0); + while ((endpoint = ao2_iterator_next(&it_endpoints))) { + ast_cli(a->fd, "%s\n", ast_sorcery_object_get_id(endpoint)); + ao2_ref(endpoint, -1); + } + ao2_iterator_destroy(&it_endpoints); + return CLI_SUCCESS; +} + +static int show_contact(void *obj, void *arg, int flags) +{ + struct ast_sip_contact *contact = obj; + struct ast_cli_args *a = arg; + RAII_VAR(struct ast_sip_contact_status *, status, ast_sorcery_retrieve_by_id( + ast_sip_get_sorcery(), CONTACT_STATUS, + ast_sorcery_object_get_id(contact)), ao2_cleanup); + + ast_cli(a->fd, "\tContact %s:\n", contact->uri); + + if (!status) { + ast_cli(a->fd, "\tStatus not found!\n"); + return 0; + } + + ast_cli(a->fd, "\t\tavailable = %s\n", status->status ? "yes" : "no"); + + if (status->status) { + ast_cli(a->fd, "\t\tRTT = %lld microseconds\n", (long long)status->rtt); + } + + return 0; +} + +static void show_endpoint(struct ast_sip_endpoint *endpoint, struct ast_cli_args *a) +{ + char *aor_name, *aors; + + if (ast_strlen_zero(endpoint->aors)) { + return; + } + + aors = ast_strdupa(endpoint->aors); + + while ((aor_name = strsep(&aors, ","))) { + RAII_VAR(struct ast_sip_aor *, aor, + ast_sip_location_retrieve_aor(aor_name), ao2_cleanup); + RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup); + + if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) { + continue; + } + + ast_cli(a->fd, "AOR %s:\n", ast_sorcery_object_get_id(aor)); + ao2_callback(contacts, OBJ_NODATA, show_contact, a); + } + + return; +} + +static char *cli_show_endpoint(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); + const char *endpoint_name; + + switch (cmd) { + case CLI_INIT: + e->command = "pjsip show endpoint"; + e->usage = + "Usage: pjsip show endpoint <endpoint>\n" + " Show the given PJSIP endpoint.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 4) { + return CLI_SHOWUSAGE; + } + + endpoint_name = a->argv[3]; + + if (!(endpoint = ast_sorcery_retrieve_by_id( + ast_sip_get_sorcery(), "endpoint", endpoint_name))) { + ast_cli(a->fd, "Unable to retrieve endpoint %s\n", endpoint_name); + return CLI_FAILURE; + } + + ast_cli(a->fd, "Endpoint %s:\n", endpoint_name); + show_endpoint(endpoint, a); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_commands[] = { + AST_CLI_DEFINE(handle_cli_show_endpoints, "Show PJSIP Endpoints"), + AST_CLI_DEFINE(cli_show_endpoint, "Show PJSIP Endpoint") +}; + +static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + if (!strcasecmp(var->value, "rfc4733")) { + endpoint->dtmf = AST_SIP_DTMF_RFC_4733; + } else if (!strcasecmp(var->value, "inband")) { + endpoint->dtmf = AST_SIP_DTMF_INBAND; + } else if (!strcasecmp(var->value, "info")) { + endpoint->dtmf = AST_SIP_DTMF_INFO; + } else if (!strcasecmp(var->value, "none")) { + endpoint->dtmf = AST_SIP_DTMF_NONE; + } else { + return -1; + } + + return 0; +} + +static int prack_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + if (ast_true(var->value)) { + endpoint->extensions.flags |= PJSIP_INV_SUPPORT_100REL; + } else if (ast_false(var->value)) { + endpoint->extensions.flags &= PJSIP_INV_SUPPORT_100REL; + } else if (!strcasecmp(var->value, "required")) { + endpoint->extensions.flags |= PJSIP_INV_REQUIRE_100REL; + } else { + return -1; + } + + return 0; +} + +static int timers_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + if (ast_true(var->value)) { + endpoint->extensions.flags |= PJSIP_INV_SUPPORT_TIMER; + } else if (ast_false(var->value)) { + endpoint->extensions.flags &= PJSIP_INV_SUPPORT_TIMER; + } else if (!strcasecmp(var->value, "required")) { + endpoint->extensions.flags |= PJSIP_INV_REQUIRE_TIMER; + } else if (!strcasecmp(var->value, "always")) { + endpoint->extensions.flags |= PJSIP_INV_ALWAYS_USE_TIMER; + } else { + return -1; + } + + return 0; +} + +void ast_sip_auth_array_destroy(struct ast_sip_auth_array *auths) +{ + int i; + + if (!auths) { + return; + } + + for (i = 0; i < auths->num; ++i) { + ast_free((char *) auths->names[i]); + } + ast_free(auths->names); + auths->num = 0; +} + +#define AUTH_INCREMENT 4 + +int ast_sip_auth_array_init(struct ast_sip_auth_array *auths, const char *value) +{ + char *auth_names = ast_strdupa(value); + char *val; + int num_alloced = 0; + const char **alloced_auths = NULL; + + while ((val = strsep(&auth_names, ","))) { + if (auths->num >= num_alloced) { + size_t size; + num_alloced += AUTH_INCREMENT; + size = num_alloced * sizeof(char *); + auths->names = ast_realloc(alloced_auths, size); + if (!auths->names) { + goto failure; + } + } + auths->names[auths->num] = ast_strdup(val); + if (!auths->names[auths->num]) { + goto failure; + } + ++auths->num; + } + return 0; + +failure: + ast_sip_auth_array_destroy(auths); + return -1; +} + +static int inbound_auth_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + return ast_sip_auth_array_init(&endpoint->inbound_auths, var->value); +} + +static int outbound_auth_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + return ast_sip_auth_array_init(&endpoint->outbound_auths, var->value); +} + +static int ident_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + char *idents = ast_strdupa(var->value); + char *val; + + while ((val = strsep(&idents, ","))) { + if (!strcasecmp(val, "username")) { + endpoint->ident_method |= AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME; + } else { + ast_log(LOG_ERROR, "Unrecognized identification method %s specified for endpoint %s\n", + val, ast_sorcery_object_get_id(endpoint)); + return -1; + } + } + return 0; +} + +static int direct_media_method_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + if (!strcasecmp(var->value, "invite") || !strcasecmp(var->value, "reinvite")) { + endpoint->media.direct_media.method = AST_SIP_SESSION_REFRESH_METHOD_INVITE; + } else if (!strcasecmp(var->value, "update")) { + endpoint->media.direct_media.method = AST_SIP_SESSION_REFRESH_METHOD_UPDATE; + } else { + ast_log(LOG_NOTICE, "Unrecognized option value %s for %s on endpoint %s\n", + var->value, var->name, ast_sorcery_object_get_id(endpoint)); + return -1; + } + return 0; +} + +static int connected_line_method_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + if (!strcasecmp(var->value, "invite") || !strcasecmp(var->value, "reinvite")) { + endpoint->id.refresh_method = AST_SIP_SESSION_REFRESH_METHOD_INVITE; + } else if (!strcasecmp(var->value, "update")) { + endpoint->id.refresh_method = AST_SIP_SESSION_REFRESH_METHOD_UPDATE; + } else { + ast_log(LOG_NOTICE, "Unrecognized option value %s for %s on endpoint %s\n", + var->value, var->name, ast_sorcery_object_get_id(endpoint)); + return -1; + } + return 0; +} + +static int direct_media_glare_mitigation_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + if (!strcasecmp(var->value, "none")) { + endpoint->media.direct_media.glare_mitigation = AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_NONE; + } else if (!strcasecmp(var->value, "outgoing")) { + endpoint->media.direct_media.glare_mitigation = AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_OUTGOING; + } else if (!strcasecmp(var->value, "incoming")) { + endpoint->media.direct_media.glare_mitigation = AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_INCOMING; + } else { + ast_log(LOG_NOTICE, "Unrecognized option value %s for %s on endpoint %s\n", + var->value, var->name, ast_sorcery_object_get_id(endpoint)); + return -1; + } + + return 0; +} + +static int caller_id_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + char cid_name[80] = { '\0' }; + char cid_num[80] = { '\0' }; + + ast_callerid_split(var->value, cid_name, sizeof(cid_name), cid_num, sizeof(cid_num)); + if (!ast_strlen_zero(cid_name)) { + endpoint->id.self.name.str = ast_strdup(cid_name); + if (!endpoint->id.self.name.str) { + return -1; + } + endpoint->id.self.name.valid = 1; + } + if (!ast_strlen_zero(cid_num)) { + endpoint->id.self.number.str = ast_strdup(cid_num); + if (!endpoint->id.self.number.str) { + return -1; + } + endpoint->id.self.number.valid = 1; + } + return 0; +} + +static int caller_id_privacy_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + int callingpres = ast_parse_caller_presentation(var->value); + if (callingpres == -1 && sscanf(var->value, "%d", &callingpres) != 1) { + return -1; + } + endpoint->id.self.number.presentation = callingpres; + endpoint->id.self.name.presentation = callingpres; + return 0; +} + +static int caller_id_tag_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + endpoint->id.self.tag = ast_strdup(var->value); + return endpoint->id.self.tag ? 0 : -1; +} + +static int media_encryption_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + if (!strcasecmp("no", var->value)) { + endpoint->media.rtp.encryption = AST_SIP_MEDIA_ENCRYPT_NONE; + } else if (!strcasecmp("sdes", var->value)) { + endpoint->media.rtp.encryption = AST_SIP_MEDIA_ENCRYPT_SDES; + } else if (!strcasecmp("dtls", var->value)) { + endpoint->media.rtp.encryption = AST_SIP_MEDIA_ENCRYPT_DTLS; + ast_rtp_dtls_cfg_parse(&endpoint->media.rtp.dtls_cfg, "dtlsenable", "yes"); + } else { + return -1; + } + + return 0; +} + +static int group_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + if (!strncmp(var->name, "callgroup", 9)) { + if (!(endpoint->pickup.callgroup = ast_get_group(var->value))) { + return -1; + } + } else if (!strncmp(var->name, "pickupgroup", 11)) { + if (!(endpoint->pickup.pickupgroup = ast_get_group(var->value))) { + return -1; + } + } else { + return -1; + } + + return 0; +} + +static int named_groups_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + if (!strncmp(var->name, "namedcallgroup", 14)) { + if (!(endpoint->pickup.named_callgroups = + ast_get_namedgroups(var->value))) { + return -1; + } + } else if (!strncmp(var->name, "namedpickupgroup", 16)) { + if (!(endpoint->pickup.named_pickupgroups = + ast_get_namedgroups(var->value))) { + return -1; + } + } else { + return -1; + } + + return 0; +} + +static int dtls_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + return ast_rtp_dtls_cfg_parse(&endpoint->media.rtp.dtls_cfg, var->name, var->value); +} + +static int t38udptl_ec_handler(const struct aco_option *opt, + struct ast_variable *var, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + if (!strcmp(var->value, "none")) { + endpoint->media.t38.error_correction = UDPTL_ERROR_CORRECTION_NONE; + } else if (!strcmp(var->value, "fec")) { + endpoint->media.t38.error_correction = UDPTL_ERROR_CORRECTION_FEC; + } else if (!strcmp(var->value, "redundancy")) { + endpoint->media.t38.error_correction = UDPTL_ERROR_CORRECTION_REDUNDANCY; + } else { + return -1; + } + + return 0; +} + +static void *sip_nat_hook_alloc(const char *name) +{ + return ast_sorcery_generic_alloc(sizeof(struct ast_sip_nat_hook), NULL); +} + +/*! \brief Destructor function for persistent endpoint information */ +static void persistent_endpoint_destroy(void *obj) +{ + struct sip_persistent_endpoint *persistent = obj; + + ast_endpoint_shutdown(persistent->endpoint); + ast_free(persistent->aors); +} + +/*! \brief Internal function which finds (or creates) persistent endpoint information */ +static struct ast_endpoint *persistent_endpoint_find_or_create(const struct ast_sip_endpoint *endpoint) +{ + RAII_VAR(struct sip_persistent_endpoint *, persistent, NULL, ao2_cleanup); + SCOPED_AO2LOCK(lock, persistent_endpoints); + + if (!(persistent = ao2_find(persistent_endpoints, ast_sorcery_object_get_id(endpoint), OBJ_KEY | OBJ_NOLOCK))) { + if (!(persistent = ao2_alloc(sizeof(*persistent), persistent_endpoint_destroy))) { + return NULL; + } + + if (!(persistent->endpoint = ast_endpoint_create("PJSIP", ast_sorcery_object_get_id(endpoint)))) { + return NULL; + } + + persistent->aors = ast_strdup(endpoint->aors); + + if (ast_strlen_zero(persistent->aors)) { + ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_UNKNOWN); + } else { + persistent_endpoint_update_state(persistent, NULL, 0); + } + + ao2_link_flags(persistent_endpoints, persistent, OBJ_NOLOCK); + } + + ao2_ref(persistent->endpoint, +1); + return persistent->endpoint; +} + +/*! \brief Callback function for when an object is finalized */ +static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + if (!(endpoint->persistent = persistent_endpoint_find_or_create(endpoint))) { + return -1; + } + + return 0; +} + +int ast_res_pjsip_initialize_configuration(void) +{ + if (ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands))) { + return -1; + } + + if (!(persistent_endpoints = ao2_container_alloc(PERSISTENT_BUCKETS, persistent_endpoint_hash, persistent_endpoint_cmp))) { + return -1; + } + + if (!(sip_sorcery = ast_sorcery_open())) { + ast_log(LOG_ERROR, "Failed to open SIP sorcery failed to open\n"); + return -1; + } + + ast_sorcery_apply_config(sip_sorcery, "res_pjsip"); + + if (ast_sip_initialize_sorcery_auth(sip_sorcery)) { + ast_log(LOG_ERROR, "Failed to register SIP authentication support\n"); + ast_sorcery_unref(sip_sorcery); + sip_sorcery = NULL; + return -1; + } + + ast_sorcery_apply_default(sip_sorcery, "endpoint", "config", "pjsip.conf,criteria=type=endpoint"); + + ast_sorcery_apply_default(sip_sorcery, "nat_hook", "memory", NULL); + + if (ast_sorcery_object_register(sip_sorcery, "endpoint", ast_sip_endpoint_alloc, NULL, sip_endpoint_apply_handler)) { + ast_log(LOG_ERROR, "Failed to register SIP endpoint object with sorcery\n"); + ast_sorcery_unref(sip_sorcery); + sip_sorcery = NULL; + return -1; + } + + ast_sorcery_object_register(sip_sorcery, "nat_hook", sip_nat_hook_alloc, NULL, NULL); + + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "type", "", OPT_NOOP_T, 0, 0); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "context", "default", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, context)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "disallow", "", OPT_CODEC_T, 0, FLDSET(struct ast_sip_endpoint, media.prefs, media.codecs)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow", "", OPT_CODEC_T, 1, FLDSET(struct ast_sip_endpoint, media.prefs, media.codecs)); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtmfmode", "rfc4733", dtmf_handler, NULL, 0, 0); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_ipv6", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.ipv6)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_symmetric", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.symmetric)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "ice_support", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.ice_support)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "use_ptime", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.use_ptime)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "force_rport", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, nat.force_rport)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rewrite_contact", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, nat.rewrite_contact)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "transport", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, transport)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, outbound_proxy)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mohsuggest", "default", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, mohsuggest)); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "100rel", "yes", prack_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "timers", "yes", timers_handler, NULL, 0, 0); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "timers_min_se", "90", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, extensions.timer.min_se)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "timers_sess_expires", "1800", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, extensions.timer.sess_expires)); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "auth", "", inbound_auth_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outbound_auth", "", outbound_auth_handler, NULL, 0, 0); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "aors", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, aors)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "external_media_address", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.external_address)); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "identify_by", "username", ident_handler, NULL, 0, 0); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "direct_media", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.direct_media.enabled)); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "direct_media_method", "invite", direct_media_method_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "connected_line_method", "invite", connected_line_method_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "direct_media_glare_mitigation", "none", direct_media_glare_mitigation_handler, NULL, 0, 0); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "disable_direct_media_on_nat", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.direct_media.disable_on_nat)); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid", "", caller_id_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid_privacy", "", caller_id_privacy_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid_tag", "", caller_id_tag_handler, NULL, 0, 0); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "trust_id_inbound", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.trust_inbound)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "trust_id_outbound", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.trust_outbound)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_pai", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.send_pai)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_rpid", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.send_rpid)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_diversion", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.send_diversion)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mailboxes", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, subscription.mwi.mailboxes)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "aggregate_mwi", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.mwi.aggregate)); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "media_encryption", "no", media_encryption_handler, NULL, 0, 0); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "use_avpf", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.use_avpf)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "one_touch_recording", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, info.recording.enabled)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "inband_progress", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, inband_progress)); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callgroup", "", group_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "pickupgroup", "", group_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "namedcallgroup", "", named_groups_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "namedpickupgroup", "", named_groups_handler, NULL, 0, 0); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "devicestate_busy_at", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, devicestate_busy_at)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "t38udptl", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.t38.enabled)); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "t38udptl_ec", "none", t38udptl_ec_handler, NULL, 0, 0); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "t38udptl_maxdatagram", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.t38.maxdatagram)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "faxdetect", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, faxdetect)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "t38udptl_nat", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.t38.nat)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "t38udptl_ipv6", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.t38.ipv6)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "tonezone", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, zone)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "language", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, language)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "recordonfeature", "automixmon", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, info.recording.onfeature)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "recordofffeature", "automixmon", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, info.recording.offfeature)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allowtransfer", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allowtransfer)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "sdpowner", "-", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.sdpowner)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "sdpsession", "Asterisk", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.sdpsession)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "tos_audio", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.tos_audio)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "tos_video", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.tos_video)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "cos_audio", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.cos_audio)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "cos_video", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.cos_video)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allowsubscribe", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.allow)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "subminexpiry", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, subscription.minexpiry)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "subminexpirey", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, subscription.minexpiry)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "fromuser", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, fromuser)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "fromdomain", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, fromdomain)); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mwifromuser", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, subscription.mwi.fromuser)); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtlsverify", "", dtls_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtlsrekey", "", dtls_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtlscertfile", "", dtls_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtlsprivatekey", "", dtls_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtlscipher", "", dtls_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtlscafile", "", dtls_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtlscapath", "", dtls_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtlssetup", "", dtls_handler, NULL, 0, 0); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "srtp_tag_32", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.srtp_tag_32)); + + if (ast_sip_initialize_sorcery_transport(sip_sorcery)) { + ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n"); + ast_sorcery_unref(sip_sorcery); + sip_sorcery = NULL; + return -1; + } + + if (ast_sip_initialize_sorcery_location(sip_sorcery)) { + ast_log(LOG_ERROR, "Failed to register SIP location support with sorcery\n"); + ast_sorcery_unref(sip_sorcery); + sip_sorcery = NULL; + return -1; + } + + if (ast_sip_initialize_sorcery_qualify(sip_sorcery)) { + ast_log(LOG_ERROR, "Failed to register SIP qualify support with sorcery\n"); + ast_sorcery_unref(sip_sorcery); + sip_sorcery = NULL; + return -1; + } + + ast_sorcery_observer_add(sip_sorcery, "contact", &state_contact_observer); + + if (ast_sip_initialize_sorcery_domain_alias(sip_sorcery)) { + ast_log(LOG_ERROR, "Failed to register SIP domain aliases support with sorcery\n"); + ast_sorcery_unref(sip_sorcery); + sip_sorcery = NULL; + return -1; + } + + if (ast_sip_initialize_sorcery_security(sip_sorcery)) { + ast_log(LOG_ERROR, "Failed to register SIP security support\n"); + ast_sorcery_unref(sip_sorcery); + sip_sorcery = NULL; + return -1; + } + + if (ast_sip_initialize_sorcery_global(sip_sorcery)) { + ast_log(LOG_ERROR, "Failed to register SIP Global support\n"); + ast_sorcery_unref(sip_sorcery); + sip_sorcery = NULL; + return -1; + } + + ast_sorcery_load(sip_sorcery); + + return 0; +} + +void ast_res_pjsip_destroy_configuration(void) +{ + ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands)); + ast_sorcery_unref(sip_sorcery); +} + +int ast_res_pjsip_reload_configuration(void) +{ + if (sip_sorcery) { + ast_sorcery_reload(sip_sorcery); + } + return 0; +} + +static void subscription_configuration_destroy(struct ast_sip_endpoint_subscription_configuration *subscription) +{ + ast_string_field_free_memory(&subscription->mwi); +} + +static void info_configuration_destroy(struct ast_sip_endpoint_info_configuration *info) +{ + ast_string_field_free_memory(&info->recording); +} + +static void media_configuration_destroy(struct ast_sip_endpoint_media_configuration *media) +{ + ast_string_field_free_memory(&media->rtp); + ast_string_field_free_memory(media); +} + +static void endpoint_destructor(void* obj) +{ + struct ast_sip_endpoint *endpoint = obj; + + ast_string_field_free_memory(endpoint); + + if (endpoint->media.codecs) { + ast_format_cap_destroy(endpoint->media.codecs); + } + subscription_configuration_destroy(&endpoint->subscription); + info_configuration_destroy(&endpoint->info); + media_configuration_destroy(&endpoint->media); + ast_sip_auth_array_destroy(&endpoint->inbound_auths); + ast_sip_auth_array_destroy(&endpoint->outbound_auths); + ast_party_id_free(&endpoint->id.self); + endpoint->pickup.named_callgroups = ast_unref_namedgroups(endpoint->pickup.named_callgroups); + endpoint->pickup.named_pickupgroups = ast_unref_namedgroups(endpoint->pickup.named_pickupgroups); + ao2_cleanup(endpoint->persistent); +} + +static int init_subscription_configuration(struct ast_sip_endpoint_subscription_configuration *subscription) +{ + return ast_string_field_init(&subscription->mwi, 64); +} + +static int init_info_configuration(struct ast_sip_endpoint_info_configuration *info) +{ + return ast_string_field_init(&info->recording, 32); +} + +static int init_media_configuration(struct ast_sip_endpoint_media_configuration *media) +{ + return ast_string_field_init(media, 64) || ast_string_field_init(&media->rtp, 32); +} + +void *ast_sip_endpoint_alloc(const char *name) +{ + struct ast_sip_endpoint *endpoint = ast_sorcery_generic_alloc(sizeof(*endpoint), endpoint_destructor); + if (!endpoint) { + return NULL; + } + if (ast_string_field_init(endpoint, 64)) { + ao2_cleanup(endpoint); + return NULL; + } + if (!(endpoint->media.codecs = ast_format_cap_alloc_nolock())) { + ao2_cleanup(endpoint); + return NULL; + } + if (init_subscription_configuration(&endpoint->subscription)) { + ao2_cleanup(endpoint); + return NULL; + } + if (init_info_configuration(&endpoint->info)) { + ao2_cleanup(endpoint); + return NULL; + } + if (init_media_configuration(&endpoint->media)) { + ao2_cleanup(endpoint); + return NULL; + } + ast_party_id_init(&endpoint->id.self); + return endpoint; +} + +struct ao2_container *ast_sip_get_endpoints(void) +{ + struct ao2_container *endpoints; + + endpoints = ast_sorcery_retrieve_by_fields(sip_sorcery, "endpoint", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL); + + return endpoints; +} + +int ast_sip_retrieve_auths(const struct ast_sip_auth_array *auths, struct ast_sip_auth **out) +{ + int i; + + for (i = 0; i < auths->num; ++i) { + out[i] = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), SIP_SORCERY_AUTH_TYPE, auths->names[i]); + if (!out[i]) { + ast_log(LOG_NOTICE, "Couldn't find auth '%s'. Cannot authenticate\n", auths->names[i]); + return -1; + } + } + + return 0; +} + +void ast_sip_cleanup_auths(struct ast_sip_auth *auths[], size_t num_auths) +{ + int i; + for (i = 0; i < num_auths; ++i) { + ao2_cleanup(auths[i]); + } +} + +struct ast_sorcery *ast_sip_get_sorcery(void) +{ + return sip_sorcery; +} diff --git a/res/res_pjsip/pjsip_distributor.c b/res/res_pjsip/pjsip_distributor.c new file mode 100644 index 000000000..7597ae79e --- /dev/null +++ b/res/res_pjsip/pjsip_distributor.c @@ -0,0 +1,322 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Mark Michelson <mmichelson@digium.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 <pjsip.h> + +#include "asterisk/res_pjsip.h" + +static int distribute(void *data); +static pj_bool_t distributor(pjsip_rx_data *rdata); + +static pjsip_module distributor_mod = { + .name = {"Request Distributor", 19}, + .priority = PJSIP_MOD_PRIORITY_TSX_LAYER - 6, + .on_rx_request = distributor, + .on_rx_response = distributor, +}; + +/*! Dialog-specific information the distributor uses */ +struct distributor_dialog_data { + /* Serializer to distribute tasks to for this dialog */ + struct ast_taskprocessor *serializer; + /* Endpoint associated with this dialog */ + struct ast_sip_endpoint *endpoint; +}; + +/*! + * \internal + * + * \note Call this with the dialog locked + */ +static struct distributor_dialog_data *distributor_dialog_data_alloc(pjsip_dialog *dlg) +{ + struct distributor_dialog_data *dist; + + dist = PJ_POOL_ZALLOC_T(dlg->pool, struct distributor_dialog_data); + pjsip_dlg_set_mod_data(dlg, distributor_mod.id, dist); + + return dist; +} + +void ast_sip_dialog_set_serializer(pjsip_dialog *dlg, struct ast_taskprocessor *serializer) +{ + struct distributor_dialog_data *dist; + SCOPED_LOCK(lock, dlg, pjsip_dlg_inc_lock, pjsip_dlg_dec_lock); + + dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id); + if (!dist) { + dist = distributor_dialog_data_alloc(dlg); + } + dist->serializer = serializer; +} + +void ast_sip_dialog_set_endpoint(pjsip_dialog *dlg, struct ast_sip_endpoint *endpoint) +{ + struct distributor_dialog_data *dist; + SCOPED_LOCK(lock, dlg, pjsip_dlg_inc_lock, pjsip_dlg_dec_lock); + + dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id); + if (!dist) { + dist = distributor_dialog_data_alloc(dlg); + } + dist->endpoint = endpoint; +} + +struct ast_sip_endpoint *ast_sip_dialog_get_endpoint(pjsip_dialog *dlg) +{ + struct distributor_dialog_data *dist; + SCOPED_LOCK(lock, dlg, pjsip_dlg_inc_lock, pjsip_dlg_dec_lock); + + dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id); + if (!dist || !dist->endpoint) { + return NULL; + } + ao2_ref(dist->endpoint, +1); + return dist->endpoint; +} + +static pj_bool_t distributor(pjsip_rx_data *rdata) +{ + pjsip_dialog *dlg = pjsip_ua_find_dialog(&rdata->msg_info.cid->id, &rdata->msg_info.to->tag, &rdata->msg_info.from->tag, PJ_TRUE); + struct distributor_dialog_data *dist = NULL; + struct ast_taskprocessor *serializer = NULL; + pjsip_rx_data *clone; + + if (dlg) { + dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id); + if (dist) { + serializer = dist->serializer; + } + } + + if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG && ( + !pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_cancel_method) || + !pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_bye_method)) && + !serializer) { + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 481, NULL, NULL, NULL); + goto end; + } + + pjsip_rx_data_clone(rdata, 0, &clone); + + if (dist) { + clone->endpt_info.mod_data[distributor_mod.id] = dist->endpoint; + } + + ast_sip_push_task(serializer, distribute, clone); + +end: + if (dlg) { + pjsip_dlg_dec_lock(dlg); + } + + return PJ_TRUE; +} + +static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata); + +static pjsip_module endpoint_mod = { + .name = {"Endpoint Identifier", 19}, + .priority = PJSIP_MOD_PRIORITY_TSX_LAYER - 3, + .on_rx_request = endpoint_lookup, +}; + +static struct ast_sip_auth *artificial_auth; + +static int create_artificial_auth(void) +{ + if (!(artificial_auth = ast_sorcery_alloc( + ast_sip_get_sorcery(), SIP_SORCERY_AUTH_TYPE, "artificial"))) { + ast_log(LOG_ERROR, "Unable to create artificial auth\n"); + return -1; + } + + ast_string_field_set(artificial_auth, realm, "asterisk"); + ast_string_field_set(artificial_auth, auth_user, ""); + ast_string_field_set(artificial_auth, auth_pass, ""); + artificial_auth->type = AST_SIP_AUTH_TYPE_ARTIFICIAL; + return 0; +} + +struct ast_sip_auth *ast_sip_get_artificial_auth(void) +{ + ao2_ref(artificial_auth, +1); + return artificial_auth; +} + +static struct ast_sip_endpoint *artificial_endpoint; + +static int create_artificial_endpoint(void) +{ + if (!(artificial_endpoint = ast_sorcery_alloc( + ast_sip_get_sorcery(), "endpoint", NULL))) { + return -1; + } + + artificial_endpoint->inbound_auths.num = 1; + return 0; +} + +struct ast_sip_endpoint *ast_sip_get_artificial_endpoint(void) +{ + ao2_ref(artificial_endpoint, +1); + return artificial_endpoint; +} + +static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata) +{ + struct ast_sip_endpoint *endpoint; + int is_ack = rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD; + + endpoint = rdata->endpt_info.mod_data[distributor_mod.id]; + if (endpoint) { + /* Bumping the refcount makes refcounting consistent whether an endpoint + * is looked up or not */ + ao2_ref(endpoint, +1); + } else { + endpoint = ast_sip_identify_endpoint(rdata); + } + + if (!endpoint && !is_ack) { + char name[AST_UUID_STR_LEN] = ""; + pjsip_uri *from = rdata->msg_info.from->uri; + + /* always use an artificial endpoint - per discussion no reason + to have "alwaysauthreject" as an option. It is felt using it + was a bug fix and it is not needed since we are not worried about + breaking old stuff and we really don't want to enable the discovery + of SIP accounts */ + endpoint = ast_sip_get_artificial_endpoint(); + + if (PJSIP_URI_SCHEME_IS_SIP(from) || PJSIP_URI_SCHEME_IS_SIPS(from)) { + pjsip_sip_uri *sip_from = pjsip_uri_get_uri(from); + ast_copy_pj_str(name, &sip_from->user, sizeof(name)); + } + + ast_sip_report_invalid_endpoint(name, rdata); + } + rdata->endpt_info.mod_data[endpoint_mod.id] = endpoint; + return PJ_FALSE; +} + +static pj_bool_t authenticate(pjsip_rx_data *rdata) +{ + RAII_VAR(struct ast_sip_endpoint *, endpoint, ast_pjsip_rdata_get_endpoint(rdata), ao2_cleanup); + int is_ack = rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD; + + ast_assert(endpoint != NULL); + + if (!is_ack && ast_sip_requires_authentication(endpoint, rdata)) { + pjsip_tx_data *tdata; + pjsip_endpt_create_response(ast_sip_get_pjsip_endpoint(), rdata, 401, NULL, &tdata); + switch (ast_sip_check_authentication(endpoint, rdata, tdata)) { + case AST_SIP_AUTHENTICATION_CHALLENGE: + /* Send the 401 we created for them */ + ast_sip_report_auth_challenge_sent(endpoint, rdata, tdata); + pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL); + return PJ_TRUE; + case AST_SIP_AUTHENTICATION_SUCCESS: + ast_sip_report_auth_success(endpoint, rdata); + pjsip_tx_data_dec_ref(tdata); + return PJ_FALSE; + case AST_SIP_AUTHENTICATION_FAILED: + ast_sip_report_auth_failed_challenge_response(endpoint, rdata); + pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL); + return PJ_TRUE; + case AST_SIP_AUTHENTICATION_ERROR: + ast_sip_report_auth_failed_challenge_response(endpoint, rdata); + pjsip_tx_data_dec_ref(tdata); + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); + return PJ_TRUE; + } + } + + return PJ_FALSE; +} + +static pjsip_module auth_mod = { + .name = {"Request Authenticator", 21}, + .priority = PJSIP_MOD_PRIORITY_APPLICATION - 1, + .on_rx_request = authenticate, +}; + +static int distribute(void *data) +{ + static pjsip_process_rdata_param param = { + .start_mod = &distributor_mod, + .idx_after_start = 1, + }; + pj_bool_t handled; + pjsip_rx_data *rdata = data; + int is_request = rdata->msg_info.msg->type == PJSIP_REQUEST_MSG; + int is_ack = is_request ? rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD : 0; + struct ast_sip_endpoint *endpoint; + + pjsip_endpt_process_rx_data(ast_sip_get_pjsip_endpoint(), rdata, ¶m, &handled); + if (!handled && is_request && !is_ack) { + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 501, NULL, NULL, NULL); + } + + /* The endpoint_mod stores an endpoint reference in the mod_data of rdata. This + * is the only appropriate spot to actually decrement the reference. + */ + endpoint = rdata->endpt_info.mod_data[endpoint_mod.id]; + ao2_cleanup(endpoint); + pjsip_rx_data_free_cloned(rdata); + return 0; +} + +struct ast_sip_endpoint *ast_pjsip_rdata_get_endpoint(pjsip_rx_data *rdata) +{ + struct ast_sip_endpoint *endpoint = rdata->endpt_info.mod_data[endpoint_mod.id]; + if (endpoint) { + ao2_ref(endpoint, +1); + } + return endpoint; +} + +int ast_sip_initialize_distributor(void) +{ + if (create_artificial_endpoint() || create_artificial_auth()) { + return -1; + } + + if (ast_sip_register_service(&distributor_mod)) { + return -1; + } + if (ast_sip_register_service(&endpoint_mod)) { + return -1; + } + if (ast_sip_register_service(&auth_mod)) { + return -1; + } + + return 0; +} + +void ast_sip_destroy_distributor(void) +{ + ast_sip_unregister_service(&distributor_mod); + ast_sip_unregister_service(&endpoint_mod); + ast_sip_unregister_service(&auth_mod); + + ao2_cleanup(artificial_auth); + ao2_cleanup(artificial_endpoint); +} diff --git a/res/res_pjsip/pjsip_global_headers.c b/res/res_pjsip/pjsip_global_headers.c new file mode 100644 index 000000000..eff870314 --- /dev/null +++ b/res/res_pjsip/pjsip_global_headers.c @@ -0,0 +1,171 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Mark Michelson <mmichelson@digium.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 <pjsip.h> +#include <pjlib.h> + +#include "asterisk/res_pjsip.h" +#include "asterisk/linkedlists.h" + +static pj_status_t add_request_headers(pjsip_tx_data *tdata); +static pj_status_t add_response_headers(pjsip_tx_data *tdata); + +/*! + * \brief Indicator we've already handled a specific request/response + * + * PJSIP tends to reuse requests and responses. If we already have added + * headers to a request or response, we mark the message with this value + * so that we know not to re-add the headers again. + */ +static unsigned int handled_id = 0xCA115785; + +static pjsip_module global_header_mod = { + .name = {"Global headers", 13}, + .priority = PJSIP_MOD_PRIORITY_APPLICATION, + .on_tx_request = add_request_headers, + .on_tx_response = add_response_headers, +}; + +struct header { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(name); + AST_STRING_FIELD(value); + ); + AST_LIST_ENTRY(header) next; +}; + +static struct header *alloc_header(const char *name, const char *value) +{ + struct header *alloc; + + alloc = ast_calloc_with_stringfields(1, struct header, 32); + + if (!alloc) { + return NULL; + } + + ast_string_field_set(alloc, name, name); + ast_string_field_set(alloc, value, value); + + return alloc; +} + +static void destroy_header(struct header *to_destroy) +{ + ast_string_field_free_memory(to_destroy); + ast_free(to_destroy); +} + +AST_RWLIST_HEAD(header_list, header); + +static struct header_list request_headers; +static struct header_list response_headers; + +static void add_headers_to_message(struct header_list *headers, pjsip_tx_data *tdata) +{ + struct header *iter; + SCOPED_LOCK(lock, headers, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK); + if (tdata->mod_data[global_header_mod.id] == &handled_id) { + return; + } + AST_LIST_TRAVERSE(headers, iter, next) { + ast_sip_add_header(tdata, iter->name, iter->value); + }; + tdata->mod_data[global_header_mod.id] = &handled_id; +} + +static pj_status_t add_request_headers(pjsip_tx_data *tdata) +{ + add_headers_to_message(&request_headers, tdata); + + return PJ_SUCCESS; +} + +static pj_status_t add_response_headers(pjsip_tx_data *tdata) +{ + add_headers_to_message(&response_headers, tdata); + + return PJ_SUCCESS; +} + +static void remove_header(struct header_list *headers, const char *to_remove) +{ + struct header *iter; + AST_LIST_TRAVERSE_SAFE_BEGIN(headers, iter, next) { + if (!strcasecmp(iter->name, to_remove)) { + AST_LIST_REMOVE_CURRENT(next); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; +} + +static int add_header(struct header_list *headers, const char *name, const char *value, int replace) +{ + struct header *to_add; + + to_add = alloc_header(name, value); + if (!to_add) { + return -1; + } + + AST_RWLIST_WRLOCK(headers); + if (replace) { + remove_header(headers, name); + } + AST_LIST_INSERT_TAIL(headers, to_add, next); + AST_RWLIST_UNLOCK(headers); + + return 0; +} + +int ast_sip_add_global_request_header(const char *name, const char *value, int replace) +{ + return add_header(&request_headers, name, value, replace); +} + +int ast_sip_add_global_response_header(const char *name, const char *value, int replace) +{ + return add_header(&response_headers, name, value, replace); +} + +void ast_sip_initialize_global_headers(void) +{ + AST_RWLIST_HEAD_INIT(&request_headers); + AST_RWLIST_HEAD_INIT(&response_headers); + + ast_sip_register_service(&global_header_mod); +} + +static void destroy_headers(struct header_list *headers) +{ + struct header *iter; + + while ((iter = AST_RWLIST_REMOVE_HEAD(headers, next))) { + destroy_header(iter); + } + AST_RWLIST_HEAD_DESTROY(headers); +} + +void ast_sip_destroy_global_headers(void) +{ + destroy_headers(&request_headers); + destroy_headers(&response_headers); +} diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c new file mode 100644 index 000000000..cc12af420 --- /dev/null +++ b/res/res_pjsip/pjsip_options.c @@ -0,0 +1,783 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Matt Jordan <mjordan@digium.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 <pjsip.h> +#include <pjsip_ua.h> +#include <pjlib.h> + +#include "asterisk/res_pjsip.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/astobj2.h" +#include "asterisk/cli.h" +#include "asterisk/time.h" +#include "include/res_pjsip_private.h" + +#define DEFAULT_LANGUAGE "en" +#define DEFAULT_ENCODING "text/plain" +#define QUALIFIED_BUCKETS 211 + +static int qualify_contact(struct ast_sip_contact *contact); + +/*! + * \internal + * \brief Create a ast_sip_contact_status object. + */ +static void *contact_status_alloc(const char *name) +{ + struct ast_sip_contact_status *status = ast_sorcery_generic_alloc(sizeof(*status), NULL); + + if (!status) { + ast_log(LOG_ERROR, "Unable to allocate ast_sip_contact_status\n"); + return NULL; + } + + status->status = UNAVAILABLE; + + return status; +} + +/*! + * \internal + * \brief Retrieve a ast_sip_contact_status object from sorcery creating + * one if not found. + */ +static struct ast_sip_contact_status *find_or_create_contact_status(const struct ast_sip_contact *contact) +{ + struct ast_sip_contact_status *status = ast_sorcery_retrieve_by_id( + ast_sip_get_sorcery(), CONTACT_STATUS, + ast_sorcery_object_get_id(contact)); + + if (status) { + return status; + } + + if (!(status = ast_sorcery_alloc( + ast_sip_get_sorcery(), CONTACT_STATUS, + ast_sorcery_object_get_id(contact)))) { + + ast_log(LOG_ERROR, "Unable to create ast_sip_contact_status for contact %s\n", + contact->uri); + return NULL; + } + + if (ast_sorcery_create(ast_sip_get_sorcery(), status)) { + ast_log(LOG_ERROR, "Unable to persist ast_sip_contact_status for contact %s\n", + contact->uri); + return NULL; + } + + return status; +} + +/*! + * \internal + * \brief Update an ast_sip_contact_status's elements. + */ +static void update_contact_status(const struct ast_sip_contact *contact, + enum ast_sip_contact_status_type value) +{ + RAII_VAR(struct ast_sip_contact_status *, status, + find_or_create_contact_status(contact), ao2_cleanup); + + RAII_VAR(struct ast_sip_contact_status *, update, ast_sorcery_alloc( + ast_sip_get_sorcery(), CONTACT_STATUS, + ast_sorcery_object_get_id(status)), ao2_cleanup); + + if (!update) { + ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n", + contact->uri); + return; + } + + update->status = value; + + /* if the contact is available calculate the rtt as + the diff between the last start time and "now" */ + update->rtt = update->status ? + ast_tvdiff_us(ast_tvnow(), status->rtt_start) : 0; + + update->rtt_start = ast_tv(0, 0); + + if (ast_sorcery_update(ast_sip_get_sorcery(), update)) { + ast_log(LOG_ERROR, "Unable to update ast_sip_contact_status for contact %s\n", + contact->uri); + } +} + +/*! + * \internal + * \brief Initialize the start time on a contact status so the round + * trip time can be calculated upon a valid response. + */ +static void init_start_time(const struct ast_sip_contact *contact) +{ + RAII_VAR(struct ast_sip_contact_status *, status, + find_or_create_contact_status(contact), ao2_cleanup); + + RAII_VAR(struct ast_sip_contact_status *, update, ast_sorcery_alloc( + ast_sip_get_sorcery(), CONTACT_STATUS, + ast_sorcery_object_get_id(status)), ao2_cleanup); + + if (!update) { + ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n", + contact->uri); + return; + } + + update->rtt_start = ast_tvnow(); + + if (ast_sorcery_update(ast_sip_get_sorcery(), update)) { + ast_log(LOG_ERROR, "Unable to update ast_sip_contact_status for contact %s\n", + contact->uri); + } +} + +/*! + * \internal + * \brief For an endpoint try to match on a given contact. + */ +static int on_endpoint(void *obj, void *arg, int flags) +{ + struct ast_sip_endpoint *endpoint = obj; + char *aor_name, *aors; + + if (!arg || ast_strlen_zero(endpoint->aors)) { + return 0; + } + + aors = ast_strdupa(endpoint->aors); + + while ((aor_name = strsep(&aors, ","))) { + RAII_VAR(struct ast_sip_aor *, aor, + ast_sip_location_retrieve_aor(aor_name), ao2_cleanup); + RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup); + + if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) { + continue; + } + + if (ao2_find(contacts, arg, OBJ_NODATA | OBJ_POINTER)) { + return CMP_MATCH; + } + } + + return 0; +} + +/*! + * \internal + * \brief Find endpoints associated with the given contact. + */ +static struct ao2_container *find_endpoints(struct ast_sip_contact *contact) +{ + RAII_VAR(struct ao2_container *, endpoints, + ast_sip_get_endpoints(), ao2_cleanup); + + return ao2_callback(endpoints, OBJ_MULTIPLE, on_endpoint, contact); +} + +/*! + * \internal + * \brief Receive an response to the qualify contact request. + */ +static void qualify_contact_cb(void *token, pjsip_event *e) +{ + RAII_VAR(struct ast_sip_contact *, contact, token, ao2_cleanup); + RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup); + RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); + + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pjsip_rx_data *challenge = e->body.tsx_state.src.rdata; + pjsip_tx_data *tdata; + + switch(e->body.tsx_state.type) { + case PJSIP_EVENT_TRANSPORT_ERROR: + case PJSIP_EVENT_TIMER: + update_contact_status(contact, UNAVAILABLE); + return; + default: + break; + } + + if (!contact->authenticate_qualify || (tsx->status_code != 401 && + tsx->status_code != 407)) { + update_contact_status(contact, AVAILABLE); + return; + } + + /* try to find endpoints that are associated with the contact */ + if (!(endpoints = find_endpoints(contact))) { + ast_log(LOG_ERROR, "No endpoints found for contact %s, cannot authenticate", + contact->uri); + return; + } + + /* find "first" endpoint in order to authenticate - actually any + endpoint should do that matched on the contact */ + endpoint = ao2_callback(endpoints, 0, NULL, NULL); + + if (!ast_sip_create_request_with_auth(&endpoint->outbound_auths, + challenge, tsx, &tdata)) { + pjsip_endpt_send_request(ast_sip_get_pjsip_endpoint(), tdata, + -1, NULL, NULL); + } +} + +/*! + * \internal + * \brief Attempt to qualify the contact + * + * \detail Sends a SIP OPTIONS request to the given contact in order to make + * sure that contact is available. + */ +static int qualify_contact(struct ast_sip_contact *contact) +{ + pjsip_tx_data *tdata; + + if (ast_sip_create_request("OPTIONS", NULL, NULL, contact->uri, &tdata)) { + ast_log(LOG_ERROR, "Unable to create request to qualify contact %s\n", + contact->uri); + return -1; + } + + init_start_time(contact); + + ao2_ref(contact, +1); + if (pjsip_endpt_send_request(ast_sip_get_pjsip_endpoint(), + tdata, -1, contact, qualify_contact_cb) != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + ast_log(LOG_ERROR, "Unable to send request to qualify contact %s\n", + contact->uri); + ao2_ref(contact, -1); + return -1; + } + + return 0; +} + +/*! + * \internal + * \brief Scheduling context for sending QUALIFY request at specified intervals. + */ +static struct ast_sched_context *sched; + +/*! + * \internal + * \brief Container to hold all actively scheduled qualifies. + */ +static struct ao2_container *sched_qualifies; + +/*! + * \internal + * \brief Structure to hold qualify contact scheduling information. + */ +struct sched_data { + /*! The scheduling id */ + int id; + /*! The the contact being checked */ + struct ast_sip_contact *contact; +}; + +/*! + * \internal + * \brief Destroy the scheduled data and remove from scheduler. + */ +static void sched_data_destructor(void *obj) +{ + struct sched_data *data = obj; + ao2_cleanup(data->contact); +} +/*! + * \internal + * \brief Create the scheduling data object. + */ +static struct sched_data *sched_data_create(struct ast_sip_contact *contact) +{ + struct sched_data *data = ao2_alloc(sizeof(*data), sched_data_destructor); + + if (!data) { + ast_log(LOG_ERROR, "Unable to create schedule qualify data\n"); + return NULL; + } + + data->contact = contact; + ao2_ref(data->contact, +1); + + return data; +} + +/*! + * \internal + * \brief Send a qualify contact request within a threaded task. + */ +static int qualify_contact_task(void *obj) +{ + RAII_VAR(struct ast_sip_contact *, contact, obj, ao2_cleanup); + return qualify_contact(contact); +} + +/*! + * \internal + * \brief Send a scheduled qualify contact request. + */ +static int qualify_contact_sched(const void *obj) +{ + struct sched_data *data = (struct sched_data *)obj; + + ao2_ref(data->contact, +1); + if (ast_sip_push_task(NULL, qualify_contact_task, data->contact)) { + ao2_ref(data->contact, -1); + ao2_cleanup(data); + return 0; + } + + return data->contact->qualify_frequency * 1000; +} + +/*! + * \internal + * \brief Set up a scheduled qualify contact check. + */ +static void schedule_qualify(struct ast_sip_contact *contact) +{ + RAII_VAR(struct sched_data *, data, sched_data_create(contact), ao2_cleanup); + + if (!data) { + return; + } + + ao2_ref(data, +1); + if ((data->id = ast_sched_add_variable( + sched, contact->qualify_frequency * 1000, + qualify_contact_sched, data, 1)) < 0) { + + ao2_ref(data, -1); + ast_log(LOG_ERROR, "Unable to schedule qualify for contact %s\n", + contact->uri); + return; + } + + ao2_link(sched_qualifies, data); +} + +/*! + * \internal + * \brief Remove the contact from the scheduler. + */ +static void unschedule_qualify(struct ast_sip_contact *contact) +{ + RAII_VAR(struct sched_data *, data, ao2_find( + sched_qualifies, contact, OBJ_UNLINK), ao2_cleanup); + + if (!data) { + return; + } + + AST_SCHED_DEL_UNREF(sched, data->id, ao2_cleanup(data)); +} + +/*! + * \internal + * \brief Qualify the given contact and set up scheduling if configured. + */ +static void qualify_and_schedule(struct ast_sip_contact *contact) +{ + unschedule_qualify(contact); + + if (contact->qualify_frequency) { + ao2_ref(contact, +1); + ast_sip_push_task(NULL, qualify_contact_task, contact); + + schedule_qualify(contact); + } +} + +/*! + * \internal + * \brief A new contact has been created make sure it is available. + */ +static void contact_created(const void *obj) +{ + qualify_and_schedule((struct ast_sip_contact *)obj); +} + +/*! + * \internal + * \brief A contact has been deleted remove status tracking. + */ +static void contact_deleted(const void *obj) +{ + struct ast_sip_contact *contact = (struct ast_sip_contact *)obj; + RAII_VAR(struct ast_sip_contact_status *, status, NULL, ao2_cleanup); + + unschedule_qualify(contact); + + if (!(status = ast_sorcery_retrieve_by_id( + ast_sip_get_sorcery(), CONTACT_STATUS, + ast_sorcery_object_get_id(contact)))) { + return; + } + + if (ast_sorcery_delete(ast_sip_get_sorcery(), status)) { + ast_log(LOG_ERROR, "Unable to delete ast_sip_contact_status for contact %s\n", + contact->uri); + } +} + +struct ast_sorcery_observer contact_observer = { + .created = contact_created, + .deleted = contact_deleted +}; + +static pj_bool_t options_start(void) +{ + if (!(sched = ast_sched_context_create()) || + ast_sched_start_thread(sched)) { + return -1; + } + + return PJ_SUCCESS; +} + +static pj_bool_t options_stop(void) +{ + ast_sorcery_observer_remove(ast_sip_get_sorcery(), "contact", &contact_observer); + + ao2_t_ref(sched_qualifies, -1, "Remove scheduled qualifies on module stop"); + + if (sched) { + ast_sched_context_destroy(sched); + } + + return PJ_SUCCESS; +} + +static pj_status_t send_options_response(pjsip_rx_data *rdata, int code) +{ + pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint(); + pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata); + pjsip_transaction *trans = pjsip_rdata_get_tsx(rdata); + pjsip_tx_data *tdata; + const pjsip_hdr *hdr; + pjsip_response_addr res_addr; + pj_status_t status; + + /* Make the response object */ + if ((status = pjsip_endpt_create_response( + endpt, rdata, code, NULL, &tdata) != PJ_SUCCESS)) { + ast_log(LOG_ERROR, "Unable to create response (%d)\n", status); + return status; + } + + /* Add appropriate headers */ + if ((hdr = pjsip_endpt_get_capability(endpt, PJSIP_H_ACCEPT, NULL))) { + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr)); + } + if ((hdr = pjsip_endpt_get_capability(endpt, PJSIP_H_ALLOW, NULL))) { + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr)); + } + if ((hdr = pjsip_endpt_get_capability(endpt, PJSIP_H_SUPPORTED, NULL))) { + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr)); + } + + /* + * XXX TODO: pjsip doesn't care a lot about either of these headers - + * while it provides specific methods to create them, they are defined + * to be the standard string header creation. We never did add them + * in chan_sip, although RFC 3261 says they SHOULD. Hard coded here. + */ + ast_sip_add_header(tdata, "Accept-Encoding", DEFAULT_ENCODING); + ast_sip_add_header(tdata, "Accept-Language", DEFAULT_LANGUAGE); + + if (dlg && trans) { + status = pjsip_dlg_send_response(dlg, trans, tdata); + } else { + /* Get where to send request. */ + if ((status = pjsip_get_response_addr( + tdata->pool, rdata, &res_addr)) != PJ_SUCCESS) { + ast_log(LOG_ERROR, "Unable to get response address (%d)\n", + status); + + pjsip_tx_data_dec_ref(tdata); + return status; + } + status = pjsip_endpt_send_response(endpt, &res_addr, tdata, + NULL, NULL); + } + + if (status != PJ_SUCCESS) { + ast_log(LOG_ERROR, "Unable to send response (%d)\n", status); + } + + return status; +} + +static pj_bool_t options_on_rx_request(pjsip_rx_data *rdata) +{ + RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); + pjsip_uri *ruri; + pjsip_sip_uri *sip_ruri; + char exten[AST_MAX_EXTENSION]; + + if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, + &pjsip_options_method)) { + return PJ_FALSE; + } + + if (!(endpoint = ast_pjsip_rdata_get_endpoint(rdata))) { + return PJ_FALSE; + } + + ruri = rdata->msg_info.msg->line.req.uri; + if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) { + send_options_response(rdata, 416); + return -1; + } + + sip_ruri = pjsip_uri_get_uri(ruri); + ast_copy_pj_str(exten, &sip_ruri->user, sizeof(exten)); + + if (ast_shutting_down()) { + send_options_response(rdata, 503); + } else if (!ast_exists_extension(NULL, endpoint->context, exten, 1, NULL)) { + send_options_response(rdata, 404); + } else { + send_options_response(rdata, 200); + } + return PJ_TRUE; +} + +static pjsip_module options_module = { + .name = {"Options Module", 14}, + .id = -1, + .priority = PJSIP_MOD_PRIORITY_APPLICATION, + .start = options_start, + .stop = options_stop, + .on_rx_request = options_on_rx_request, +}; + +/*! + * \internal + * \brief Send qualify request to the given contact. + */ +static int cli_on_contact(void *obj, void *arg, int flags) +{ + struct ast_sip_contact *contact = obj; + struct ast_cli_args *a = arg; + ast_cli(a->fd, " contact %s\n", contact->uri); + qualify_contact(contact); + return 0; +} + +/*! + * \internal + * \brief For an endpoint iterate over and qualify all aors/contacts + */ +static void cli_qualify_contacts(struct ast_cli_args *a, const char *endpoint_name, + struct ast_sip_endpoint *endpoint) +{ + char *aor_name, *aors; + + if (ast_strlen_zero(endpoint->aors)) { + ast_cli(a->fd, "Endpoint %s has no AoR's configured\n", + endpoint_name); + return; + } + + aors = ast_strdupa(endpoint->aors); + + while ((aor_name = strsep(&aors, ","))) { + RAII_VAR(struct ast_sip_aor *, aor, + ast_sip_location_retrieve_aor(aor_name), ao2_cleanup); + RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup); + + if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) { + continue; + } + + ast_cli(a->fd, "Sending qualify to endpoint %s\n", endpoint_name); + ao2_callback(contacts, OBJ_NODATA, cli_on_contact, a); + } +} + +static char *cli_qualify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); + const char *endpoint_name; + + switch (cmd) { + case CLI_INIT: + e->command = "sip qualify"; + e->usage = + "Usage: sip qualify <endpoint>\n" + " Send a SIP OPTIONS request to all contacts on the endpoint.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 3) { + return CLI_SHOWUSAGE; + } + + endpoint_name = a->argv[2]; + + if (!(endpoint = ast_sorcery_retrieve_by_id( + ast_sip_get_sorcery(), "endpoint", endpoint_name))) { + ast_cli(a->fd, "Unable to retrieve endpoint %s\n", endpoint_name); + return CLI_FAILURE; + } + + /* send a qualify for all contacts registered with the endpoint */ + cli_qualify_contacts(a, endpoint_name, endpoint); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_options[] = { + AST_CLI_DEFINE(cli_qualify, "Send an OPTIONS request to a SIP endpoint") +}; + +static int sched_qualifies_hash_fn(const void *obj, int flags) +{ + const struct sched_data *data = obj; + + return ast_str_hash(ast_sorcery_object_get_id(data->contact)); +} + +static int sched_qualifies_cmp_fn(void *obj, void *arg, int flags) +{ + struct sched_data *data = obj; + + return !strcmp(ast_sorcery_object_get_id(data->contact), + ast_sorcery_object_get_id(arg)); +} + +int ast_sip_initialize_sorcery_qualify(struct ast_sorcery *sorcery) +{ + /* initialize sorcery ast_sip_contact_status resource */ + ast_sorcery_apply_default(sorcery, CONTACT_STATUS, "memory", NULL); + + if (ast_sorcery_object_register(sorcery, CONTACT_STATUS, + contact_status_alloc, NULL, NULL)) { + ast_log(LOG_ERROR, "Unable to register ast_sip_contact_status in sorcery\n"); + return -1; + } + + ast_sorcery_object_field_register(sorcery, CONTACT_STATUS, "rtt", "0", OPT_UINT_T, + 1, FLDSET(struct ast_sip_contact_status, status)); + ast_sorcery_object_field_register(sorcery, CONTACT_STATUS, "rtt", "0", OPT_UINT_T, + 1, FLDSET(struct ast_sip_contact_status, rtt)); + + return 0; +} + +static int qualify_and_schedule_cb(void *obj, void *arg, int flags) +{ + struct ast_sip_contact *contact = obj; + struct ast_sip_aor *aor = arg; + + contact->qualify_frequency = aor->qualify_frequency; + qualify_and_schedule(contact); + + return 0; +} + +/*! + * \internal + * \brief Qualify and schedule an endpoint's permanent contacts + * + * \detail For the given endpoint retrieve its list of aors, qualify all + * permanent contacts, and schedule for checks if configured. + */ +static int qualify_and_schedule_permanent_cb(void *obj, void *arg, int flags) +{ + struct ast_sip_endpoint *endpoint = obj; + char *aor_name, *aors; + + if (ast_strlen_zero(endpoint->aors)) { + return 0; + } + + aors = ast_strdupa(endpoint->aors); + + while ((aor_name = strsep(&aors, ","))) { + RAII_VAR(struct ast_sip_aor *, aor, + ast_sip_location_retrieve_aor(aor_name), ao2_cleanup); + + if (!aor || !aor->permanent_contacts) { + continue; + } + ao2_callback(aor->permanent_contacts, OBJ_NODATA, qualify_and_schedule_cb, aor); + } + + return 0; +} + +static void qualify_and_schedule_permanent(void) +{ + RAII_VAR(struct ao2_container *, endpoints, + ast_sip_get_endpoints(), ao2_cleanup); + + ao2_callback(endpoints, OBJ_NODATA, + qualify_and_schedule_permanent_cb, NULL); +} + +int ast_res_pjsip_init_options_handling(int reload) +{ + const pj_str_t STR_OPTIONS = { "OPTIONS", 7 }; + + if (sched_qualifies) { + ao2_t_ref(sched_qualifies, -1, "Remove old scheduled qualifies"); + } + + if (!(sched_qualifies = ao2_t_container_alloc( + QUALIFIED_BUCKETS, sched_qualifies_hash_fn, sched_qualifies_cmp_fn, + "Create container for scheduled qualifies"))) { + + return -1; + } + + if (reload) { + qualify_and_schedule_permanent(); + return 0; + } + + if (pjsip_endpt_register_module(ast_sip_get_pjsip_endpoint(), &options_module) != PJ_SUCCESS) { + options_stop(); + return -1; + } + + if (pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_ALLOW, NULL, 1, &STR_OPTIONS) != PJ_SUCCESS) { + pjsip_endpt_unregister_module(ast_sip_get_pjsip_endpoint(), &options_module); + return -1; + } + + if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "contact", &contact_observer)) { + ast_log(LOG_WARNING, "Unable to add contact observer\n"); + return -1; + } + + qualify_and_schedule_permanent(); + ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options)); + + return 0; +} diff --git a/res/res_pjsip/pjsip_outbound_auth.c b/res/res_pjsip/pjsip_outbound_auth.c new file mode 100644 index 000000000..5996d919b --- /dev/null +++ b/res/res_pjsip/pjsip_outbound_auth.c @@ -0,0 +1,94 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Mark Michelson <mmichelson@digium.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" +#undef bzero +#define bzero bzero +#include "pjsip.h" + +#include "asterisk/res_pjsip.h" +#include "asterisk/module.h" +#include "include/res_pjsip_private.h" + +static pj_bool_t outbound_auth(pjsip_rx_data *rdata); + +static pjsip_module outbound_auth_mod = { + .name = {"Outbound Authentication", 19}, + .priority = PJSIP_MOD_PRIORITY_DIALOG_USAGE, + .on_rx_response = outbound_auth, +}; + +struct outbound_auth_cb_data { + ast_sip_dialog_outbound_auth_cb cb; + void *user_data; +}; + +static pj_bool_t outbound_auth(pjsip_rx_data *rdata) +{ + RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); + pjsip_transaction *tsx; + pjsip_dialog *dlg; + struct outbound_auth_cb_data *cb_data; + pjsip_tx_data *tdata; + + if (rdata->msg_info.msg->line.status.code != 401 && + rdata->msg_info.msg->line.status.code != 407) { + /* Doesn't pertain to us. Move on */ + return PJ_FALSE; + } + + tsx = pjsip_rdata_get_tsx(rdata); + dlg = pjsip_rdata_get_dlg(rdata); + ast_assert(dlg != NULL && tsx != NULL); + endpoint = ast_sip_dialog_get_endpoint(dlg); + + if (!endpoint) { + return PJ_FALSE; + } + + if (ast_sip_create_request_with_auth(&endpoint->outbound_auths, rdata, tsx, &tdata)) { + return PJ_FALSE; + } + + cb_data = dlg->mod_data[outbound_auth_mod.id]; + if (cb_data) { + cb_data->cb(dlg, tdata, cb_data->user_data); + return PJ_TRUE; + } + + pjsip_dlg_send_request(dlg, tdata, -1, NULL); + return PJ_TRUE; +} + +int ast_sip_dialog_setup_outbound_authentication(pjsip_dialog *dlg, const struct ast_sip_endpoint *endpoint, + ast_sip_dialog_outbound_auth_cb cb, void *user_data) +{ + struct outbound_auth_cb_data *cb_data = PJ_POOL_ZALLOC_T(dlg->pool, struct outbound_auth_cb_data); + cb_data->cb = cb; + cb_data->user_data = user_data; + + dlg->sess_count++; + pjsip_dlg_add_usage(dlg, &outbound_auth_mod, cb_data); + dlg->sess_count--; + + return 0; +} + +int ast_sip_initialize_outbound_authentication(void) { + return ast_sip_register_service(&outbound_auth_mod); +} diff --git a/res/res_pjsip/security_events.c b/res/res_pjsip/security_events.c new file mode 100644 index 000000000..7b4913753 --- /dev/null +++ b/res/res_pjsip/security_events.c @@ -0,0 +1,234 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Joshua Colp <jcolp@digium.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. + */ + +/*! + * \file + * + * \brief Generate security events in the PJSIP channel + * + * \author Joshua Colp <jcolp@digium.com> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <pjsip.h> + +#include "asterisk/res_pjsip.h" +#include "asterisk/security_events.h" + +static int find_transport_in_use(void *obj, void *arg, int flags) +{ + struct ast_sip_transport *transport = obj; + pjsip_rx_data *rdata = arg; + + if ((transport->state->transport == rdata->tp_info.transport) || + (transport->state->factory && !pj_strcmp(&transport->state->factory->addr_name.host, &rdata->tp_info.transport->local_name.host) && + transport->state->factory->addr_name.port == rdata->tp_info.transport->local_name.port)) { + return CMP_MATCH | CMP_STOP; + } + + return 0; +} + +static enum ast_transport security_event_get_transport(pjsip_rx_data *rdata) +{ + RAII_VAR(struct ao2_container *, transports, NULL, ao2_cleanup); + RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup); + + /* It should be impossible for these to fail as the transport has to exist for the message to exist */ + transports = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL); + + ast_assert(transports != NULL); + + transport = ao2_callback(transports, 0, find_transport_in_use, rdata); + + ast_assert(transport != NULL); + + return transport->type; +} + +static void security_event_populate(pjsip_rx_data *rdata, char *call_id, size_t call_id_size, struct ast_sockaddr *local, struct ast_sockaddr *remote) +{ + char host[NI_MAXHOST]; + + ast_copy_pj_str(call_id, &rdata->msg_info.cid->id, call_id_size); + + ast_copy_pj_str(host, &rdata->tp_info.transport->local_name.host, sizeof(host)); + ast_sockaddr_parse(local, host, PARSE_PORT_FORBID); + ast_sockaddr_set_port(local, rdata->tp_info.transport->local_name.port); + + ast_sockaddr_parse(remote, rdata->pkt_info.src_name, PARSE_PORT_FORBID); + ast_sockaddr_set_port(remote, rdata->pkt_info.src_port); +} + +void ast_sip_report_invalid_endpoint(const char *name, pjsip_rx_data *rdata) +{ + enum ast_transport transport = security_event_get_transport(rdata); + char call_id[pj_strlen(&rdata->msg_info.cid->id) + 1]; + struct ast_sockaddr local, remote; + + struct ast_security_event_inval_acct_id inval_acct_id = { + .common.event_type = AST_SECURITY_EVENT_INVAL_ACCT_ID, + .common.version = AST_SECURITY_EVENT_INVAL_ACCT_ID_VERSION, + .common.service = "PJSIP", + .common.account_id = name, + .common.local_addr = { + .addr = &local, + .transport = transport, + }, + .common.remote_addr = { + .addr = &remote, + .transport = transport, + }, + .common.session_id = call_id, + }; + + security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote); + + ast_security_event_report(AST_SEC_EVT(&inval_acct_id)); +} + +void ast_sip_report_failed_acl(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, const char *name) +{ + enum ast_transport transport = security_event_get_transport(rdata); + char call_id[pj_strlen(&rdata->msg_info.cid->id) + 1]; + struct ast_sockaddr local, remote; + + struct ast_security_event_failed_acl failed_acl_event = { + .common.event_type = AST_SECURITY_EVENT_FAILED_ACL, + .common.version = AST_SECURITY_EVENT_FAILED_ACL_VERSION, + .common.service = "PJSIP", + .common.account_id = ast_sorcery_object_get_id(endpoint), + .common.local_addr = { + .addr = &local, + .transport = transport, + }, + .common.remote_addr = { + .addr = &remote, + .transport = transport, + }, + .common.session_id = call_id, + .acl_name = name, + }; + + security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote); + + ast_security_event_report(AST_SEC_EVT(&failed_acl_event)); +} + +void ast_sip_report_auth_failed_challenge_response(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata) +{ + pjsip_authorization_hdr *auth = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, NULL); + enum ast_transport transport = security_event_get_transport(rdata); + char call_id[pj_strlen(&rdata->msg_info.cid->id) + 1]; + char nonce[64] = "", response[256] = ""; + struct ast_sockaddr local, remote; + + struct ast_security_event_chal_resp_failed chal_resp_failed = { + .common.event_type = AST_SECURITY_EVENT_CHAL_RESP_FAILED, + .common.version = AST_SECURITY_EVENT_CHAL_RESP_FAILED_VERSION, + .common.service = "PJSIP", + .common.account_id = ast_sorcery_object_get_id(endpoint), + .common.local_addr = { + .addr = &local, + .transport = transport, + }, + .common.remote_addr = { + .addr = &remote, + .transport = transport, + }, + .common.session_id = call_id, + + .challenge = nonce, + .response = response, + .expected_response = "", + }; + + if (auth && !pj_strcmp2(&auth->scheme, "digest")) { + ast_copy_pj_str(nonce, &auth->credential.digest.nonce, sizeof(nonce)); + ast_copy_pj_str(response, &auth->credential.digest.response, sizeof(response)); + } + + security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote); + + ast_security_event_report(AST_SEC_EVT(&chal_resp_failed)); +} + +void ast_sip_report_auth_success(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata) +{ + pjsip_authorization_hdr *auth = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, NULL); + enum ast_transport transport = security_event_get_transport(rdata); + char call_id[pj_strlen(&rdata->msg_info.cid->id) + 1]; + struct ast_sockaddr local, remote; + + struct ast_security_event_successful_auth successful_auth = { + .common.event_type = AST_SECURITY_EVENT_SUCCESSFUL_AUTH, + .common.version = AST_SECURITY_EVENT_SUCCESSFUL_AUTH_VERSION, + .common.service = "PJSIP", + .common.account_id = ast_sorcery_object_get_id(endpoint), + .common.local_addr = { + .addr = &local, + .transport = transport, + }, + .common.remote_addr = { + .addr = &remote, + .transport = transport, + }, + .common.session_id = call_id, + .using_password = auth ? (uint32_t *)1 : (uint32_t *)0, + }; + + security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote); + + ast_security_event_report(AST_SEC_EVT(&successful_auth)); +} + +void ast_sip_report_auth_challenge_sent(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, pjsip_tx_data *tdata) +{ + pjsip_www_authenticate_hdr *auth = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_WWW_AUTHENTICATE, NULL); + enum ast_transport transport = security_event_get_transport(rdata); + char nonce[64] = "", call_id[pj_strlen(&rdata->msg_info.cid->id) + 1]; + struct ast_sockaddr local, remote; + + struct ast_security_event_chal_sent chal_sent = { + .common.event_type = AST_SECURITY_EVENT_CHAL_SENT, + .common.version = AST_SECURITY_EVENT_CHAL_SENT_VERSION, + .common.service = "PJSIP", + .common.account_id = ast_sorcery_object_get_id(endpoint), + .common.local_addr = { + .addr = &local, + .transport = transport, + }, + .common.remote_addr = { + .addr = &remote, + .transport = transport, + }, + .common.session_id = call_id, + .challenge = nonce, + }; + + if (auth && !pj_strcmp2(&auth->scheme, "digest")) { + ast_copy_pj_str(nonce, &auth->challenge.digest.nonce, sizeof(nonce)); + } + + security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote); + + ast_security_event_report(AST_SEC_EVT(&chal_sent)); +} |