summaryrefslogtreecommitdiff
path: root/res/res_pjsip
diff options
context:
space:
mode:
authorMark Michelson <mmichelson@digium.com>2013-07-30 18:14:50 +0000
committerMark Michelson <mmichelson@digium.com>2013-07-30 18:14:50 +0000
commit735b30ad71110c2a51404cb8686bbe3cf14b630c (patch)
tree76b1f10135c1b7f210e576be1359539de7e3476c /res/res_pjsip
parent895c8e0d2c97cd04299f3f179e99d8a3873c06c6 (diff)
The large GULP->PJSIP renaming effort.
The general gist is to have a clear boundary between old SIP stuff and new SIP stuff by having the word "SIP" for old stuff and "PJSIP" for new stuff. Here's a brief rundown of the changes: * The word "Gulp" in dialstrings, functions, and CLI commands is now "PJSIP" * chan_gulp.c is now chan_pjsip.c * Function names in chan_gulp.c that were "gulp_*" are now "chan_pjsip_*" * All files that were "res_sip*" are now "res_pjsip*" * The "res_sip" directory is now "res_pjsip" * Files in the "res_pjsip" directory that began with "sip_*" are now "pjsip_*" * The configuration file is now "pjsip.conf" instead of "res_sip.conf" * The module info for all PJSIP-related files now uses "PJSIP" instead of "SIP" * CLI and AMI commands created by Asterisk's PJSIP modules now have "pjsip" as the starting word instead of "sip" git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@395764 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'res/res_pjsip')
-rw-r--r--res/res_pjsip/config_auth.c127
-rw-r--r--res/res_pjsip/config_domain_aliases.c65
-rw-r--r--res/res_pjsip/config_global.c90
-rw-r--r--res/res_pjsip/config_security.c88
-rw-r--r--res/res_pjsip/config_system.c112
-rw-r--r--res/res_pjsip/config_transport.c338
-rw-r--r--res/res_pjsip/include/res_pjsip_private.h74
-rw-r--r--res/res_pjsip/location.c328
-rw-r--r--res/res_pjsip/pjsip_configuration.c892
-rw-r--r--res/res_pjsip/pjsip_distributor.c322
-rw-r--r--res/res_pjsip/pjsip_global_headers.c171
-rw-r--r--res/res_pjsip/pjsip_options.c783
-rw-r--r--res/res_pjsip/pjsip_outbound_auth.c94
-rw-r--r--res/res_pjsip/security_events.c234
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, &param, &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));
+}