summaryrefslogtreecommitdiff
path: root/res
diff options
context:
space:
mode:
Diffstat (limited to 'res')
-rw-r--r--res/Makefile5
-rw-r--r--res/res_sip.c906
-rw-r--r--res/res_sip.exports.in52
-rw-r--r--res/res_sip/config_auth.c120
-rw-r--r--res/res_sip/config_domain_aliases.c65
-rw-r--r--res/res_sip/config_transport.c299
-rw-r--r--res/res_sip/include/res_sip_private.h57
-rw-r--r--res/res_sip/location.c262
-rw-r--r--res/res_sip/sip_configuration.c463
-rw-r--r--res/res_sip/sip_distributor.c239
-rw-r--r--res/res_sip/sip_options.c378
-rw-r--r--res/res_sip/sip_outbound_auth.c94
-rw-r--r--res/res_sip_acl.c222
-rw-r--r--res/res_sip_authenticator_digest.c455
-rw-r--r--res/res_sip_caller_id.c715
-rw-r--r--res/res_sip_dtmf_info.c128
-rw-r--r--res/res_sip_endpoint_identifier_constant.c67
-rw-r--r--res/res_sip_endpoint_identifier_ip.c151
-rw-r--r--res/res_sip_endpoint_identifier_user.c128
-rw-r--r--res/res_sip_logger.c81
-rw-r--r--res/res_sip_mwi.c709
-rw-r--r--res/res_sip_nat.c235
-rw-r--r--res/res_sip_outbound_authenticator_digest.c110
-rw-r--r--res/res_sip_outbound_registration.c708
-rw-r--r--res/res_sip_pubsub.c662
-rw-r--r--res/res_sip_pubsub.exports.in16
-rw-r--r--res/res_sip_registrar.c382
-rw-r--r--res/res_sip_rfc3326.c145
-rw-r--r--res/res_sip_sdp_rtp.c848
-rw-r--r--res/res_sip_session.c1799
-rw-r--r--res/res_sip_session.exports.in18
-rw-r--r--res/res_sorcery_config.c1
32 files changed, 10518 insertions, 2 deletions
diff --git a/res/Makefile b/res/Makefile
index fec20a2e0..35d09275c 100644
--- a/res/Makefile
+++ b/res/Makefile
@@ -43,6 +43,9 @@ snmp/agent.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_snmp)
$(if $(filter res_ael_share,$(EMBEDDED_MODS)),modules.link,res_ael_share.so): ael/ael_lex.o ael/ael.tab.o ael/pval.o
ael/ael_lex.o ael/ael.tab.o ael/pval.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ael_share)
+$(if $(filter res_sip,$(EMBEDDED_MODS)),modules.link,res_sip.so): $(subst .c,.o,$(wildcard res_sip/*.c))
+$(subst .c,.o,$(wildcard res_sip/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_sip)
+
ifneq ($(findstring REBUILD_PARSERS,$(MENUSELECT_CFLAGS)),)
ael/ael_lex.c: ael/ael.flex
else
@@ -67,7 +70,7 @@ endif
ael/pval.o: ael/pval.c
clean::
- rm -f snmp/*.[oi] ael/*.[oi] ais/*.[oi] stasis_http/*.[oi]
+ rm -f snmp/*.[oi] ael/*.[oi] ais/*.[oi] stasis_http/*.[oi] res_sip/*.[oi]
# Dependencies for res_stasis_http_*.so are generated, so they're in this file
include stasis_http.make
diff --git a/res/res_sip.c b/res/res_sip.c
new file mode 100644
index 000000000..18eead992
--- /dev/null
+++ b/res/res_sip.c
@@ -0,0 +1,906 @@
+/*
+ * 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>
+/* Needed for SUBSCRIBE, NOTIFY, and PUBLISH method definitions */
+#include <pjsip_simple.h>
+#include <pjlib.h>
+
+#include "asterisk/res_sip.h"
+#include "res_sip/include/res_sip_private.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/logger.h"
+#include "asterisk/lock.h"
+#include "asterisk/utils.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/module.h"
+#include "asterisk/threadpool.h"
+#include "asterisk/taskprocessor.h"
+#include "asterisk/uuid.h"
+#include "asterisk/sorcery.h"
+
+/*** MODULEINFO
+ <depend>pjproject</depend>
+ <depend>res_sorcery_config</depend>
+ <support_level>core</support_level>
+ ***/
+
+static pjsip_endpoint *ast_pjsip_endpoint;
+
+static struct ast_threadpool *sip_threadpool;
+
+static int register_service(void *data)
+{
+ pjsip_module **module = data;
+ if (!ast_pjsip_endpoint) {
+ ast_log(LOG_ERROR, "There is no PJSIP endpoint. Unable to register services\n");
+ return -1;
+ }
+ if (pjsip_endpt_register_module(ast_pjsip_endpoint, *module) != PJ_SUCCESS) {
+ ast_log(LOG_ERROR, "Unable to register module %.*s\n", (int) pj_strlen(&(*module)->name), pj_strbuf(&(*module)->name));
+ return -1;
+ }
+ ast_debug(1, "Registered SIP service %.*s (%p)\n", (int) pj_strlen(&(*module)->name), pj_strbuf(&(*module)->name), *module);
+ ast_module_ref(ast_module_info->self);
+ return 0;
+}
+
+int ast_sip_register_service(pjsip_module *module)
+{
+ return ast_sip_push_task_synchronous(NULL, register_service, &module);
+}
+
+static int unregister_service(void *data)
+{
+ pjsip_module **module = data;
+ ast_module_unref(ast_module_info->self);
+ if (!ast_pjsip_endpoint) {
+ return -1;
+ }
+ pjsip_endpt_unregister_module(ast_pjsip_endpoint, *module);
+ ast_debug(1, "Unregistered SIP service %.*s\n", (int) pj_strlen(&(*module)->name), pj_strbuf(&(*module)->name));
+ return 0;
+}
+
+void ast_sip_unregister_service(pjsip_module *module)
+{
+ ast_sip_push_task_synchronous(NULL, unregister_service, &module);
+}
+
+static struct ast_sip_authenticator *registered_authenticator;
+
+int ast_sip_register_authenticator(struct ast_sip_authenticator *auth)
+{
+ if (registered_authenticator) {
+ ast_log(LOG_WARNING, "Authenticator %p is already registered. Cannot register a new one\n", registered_authenticator);
+ return -1;
+ }
+ registered_authenticator = auth;
+ ast_debug(1, "Registered SIP authenticator module %p\n", auth);
+ ast_module_ref(ast_module_info->self);
+ return 0;
+}
+
+void ast_sip_unregister_authenticator(struct ast_sip_authenticator *auth)
+{
+ if (registered_authenticator != auth) {
+ ast_log(LOG_WARNING, "Trying to unregister authenticator %p but authenticator %p registered\n",
+ auth, registered_authenticator);
+ return;
+ }
+ registered_authenticator = NULL;
+ ast_debug(1, "Unregistered SIP authenticator %p\n", auth);
+ ast_module_unref(ast_module_info->self);
+}
+
+int ast_sip_requires_authentication(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
+{
+ if (!registered_authenticator) {
+ ast_log(LOG_WARNING, "No SIP authenticator registered. Assuming authentication is not required\n");
+ return 0;
+ }
+
+ return registered_authenticator->requires_authentication(endpoint, rdata);
+}
+
+enum ast_sip_check_auth_result ast_sip_check_authentication(struct ast_sip_endpoint *endpoint,
+ pjsip_rx_data *rdata, pjsip_tx_data *tdata)
+{
+ if (!registered_authenticator) {
+ ast_log(LOG_WARNING, "No SIP authenticator registered. Assuming authentication is successful\n");
+ return 0;
+ }
+ return registered_authenticator->check_authentication(endpoint, rdata, tdata);
+}
+
+static struct ast_sip_outbound_authenticator *registered_outbound_authenticator;
+
+int ast_sip_register_outbound_authenticator(struct ast_sip_outbound_authenticator *auth)
+{
+ if (registered_outbound_authenticator) {
+ ast_log(LOG_WARNING, "Outbound authenticator %p is already registered. Cannot register a new one\n", registered_outbound_authenticator);
+ return -1;
+ }
+ registered_outbound_authenticator = auth;
+ ast_debug(1, "Registered SIP outbound authenticator module %p\n", auth);
+ ast_module_ref(ast_module_info->self);
+ return 0;
+}
+
+void ast_sip_unregister_outbound_authenticator(struct ast_sip_outbound_authenticator *auth)
+{
+ if (registered_outbound_authenticator != auth) {
+ ast_log(LOG_WARNING, "Trying to unregister outbound authenticator %p but outbound authenticator %p registered\n",
+ auth, registered_outbound_authenticator);
+ return;
+ }
+ registered_outbound_authenticator = NULL;
+ ast_debug(1, "Unregistered SIP outbound authenticator %p\n", auth);
+ ast_module_unref(ast_module_info->self);
+}
+
+int ast_sip_create_request_with_auth(const char **auths, size_t num_auths, pjsip_rx_data *challenge,
+ pjsip_transaction *tsx, pjsip_tx_data **new_request)
+{
+ if (!registered_outbound_authenticator) {
+ ast_log(LOG_WARNING, "No SIP outbound authenticator registered. Cannot respond to authentication challenge\n");
+ return -1;
+ }
+ return registered_outbound_authenticator->create_request_with_auth(auths, num_auths, challenge, tsx, new_request);
+}
+
+struct endpoint_identifier_list {
+ struct ast_sip_endpoint_identifier *identifier;
+ AST_RWLIST_ENTRY(endpoint_identifier_list) list;
+};
+
+static AST_RWLIST_HEAD_STATIC(endpoint_identifiers, endpoint_identifier_list);
+
+int ast_sip_register_endpoint_identifier(struct ast_sip_endpoint_identifier *identifier)
+{
+ struct endpoint_identifier_list *id_list_item;
+ SCOPED_LOCK(lock, &endpoint_identifiers, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+
+ id_list_item = ast_calloc(1, sizeof(*id_list_item));
+ if (!id_list_item) {
+ ast_log(LOG_ERROR, "Unabled to add endpoint identifier. Out of memory.\n");
+ return -1;
+ }
+ id_list_item->identifier = identifier;
+
+ AST_RWLIST_INSERT_TAIL(&endpoint_identifiers, id_list_item, list);
+ ast_debug(1, "Registered endpoint identifier %p\n", identifier);
+
+ ast_module_ref(ast_module_info->self);
+ return 0;
+}
+
+void ast_sip_unregister_endpoint_identifier(struct ast_sip_endpoint_identifier *identifier)
+{
+ struct endpoint_identifier_list *iter;
+ SCOPED_LOCK(lock, &endpoint_identifiers, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&endpoint_identifiers, iter, list) {
+ if (iter->identifier == identifier) {
+ AST_RWLIST_REMOVE_CURRENT(list);
+ ast_free(iter);
+ ast_debug(1, "Unregistered endpoint identifier %p\n", identifier);
+ ast_module_unref(ast_module_info->self);
+ break;
+ }
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+}
+
+struct ast_sip_endpoint *ast_sip_identify_endpoint(pjsip_rx_data *rdata)
+{
+ struct endpoint_identifier_list *iter;
+ struct ast_sip_endpoint *endpoint = NULL;
+ SCOPED_LOCK(lock, &endpoint_identifiers, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
+ AST_RWLIST_TRAVERSE(&endpoint_identifiers, iter, list) {
+ ast_assert(iter->identifier->identify_endpoint != NULL);
+ endpoint = iter->identifier->identify_endpoint(rdata);
+ if (endpoint) {
+ break;
+ }
+ }
+ return endpoint;
+}
+
+pjsip_endpoint *ast_sip_get_pjsip_endpoint(void)
+{
+ return ast_pjsip_endpoint;
+}
+
+static int sip_dialog_create_from(pj_pool_t *pool, pj_str_t *from, const char *user, const pj_str_t *target, pjsip_tpselector *selector)
+{
+ pj_str_t tmp, local_addr;
+ pjsip_uri *uri;
+ pjsip_sip_uri *sip_uri;
+ pjsip_transport_type_e type = PJSIP_TRANSPORT_UNSPECIFIED;
+ int local_port;
+ char uuid_str[AST_UUID_STR_LEN];
+
+ if (!user) {
+ RAII_VAR(struct ast_uuid *, uuid, ast_uuid_generate(), ast_free_ptr);
+ if (!uuid) {
+ return -1;
+ }
+ user = ast_uuid_to_str(uuid, uuid_str, sizeof(uuid_str));
+ }
+
+ /* Parse the provided target URI so we can determine what transport it will end up using */
+ pj_strdup_with_null(pool, &tmp, target);
+
+ if (!(uri = pjsip_parse_uri(pool, tmp.ptr, tmp.slen, 0)) ||
+ (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri))) {
+ return -1;
+ }
+
+ sip_uri = pjsip_uri_get_uri(uri);
+
+ /* Determine the transport type to use */
+ if (PJSIP_URI_SCHEME_IS_SIPS(sip_uri)) {
+ type = PJSIP_TRANSPORT_TLS;
+ } else if (!sip_uri->transport_param.slen) {
+ type = PJSIP_TRANSPORT_UDP;
+ } else {
+ type = pjsip_transport_get_type_from_name(&sip_uri->transport_param);
+ }
+
+ if (type == PJSIP_TRANSPORT_UNSPECIFIED) {
+ return -1;
+ }
+
+ /* If the host is IPv6 turn the transport into an IPv6 version */
+ if (pj_strchr(&sip_uri->host, ':')) {
+ type = (pjsip_transport_type_e)(((int)type) + PJSIP_TRANSPORT_IPV6);
+ }
+
+ /* Get the local bound address for the transport that will be used when communicating with the provided URI */
+ if (pjsip_tpmgr_find_local_addr(pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()), pool, type, selector,
+ &local_addr, &local_port) != PJ_SUCCESS) {
+ return -1;
+ }
+
+ /* If IPv6 was not specified in the host but is in the transport, set the proper type */
+ if (!pj_strchr(&sip_uri->host, ':') && pj_strchr(&local_addr, ':')) {
+ type = (pjsip_transport_type_e)(((int)type) + PJSIP_TRANSPORT_IPV6);
+ }
+
+ from->ptr = pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
+ from->slen = pj_ansi_snprintf(from->ptr, PJSIP_MAX_URL_SIZE,
+ "<%s:%s@%s%.*s%s:%d%s%s>",
+ (pjsip_transport_get_flag_from_type(type) & PJSIP_TRANSPORT_SECURE) ? "sips" : "sip",
+ user,
+ (type & PJSIP_TRANSPORT_IPV6) ? "[" : "",
+ (int)local_addr.slen,
+ local_addr.ptr,
+ (type & PJSIP_TRANSPORT_IPV6) ? "]" : "",
+ local_port,
+ (type != PJSIP_TRANSPORT_UDP && type != PJSIP_TRANSPORT_UDP6) ? ";transport=" : "",
+ (type != PJSIP_TRANSPORT_UDP && type != PJSIP_TRANSPORT_UDP6) ? pjsip_transport_get_type_name(type) : "");
+
+ return 0;
+}
+
+static int sip_get_tpselector_from_endpoint(const struct ast_sip_endpoint *endpoint, pjsip_tpselector *selector)
+{
+ RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
+ const char *transport_name = endpoint->transport;
+
+ if (ast_strlen_zero(transport_name)) {
+ return 0;
+ }
+
+ transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_name);
+
+ if (!transport || !transport->state) {
+ return -1;
+ }
+
+ if (transport->type == AST_SIP_TRANSPORT_UDP) {
+ selector->type = PJSIP_TPSELECTOR_TRANSPORT;
+ selector->u.transport = transport->state->transport;
+ } else if (transport->type == AST_SIP_TRANSPORT_TCP || transport->type == AST_SIP_TRANSPORT_TLS) {
+ selector->type = PJSIP_TPSELECTOR_LISTENER;
+ selector->u.listener = transport->state->factory;
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+pjsip_dialog *ast_sip_create_dialog(const struct ast_sip_endpoint *endpoint, const char *uri, const char *request_user)
+{
+ pj_str_t local_uri = { "sip:temp@temp", 13 }, remote_uri;
+ pjsip_dialog *dlg = NULL;
+ const char *outbound_proxy = endpoint->outbound_proxy;
+ pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
+ static const pj_str_t HCONTACT = { "Contact", 7 };
+
+ pj_cstr(&remote_uri, uri);
+
+ if (pjsip_dlg_create_uac(pjsip_ua_instance(), &local_uri, NULL, &remote_uri, NULL, &dlg) != PJ_SUCCESS) {
+ return NULL;
+ }
+
+ if (sip_get_tpselector_from_endpoint(endpoint, &selector)) {
+ pjsip_dlg_terminate(dlg);
+ return NULL;
+ }
+
+ if (sip_dialog_create_from(dlg->pool, &local_uri, NULL, &remote_uri, &selector)) {
+ pjsip_dlg_terminate(dlg);
+ return NULL;
+ }
+
+ /* Update the dialog with the new local URI, we do it afterwards so we can use the dialog pool for construction */
+ pj_strdup_with_null(dlg->pool, &dlg->local.info_str, &local_uri);
+ dlg->local.info->uri = pjsip_parse_uri(dlg->pool, dlg->local.info_str.ptr, dlg->local.info_str.slen, 0);
+ dlg->local.contact = pjsip_parse_hdr(dlg->pool, &HCONTACT, local_uri.ptr, local_uri.slen, NULL);
+
+ /* If a request user has been specified and we are permitted to change it, do so */
+ if (!ast_strlen_zero(request_user) && (PJSIP_URI_SCHEME_IS_SIP(dlg->target) || PJSIP_URI_SCHEME_IS_SIPS(dlg->target))) {
+ pjsip_sip_uri *target = pjsip_uri_get_uri(dlg->target);
+ pj_strdup2(dlg->pool, &target->user, request_user);
+ }
+
+ /* We have to temporarily bump up the sess_count here so the dialog is not prematurely destroyed */
+ dlg->sess_count++;
+
+ pjsip_dlg_set_transport(dlg, &selector);
+
+ if (!ast_strlen_zero(outbound_proxy)) {
+ pjsip_route_hdr route_set, *route;
+ static const pj_str_t ROUTE_HNAME = { "Route", 5 };
+ pj_str_t tmp;
+
+ pj_list_init(&route_set);
+
+ pj_strdup2_with_null(dlg->pool, &tmp, outbound_proxy);
+ if (!(route = pjsip_parse_hdr(dlg->pool, &ROUTE_HNAME, tmp.ptr, tmp.slen, NULL))) {
+ pjsip_dlg_terminate(dlg);
+ return NULL;
+ }
+ pj_list_push_back(&route_set, route);
+
+ pjsip_dlg_set_route_set(dlg, &route_set);
+ }
+
+ dlg->sess_count--;
+
+ return dlg;
+}
+
+/* PJSIP doesn't know about the INFO method, so we have to define it ourselves */
+const pjsip_method pjsip_info_method = {PJSIP_OTHER_METHOD, {"INFO", 4} };
+
+static struct {
+ const char *method;
+ const pjsip_method *pmethod;
+} methods [] = {
+ { "INVITE", &pjsip_invite_method },
+ { "CANCEL", &pjsip_cancel_method },
+ { "ACK", &pjsip_ack_method },
+ { "BYE", &pjsip_bye_method },
+ { "REGISTER", &pjsip_register_method },
+ { "OPTIONS", &pjsip_options_method },
+ { "SUBSCRIBE", &pjsip_subscribe_method },
+ { "NOTIFY", &pjsip_notify_method },
+ { "PUBLISH", &pjsip_publish_method },
+ { "INFO", &pjsip_info_method },
+};
+
+static const pjsip_method *get_pjsip_method(const char *method)
+{
+ int i;
+ for (i = 0; i < ARRAY_LEN(methods); ++i) {
+ if (!strcmp(method, methods[i].method)) {
+ return methods[i].pmethod;
+ }
+ }
+ return NULL;
+}
+
+static int create_in_dialog_request(const pjsip_method *method, struct pjsip_dialog *dlg, pjsip_tx_data **tdata)
+{
+ if (pjsip_dlg_create_request(dlg, method, -1, tdata) != PJ_SUCCESS) {
+ ast_log(LOG_WARNING, "Unable to create in-dialog request.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int create_out_of_dialog_request(const pjsip_method *method, struct ast_sip_endpoint *endpoint,
+ const char *uri, pjsip_tx_data **tdata)
+{
+ RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
+ pj_str_t remote_uri;
+ pj_str_t from;
+ pj_pool_t *pool;
+ pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
+
+ if (ast_strlen_zero(uri)) {
+ contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
+ if (!contact || ast_strlen_zero(contact->uri)) {
+ ast_log(LOG_ERROR, "Unable to retrieve contact for endpoint %s\n",
+ ast_sorcery_object_get_id(endpoint));
+ return -1;
+ }
+
+ pj_cstr(&remote_uri, contact->uri);
+ } else {
+ pj_cstr(&remote_uri, uri);
+ }
+
+ if (sip_get_tpselector_from_endpoint(endpoint, &selector)) {
+ ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport selector for endpoint %s\n",
+ ast_sorcery_object_get_id(endpoint));
+ return -1;
+ }
+
+ pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Outbound request", 256, 256);
+
+ if (!pool) {
+ ast_log(LOG_ERROR, "Unable to create PJLIB memory pool\n");
+ return -1;
+ }
+
+ if (sip_dialog_create_from(pool, &from, NULL, &remote_uri, &selector)) {
+ ast_log(LOG_ERROR, "Unable to create From header for %.*s request to endpoint %s\n",
+ (int) pj_strlen(&method->name), pj_strbuf(&method->name), ast_sorcery_object_get_id(endpoint));
+ pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+ return -1;
+ }
+
+ if (pjsip_endpt_create_request(ast_sip_get_pjsip_endpoint(), method, &remote_uri,
+ &from, &remote_uri, &from, NULL, -1, NULL, tdata) != PJ_SUCCESS) {
+ ast_log(LOG_ERROR, "Unable to create outbound %.*s request to endpoint %s\n",
+ (int) pj_strlen(&method->name), pj_strbuf(&method->name), ast_sorcery_object_get_id(endpoint));
+ pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+ return -1;
+ }
+
+ /* We can release this pool since request creation copied all the necessary
+ * data into the outbound request's pool
+ */
+ pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+ return 0;
+}
+
+int ast_sip_create_request(const char *method, struct pjsip_dialog *dlg,
+ struct ast_sip_endpoint *endpoint, const char *uri, pjsip_tx_data **tdata)
+{
+ const pjsip_method *pmethod = get_pjsip_method(method);
+
+ if (!pmethod) {
+ ast_log(LOG_WARNING, "Unknown method '%s'. Cannot send request\n", method);
+ return -1;
+ }
+
+ if (dlg) {
+ return create_in_dialog_request(pmethod, dlg, tdata);
+ } else {
+ return create_out_of_dialog_request(pmethod, endpoint, uri, tdata);
+ }
+}
+
+static int send_in_dialog_request(pjsip_tx_data *tdata, struct pjsip_dialog *dlg)
+{
+ if (pjsip_dlg_send_request(dlg, tdata, -1, NULL) != PJ_SUCCESS) {
+ ast_log(LOG_WARNING, "Unable to send in-dialog request.\n");
+ return -1;
+ }
+ return 0;
+}
+
+static void send_request_cb(void *token, pjsip_event *e)
+{
+ RAII_VAR(struct ast_sip_endpoint *, endpoint, token, ao2_cleanup);
+ pjsip_transaction *tsx = e->body.tsx_state.tsx;
+ pjsip_rx_data *challenge = e->body.tsx_state.src.rdata;
+ pjsip_tx_data *tdata;
+
+ if (tsx->status_code != 401 && tsx->status_code != 407) {
+ return;
+ }
+
+ if (!ast_sip_create_request_with_auth(endpoint->sip_outbound_auths, endpoint->num_outbound_auths, challenge, tsx, &tdata)) {
+ pjsip_endpt_send_request(ast_sip_get_pjsip_endpoint(), tdata, -1, NULL, NULL);
+ }
+}
+
+static int send_out_of_dialog_request(pjsip_tx_data *tdata, struct ast_sip_endpoint *endpoint)
+{
+ ao2_ref(endpoint, +1);
+ if (pjsip_endpt_send_request(ast_sip_get_pjsip_endpoint(), tdata, -1, endpoint, send_request_cb) != PJ_SUCCESS) {
+ ast_log(LOG_ERROR, "Error attempting to send outbound %.*s request to endpoint %s\n",
+ (int) pj_strlen(&tdata->msg->line.req.method.name),
+ pj_strbuf(&tdata->msg->line.req.method.name),
+ ast_sorcery_object_get_id(endpoint));
+ ao2_ref(endpoint, -1);
+ return -1;
+ }
+
+ return 0;
+}
+
+int ast_sip_send_request(pjsip_tx_data *tdata, struct pjsip_dialog *dlg, struct ast_sip_endpoint *endpoint)
+{
+ ast_assert(tdata->msg->type == PJSIP_REQUEST_MSG);
+
+ if (dlg) {
+ return send_in_dialog_request(tdata, dlg);
+ } else {
+ return send_out_of_dialog_request(tdata, endpoint);
+ }
+}
+
+int ast_sip_add_header(pjsip_tx_data *tdata, const char *name, const char *value)
+{
+ pj_str_t hdr_name;
+ pj_str_t hdr_value;
+ pjsip_generic_string_hdr *hdr;
+
+ pj_cstr(&hdr_name, name);
+ pj_cstr(&hdr_value, value);
+
+ hdr = pjsip_generic_string_hdr_create(tdata->pool, &hdr_name, &hdr_value);
+
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) hdr);
+ return 0;
+}
+
+static pjsip_msg_body *ast_body_to_pjsip_body(pj_pool_t *pool, const struct ast_sip_body *body)
+{
+ pj_str_t type;
+ pj_str_t subtype;
+ pj_str_t body_text;
+
+ pj_cstr(&type, body->type);
+ pj_cstr(&subtype, body->subtype);
+ pj_cstr(&body_text, body->body_text);
+
+ return pjsip_msg_body_create(pool, &type, &subtype, &body_text);
+}
+
+int ast_sip_add_body(pjsip_tx_data *tdata, const struct ast_sip_body *body)
+{
+ pjsip_msg_body *pjsip_body = ast_body_to_pjsip_body(tdata->pool, body);
+ tdata->msg->body = pjsip_body;
+ return 0;
+}
+
+int ast_sip_add_body_multipart(pjsip_tx_data *tdata, const struct ast_sip_body *bodies[], int num_bodies)
+{
+ int i;
+ /* NULL for type and subtype automatically creates "multipart/mixed" */
+ pjsip_msg_body *body = pjsip_multipart_create(tdata->pool, NULL, NULL);
+
+ for (i = 0; i < num_bodies; ++i) {
+ pjsip_multipart_part *part = pjsip_multipart_create_part(tdata->pool);
+ part->body = ast_body_to_pjsip_body(tdata->pool, bodies[i]);
+ pjsip_multipart_add_part(tdata->pool, body, part);
+ }
+
+ tdata->msg->body = body;
+ return 0;
+}
+
+int ast_sip_append_body(pjsip_tx_data *tdata, const char *body_text)
+{
+ size_t combined_size = strlen(body_text) + tdata->msg->body->len;
+ struct ast_str *body_buffer = ast_str_alloca(combined_size);
+
+ ast_str_set(&body_buffer, 0, "%.*s%s", (int) tdata->msg->body->len, (char *) tdata->msg->body->data, body_text);
+
+ tdata->msg->body->data = pj_pool_alloc(tdata->pool, combined_size);
+ pj_memcpy(tdata->msg->body->data, ast_str_buffer(body_buffer), combined_size);
+ tdata->msg->body->len = combined_size;
+
+ return 0;
+}
+
+struct ast_taskprocessor *ast_sip_create_serializer(void)
+{
+ struct ast_taskprocessor *serializer;
+ RAII_VAR(struct ast_uuid *, uuid, ast_uuid_generate(), ast_free_ptr);
+ char name[AST_UUID_STR_LEN];
+
+ if (!uuid) {
+ return NULL;
+ }
+
+ ast_uuid_to_str(uuid, name, sizeof(name));
+
+ serializer = ast_threadpool_serializer(name, sip_threadpool);
+ if (!serializer) {
+ return NULL;
+ }
+ return serializer;
+}
+
+int ast_sip_push_task(struct ast_taskprocessor *serializer, int (*sip_task)(void *), void *task_data)
+{
+ if (serializer) {
+ return ast_taskprocessor_push(serializer, sip_task, task_data);
+ } else {
+ return ast_threadpool_push(sip_threadpool, sip_task, task_data);
+ }
+}
+
+struct sync_task_data {
+ ast_mutex_t lock;
+ ast_cond_t cond;
+ int complete;
+ int fail;
+ int (*task)(void *);
+ void *task_data;
+};
+
+static int sync_task(void *data)
+{
+ struct sync_task_data *std = data;
+ std->fail = std->task(std->task_data);
+
+ ast_mutex_lock(&std->lock);
+ std->complete = 1;
+ ast_cond_signal(&std->cond);
+ ast_mutex_unlock(&std->lock);
+ return std->fail;
+}
+
+int ast_sip_push_task_synchronous(struct ast_taskprocessor *serializer, int (*sip_task)(void *), void *task_data)
+{
+ /* This method is an onion */
+ struct sync_task_data std;
+ ast_mutex_init(&std.lock);
+ ast_cond_init(&std.cond, NULL);
+ std.fail = std.complete = 0;
+ std.task = sip_task;
+ std.task_data = task_data;
+
+ if (serializer) {
+ if (ast_taskprocessor_push(serializer, sync_task, &std)) {
+ return -1;
+ }
+ } else {
+ if (ast_threadpool_push(sip_threadpool, sync_task, &std)) {
+ return -1;
+ }
+ }
+
+ ast_mutex_lock(&std.lock);
+ while (!std.complete) {
+ ast_cond_wait(&std.cond, &std.lock);
+ }
+ ast_mutex_unlock(&std.lock);
+
+ ast_mutex_destroy(&std.lock);
+ ast_cond_destroy(&std.cond);
+ return std.fail;
+}
+
+void ast_copy_pj_str(char *dest, pj_str_t *src, size_t size)
+{
+ size_t chars_to_copy = MIN(size - 1, pj_strlen(src));
+ memcpy(dest, pj_strbuf(src), chars_to_copy);
+ dest[chars_to_copy] = '\0';
+}
+
+pj_caching_pool caching_pool;
+pj_pool_t *memory_pool;
+pj_thread_t *monitor_thread;
+static int monitor_continue;
+
+static void *monitor_thread_exec(void *endpt)
+{
+ while (monitor_continue) {
+ const pj_time_val delay = {0, 10};
+ pjsip_endpt_handle_events(ast_pjsip_endpoint, &delay);
+ }
+ return NULL;
+}
+
+static void stop_monitor_thread(void)
+{
+ monitor_continue = 0;
+ pj_thread_join(monitor_thread);
+}
+
+AST_THREADSTORAGE(pj_thread_storage);
+AST_THREADSTORAGE(servant_id_storage);
+#define SIP_SERVANT_ID 0xDEFECA7E
+
+static void sip_thread_start(void)
+{
+ pj_thread_desc *desc;
+ pj_thread_t *thread;
+ uint32_t *servant_id;
+
+ servant_id = ast_threadstorage_get(&servant_id_storage, sizeof(*servant_id));
+ if (!servant_id) {
+ ast_log(LOG_ERROR, "Could not set SIP servant ID in thread-local storage.\n");
+ return;
+ }
+ *servant_id = SIP_SERVANT_ID;
+
+ desc = ast_threadstorage_get(&pj_thread_storage, sizeof(pj_thread_desc));
+ if (!desc) {
+ ast_log(LOG_ERROR, "Could not get thread desc from thread-local storage. Expect awful things to occur\n");
+ return;
+ }
+ pj_bzero(*desc, sizeof(*desc));
+
+ if (pj_thread_register("Asterisk Thread", *desc, &thread) != PJ_SUCCESS) {
+ ast_log(LOG_ERROR, "Couldn't register thread with PJLIB.\n");
+ }
+}
+
+int ast_sip_thread_is_servant(void)
+{
+ uint32_t *servant_id;
+
+ servant_id = ast_threadstorage_get(&servant_id_storage, sizeof(*servant_id));
+ if (!servant_id) {
+ return 0;
+ }
+
+ return *servant_id == SIP_SERVANT_ID;
+}
+
+static int load_module(void)
+{
+ /* The third parameter is just copied from
+ * example code from PJLIB. This can be adjusted
+ * if necessary.
+ */
+ pj_status_t status;
+
+ /* XXX For the time being, create hard-coded threadpool
+ * options. Just bump up by five threads every time we
+ * don't have any available threads. Idle threads time
+ * out after a minute. No maximum size
+ */
+ struct ast_threadpool_options options = {
+ .version = AST_THREADPOOL_OPTIONS_VERSION,
+ .auto_increment = 5,
+ .max_size = 0,
+ .idle_timeout = 60,
+ .initial_size = 0,
+ .thread_start = sip_thread_start,
+ };
+ sip_threadpool = ast_threadpool_create("SIP", NULL, &options);
+
+ if (pj_init() != PJ_SUCCESS) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ if (pjlib_util_init() != PJ_SUCCESS) {
+ pj_shutdown();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ pj_caching_pool_init(&caching_pool, NULL, 1024 * 1024);
+ if (pjsip_endpt_create(&caching_pool.factory, "SIP", &ast_pjsip_endpoint) != PJ_SUCCESS) {
+ ast_log(LOG_ERROR, "Failed to create PJSIP endpoint structure. Aborting load\n");
+ goto error;
+ }
+ memory_pool = pj_pool_create(&caching_pool.factory, "SIP", 1024, 1024, NULL);
+ if (!memory_pool) {
+ ast_log(LOG_ERROR, "Failed to create memory pool for SIP. Aborting load\n");
+ goto error;
+ }
+
+ pjsip_tsx_layer_init_module(ast_pjsip_endpoint);
+ pjsip_ua_init_module(ast_pjsip_endpoint, NULL);
+
+ monitor_continue = 1;
+ status = pj_thread_create(memory_pool, "SIP", (pj_thread_proc *) &monitor_thread_exec,
+ NULL, PJ_THREAD_DEFAULT_STACK_SIZE * 2, 0, &monitor_thread);
+ if (status != PJ_SUCCESS) {
+ ast_log(LOG_ERROR, "Failed to start SIP monitor thread. Aborting load\n");
+ goto error;
+ }
+
+ if (ast_res_sip_initialize_configuration()) {
+ ast_log(LOG_ERROR, "Failed to initialize SIP configuration. Aborting load\n");
+ goto error;
+ }
+
+ if (ast_sip_initialize_distributor()) {
+ ast_log(LOG_ERROR, "Failed to register distributor module. Aborting load\n");
+ goto error;
+ }
+
+ if (ast_sip_initialize_outbound_authentication()) {
+ ast_log(LOG_ERROR, "Failed to initialize outbound authentication. Aborting load\n");
+ goto error;
+ }
+
+ ast_res_sip_init_options_handling(0);
+
+return AST_MODULE_LOAD_SUCCESS;
+
+error:
+ ast_res_sip_destroy_configuration();
+ if (monitor_thread) {
+ stop_monitor_thread();
+ }
+ if (memory_pool) {
+ pj_pool_release(memory_pool);
+ memory_pool = NULL;
+ }
+ if (ast_pjsip_endpoint) {
+ pjsip_endpt_destroy(ast_pjsip_endpoint);
+ ast_pjsip_endpoint = NULL;
+ }
+ pj_caching_pool_destroy(&caching_pool);
+ /* XXX Should have a way of stopping monitor thread */
+ return AST_MODULE_LOAD_DECLINE;
+}
+
+static int reload_module(void)
+{
+ if (ast_res_sip_reload_configuration()) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ ast_res_sip_init_options_handling(1);
+ return 0;
+}
+
+static int unload_pjsip(void *data)
+{
+ if (memory_pool) {
+ pj_pool_release(memory_pool);
+ memory_pool = NULL;
+ }
+ if (ast_pjsip_endpoint) {
+ pjsip_endpt_destroy(ast_pjsip_endpoint);
+ ast_pjsip_endpoint = NULL;
+ }
+ pj_caching_pool_destroy(&caching_pool);
+ return 0;
+}
+
+static int unload_module(void)
+{
+ ast_res_sip_destroy_configuration();
+ if (monitor_thread) {
+ stop_monitor_thread();
+ }
+ /* The thread this is called from cannot call PJSIP/PJLIB functions,
+ * so we have to push the work to the threadpool to handle
+ */
+ ast_sip_push_task_synchronous(NULL, unload_pjsip, NULL);
+
+ ast_threadpool_shutdown(sip_threadpool);
+
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Basic SIP resource",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload_module,
+ .load_pri = AST_MODPRI_CHANNEL_DEPEND,
+);
diff --git a/res/res_sip.exports.in b/res/res_sip.exports.in
new file mode 100644
index 000000000..010f90cb1
--- /dev/null
+++ b/res/res_sip.exports.in
@@ -0,0 +1,52 @@
+{
+ global:
+ LINKER_SYMBOL_PREFIXast_sip_register_service;
+ LINKER_SYMBOL_PREFIXast_sip_unregister_service;
+ LINKER_SYMBOL_PREFIXast_sip_register_authenticator;
+ LINKER_SYMBOL_PREFIXast_sip_unregister_authenticator;
+ LINKER_SYMBOL_PREFIXast_sip_register_outbound_authenticator;
+ LINKER_SYMBOL_PREFIXast_sip_unregister_outbound_authenticator;
+ LINKER_SYMBOL_PREFIXast_sip_register_endpoint_identifier;
+ LINKER_SYMBOL_PREFIXast_sip_unregister_endpoint_identifier;
+ LINKER_SYMBOL_PREFIXast_sip_create_serializer;
+ LINKER_SYMBOL_PREFIXast_sip_push_task;
+ LINKER_SYMBOL_PREFIXast_sip_push_task_synchronous;
+ LINKER_SYMBOL_PREFIXast_sip_create_request;
+ LINKER_SYMBOL_PREFIXast_sip_create_request_with_auth;
+ LINKER_SYMBOL_PREFIXast_sip_send_request;
+ LINKER_SYMBOL_PREFIXast_sip_requires_authentication;
+ LINKER_SYMBOL_PREFIXast_sip_authenticate_request;
+ LINKER_SYMBOL_PREFIXast_sip_get_authentication_credentials;
+ LINKER_SYMBOL_PREFIXast_sip_check_authentication;
+ LINKER_SYMBOL_PREFIXast_sip_create_auth_challenge_response;
+ LINKER_SYMBOL_PREFIXast_sip_set_outbound_authentication_credentials;
+ LINKER_SYMBOL_PREFIXast_sip_dialog_setup_outbound_authentication;
+ LINKER_SYMBOL_PREFIXast_sip_add_digest_to_challenge;
+ LINKER_SYMBOL_PREFIXast_sip_identify_endpoint;
+ LINKER_SYMBOL_PREFIXast_sip_add_header;
+ LINKER_SYMBOL_PREFIXast_sip_add_body;
+ LINKER_SYMBOL_PREFIXast_sip_add_body_multipart;
+ LINKER_SYMBOL_PREFIXast_sip_append_body;
+ LINKER_SYMBOL_PREFIXast_sip_get_pjsip_endpoint;
+ LINKER_SYMBOL_PREFIXast_sip_endpoint_alloc;
+ LINKER_SYMBOL_PREFIXast_copy_pj_str;
+ LINKER_SYMBOL_PREFIXast_sip_get_sorcery;
+ LINKER_SYMBOL_PREFIXast_sip_create_dialog;
+ LINKER_SYMBOL_PREFIXast_sip_location_retrieve_aor;
+ LINKER_SYMBOL_PREFIXast_sip_location_retrieve_first_aor_contact;
+ LINKER_SYMBOL_PREFIXast_sip_location_retrieve_contact_from_aor_list;
+ LINKER_SYMBOL_PREFIXast_sip_location_retrieve_aor_contacts;
+ LINKER_SYMBOL_PREFIXast_sip_location_retrieve_contact;
+ LINKER_SYMBOL_PREFIXast_sip_location_add_contact;
+ LINKER_SYMBOL_PREFIXast_sip_location_update_contact;
+ LINKER_SYMBOL_PREFIXast_sip_location_delete_contact;
+ LINKER_SYMBOL_PREFIXast_pjsip_rdata_get_endpoint;
+ LINKER_SYMBOL_PREFIXast_sip_thread_is_servant;
+ LINKER_SYMBOL_PREFIXast_sip_dialog_set_serializer;
+ LINKER_SYMBOL_PREFIXast_sip_dialog_set_endpoint;
+ LINKER_SYMBOL_PREFIXast_sip_dialog_get_endpoint;
+ LINKER_SYMBOL_PREFIXast_sip_retrieve_auths;
+ LINKER_SYMBOL_PREFIXast_sip_cleanup_auths;
+ local:
+ *;
+};
diff --git a/res/res_sip/config_auth.c b/res/res_sip/config_auth.c
new file mode 100644
index 000000000..9881dd8c6
--- /dev/null
+++ b/res/res_sip/config_auth.c
@@ -0,0 +1,120 @@
+/*
+ * 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_sip.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 = ao2_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;
+ }
+ 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", "res_sip.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_sip/config_domain_aliases.c b/res/res_sip/config_domain_aliases.c
new file mode 100644
index 000000000..86b4636ea
--- /dev/null
+++ b/res/res_sip/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_sip.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 = ao2_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", "res_sip.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_sip/config_transport.c b/res/res_sip/config_transport.c
new file mode 100644
index 000000000..eb89ee44e
--- /dev/null
+++ b/res/res_sip/config_transport.c
@@ -0,0 +1,299 @@
+/*
+ * 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_sip.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 = ao2_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;
+}
+
+/*! \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;
+ }
+
+ /* Set default port if not present */
+ if (!pj_sockaddr_get_port(&transport->host)) {
+ pj_sockaddr_set_port(&transport->host, (transport->type == AST_SIP_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_SIP_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);
+ }
+ } else if (transport->type == AST_SIP_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;
+
+ res = pjsip_tcp_transport_start3(ast_sip_get_pjsip_endpoint(), &cfg, &transport->state->factory);
+ } else if (transport->type == AST_SIP_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);
+
+ res = pjsip_tls_transport_start2(ast_sip_get_pjsip_endpoint(), &transport->tls, &transport->host, NULL, transport->async_operations, &transport->state->factory);
+ }
+
+ 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_SIP_TRANSPORT_UDP;
+ } else if (!strcasecmp(var->value, "tcp")) {
+ transport->type = AST_SIP_TRANSPORT_TCP;
+ } else if (!strcasecmp(var->value, "tls")) {
+ transport->type = AST_SIP_TRANSPORT_TLS;
+ } else {
+ /* TODO: Implement websockets */
+ 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", "res_sip.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);
+
+ return 0;
+}
diff --git a/res/res_sip/include/res_sip_private.h b/res/res_sip/include/res_sip_private.h
new file mode 100644
index 000000000..318510aae
--- /dev/null
+++ b/res/res_sip/include/res_sip_private.h
@@ -0,0 +1,57 @@
+/*
+ * res_sip.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_sip
+ */
+int ast_res_sip_initialize_configuration(void);
+
+/*!
+ * \brief Annihilate the configuration objects
+ */
+void ast_res_sip_destroy_configuration(void);
+
+/*!
+ * \brief Reload the configuration
+ */
+int ast_res_sip_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_sip_init_options_handling(int reload);
+
+/*!
+ * \brief Initialize outbound authentication support
+ *
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+int ast_sip_initialize_outbound_authentication(void);
+
+/*!
+ * \brief Get the current defined endpoints
+ *
+ * \retval The current endpoints loaded by res_sip
+ */
+struct ao2_container *ast_res_sip_get_endpoints(void);
+
+#endif /* RES_SIP_PRIVATE_H_ */
diff --git a/res/res_sip/location.c b/res/res_sip/location.c
new file mode 100644
index 000000000..91521c813
--- /dev/null
+++ b/res/res_sip/location.c
@@ -0,0 +1,262 @@
+/*
+ * 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_sip.h"
+#include "asterisk/logger.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/sorcery.h"
+
+/*! \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 = ao2_alloc_options(sizeof(struct ast_sip_aor), aor_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+ 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 = ao2_alloc_options(sizeof(*contact), contact_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+
+ if (!contact) {
+ return NULL;
+ }
+
+ if (ast_string_field_init(contact, 256)) {
+ ao2_cleanup(contact);
+ return NULL;
+ }
+
+ return contact;
+}
+
+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;
+
+ 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", "res_sip.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, "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", "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;
+}
diff --git a/res/res_sip/sip_configuration.c b/res/res_sip/sip_configuration.c
new file mode 100644
index 000000000..489ba6aec
--- /dev/null
+++ b/res/res_sip/sip_configuration.c
@@ -0,0 +1,463 @@
+/*
+ * sip_cli_commands.c
+ *
+ * Created on: Jan 25, 2013
+ * Author: mjordan
+ */
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+
+#include "asterisk/res_sip.h"
+#include "include/res_sip_private.h"
+#include "asterisk/cli.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/utils.h"
+#include "asterisk/sorcery.h"
+#include "asterisk/callerid.h"
+
+static struct ast_sorcery *sip_sorcery;
+
+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 = "sip show endpoints";
+ e->usage =
+ "Usage: sip show endpoints\n"
+ " Show the registered SIP endpoints\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ endpoints = ast_res_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 struct ast_cli_entry cli_commands[] = {
+ AST_CLI_DEFINE(handle_cli_show_endpoints, "Show SIP Endpoints"),
+};
+
+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 |= PJSIP_INV_SUPPORT_100REL;
+ } else if (ast_false(var->value)) {
+ endpoint->extensions &= PJSIP_INV_SUPPORT_100REL;
+ } else if (!strcasecmp(var->value, "required")) {
+ endpoint->extensions |= 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 |= PJSIP_INV_SUPPORT_TIMER;
+ } else if (ast_false(var->value)) {
+ endpoint->extensions &= PJSIP_INV_SUPPORT_TIMER;
+ } else if (!strcasecmp(var->value, "required")) {
+ endpoint->extensions |= PJSIP_INV_REQUIRE_TIMER;
+ } else if (!strcasecmp(var->value, "always")) {
+ endpoint->extensions |= PJSIP_INV_ALWAYS_USE_TIMER;
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+static void destroy_auths(const char **auths, size_t num_auths)
+{
+ int i;
+ for (i = 0; i < num_auths; ++i) {
+ ast_free((char *) auths[i]);
+ }
+ ast_free(auths);
+}
+
+#define AUTH_INCREMENT 4
+
+static const char **auth_alloc(const char *value, size_t *num_auths)
+{
+ char *auths = ast_strdupa(value);
+ char *val;
+ int num_alloced = 0;
+ const char **alloced_auths = NULL;
+
+ while ((val = strsep(&auths, ","))) {
+ if (*num_auths >= num_alloced) {
+ size_t size;
+ num_alloced += AUTH_INCREMENT;
+ size = num_alloced * sizeof(char *);
+ alloced_auths = ast_realloc(alloced_auths, size);
+ if (!alloced_auths) {
+ goto failure;
+ }
+ }
+ alloced_auths[*num_auths] = ast_strdup(val);
+ if (!alloced_auths[*num_auths]) {
+ goto failure;
+ }
+ ++(*num_auths);
+ }
+ return alloced_auths;
+
+failure:
+ destroy_auths(alloced_auths, *num_auths);
+ return NULL;
+}
+
+static int inbound_auth_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct ast_sip_endpoint *endpoint = obj;
+
+ endpoint->sip_inbound_auths = auth_alloc(var->value, &endpoint->num_inbound_auths);
+ if (!endpoint->sip_inbound_auths) {
+ return -1;
+ }
+ return 0;
+}
+static int outbound_auth_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct ast_sip_endpoint *endpoint = obj;
+
+ endpoint->sip_outbound_auths = auth_alloc(var->value, &endpoint->num_outbound_auths);
+ if (!endpoint->sip_outbound_auths) {
+ return -1;
+ }
+ return 0;
+}
+
+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 if (!strcasecmp(val, "location")) {
+ endpoint->ident_method |= AST_SIP_ENDPOINT_IDENTIFY_BY_LOCATION;
+ } 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->direct_media_method = AST_SIP_SESSION_REFRESH_METHOD_INVITE;
+ } else if (!strcasecmp(var->value, "update")) {
+ endpoint->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 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->direct_media_glare_mitigation = AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_NONE;
+ } else if (!strcasecmp(var->value, "outgoing")) {
+ endpoint->direct_media_glare_mitigation = AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_OUTGOING;
+ } else if (!strcasecmp(var->value, "incoming")) {
+ endpoint->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.name.str = ast_strdup(cid_name);
+ if (!endpoint->id.name.str) {
+ return -1;
+ }
+ endpoint->id.name.valid = 1;
+ }
+ if (!ast_strlen_zero(cid_num)) {
+ endpoint->id.number.str = ast_strdup(cid_num);
+ if (!endpoint->id.number.str) {
+ return -1;
+ }
+ endpoint->id.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.number.presentation = callingpres;
+ endpoint->id.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.tag = ast_strdup(var->value);
+ return endpoint->id.tag ? 0 : -1;
+}
+
+static void *sip_nat_hook_alloc(const char *name)
+{
+ return ao2_alloc(sizeof(struct ast_sip_nat_hook), NULL);
+}
+
+int ast_res_sip_initialize_configuration(void)
+{
+ if (ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands))) {
+ 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_sip");
+
+ 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", "res_sip.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, NULL)) {
+ 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, prefs, codecs));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow", "", OPT_CODEC_T, 1, FLDSET(struct ast_sip_endpoint, prefs, codecs));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "qualify_frequency", 0, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_endpoint, qualify_frequency), 0, 86400);
+ 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, rtp_ipv6));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_symmetric", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, rtp_symmetric));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "ice_support", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, ice_support));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "use_ptime", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, use_ptime));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "force_rport", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, force_rport));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rewrite_contact", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, 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, min_se));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "timers_sess_expires", "1800", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, 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, external_media_address));
+ ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "identify_by", "username,location", 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, direct_media));
+ 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", "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, disable_direct_media_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, trust_id_inbound));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "trust_id_outbound", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, trust_id_outbound));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_pai", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, send_pai));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_rpid", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, send_rpid));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mailboxes", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, mailboxes));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "aggregate_mwi", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, aggregate_mwi));
+
+ 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_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;
+ }
+
+ ast_sorcery_load(sip_sorcery);
+
+ return 0;
+}
+
+void ast_res_sip_destroy_configuration(void)
+{
+ ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
+ ast_sorcery_unref(sip_sorcery);
+}
+
+int ast_res_sip_reload_configuration(void)
+{
+ if (sip_sorcery) {
+ ast_sorcery_reload(sip_sorcery);
+ }
+ return 0;
+}
+
+static void endpoint_destructor(void* obj)
+{
+ struct ast_sip_endpoint *endpoint = obj;
+
+ ast_string_field_free_memory(endpoint);
+
+ if (endpoint->codecs) {
+ ast_format_cap_destroy(endpoint->codecs);
+ }
+ destroy_auths(endpoint->sip_inbound_auths, endpoint->num_inbound_auths);
+ destroy_auths(endpoint->sip_outbound_auths, endpoint->num_outbound_auths);
+ ast_party_id_free(&endpoint->id);
+}
+
+void *ast_sip_endpoint_alloc(const char *name)
+{
+ struct ast_sip_endpoint *endpoint = ao2_alloc(sizeof(*endpoint), endpoint_destructor);
+ if (!endpoint) {
+ return NULL;
+ }
+ if (ast_string_field_init(endpoint, 64)) {
+ ao2_cleanup(endpoint);
+ return NULL;
+ }
+ if (!(endpoint->codecs = ast_format_cap_alloc_nolock())) {
+ ao2_cleanup(endpoint);
+ return NULL;
+ }
+ ast_party_id_init(&endpoint->id);
+ return endpoint;
+}
+
+struct ao2_container *ast_res_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 char *auth_names[], size_t num_auths, struct ast_sip_auth **out)
+{
+ int i;
+
+ for (i = 0; i < num_auths; ++i) {
+ out[i] = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), SIP_SORCERY_AUTH_TYPE, auth_names[i]);
+ if (!out[i]) {
+ ast_log(LOG_NOTICE, "Couldn't find auth '%s'. Cannot authenticate\n", auth_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_sip/sip_distributor.c b/res/res_sip/sip_distributor.c
new file mode 100644
index 000000000..766261089
--- /dev/null
+++ b/res/res_sip/sip_distributor.c
@@ -0,0 +1,239 @@
+/*
+ * 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_sip.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;
+
+ pjsip_rx_data_clone(rdata, 0, &clone);
+ if (dlg) {
+ dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id);
+ if (dist) {
+ serializer = dist->serializer;
+ clone->endpt_info.mod_data[distributor_mod.id] = dist->endpoint;
+ }
+ }
+
+ ast_sip_push_task(serializer, distribute, clone);
+
+ 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 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) {
+ /* XXX When we do an alwaysauthreject-like option, we'll need to take that into account
+ * for this response. Either that, or have a pseudo-endpoint to pass along so that authentication
+ * will fail
+ */
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+ return PJ_TRUE;
+ }
+ 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 */
+ pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL);
+ return PJ_TRUE;
+ case AST_SIP_AUTHENTICATION_SUCCESS:
+ pjsip_tx_data_dec_ref(tdata);
+ return PJ_FALSE;
+ case AST_SIP_AUTHENTICATION_FAILED:
+ pjsip_tx_data_dec_ref(tdata);
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+ return PJ_TRUE;
+ case AST_SIP_AUTHENTICATION_ERROR:
+ 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 (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;
+}
diff --git a/res/res_sip/sip_options.c b/res/res_sip/sip_options.c
new file mode 100644
index 000000000..5e3f8edca
--- /dev/null
+++ b/res/res_sip/sip_options.c
@@ -0,0 +1,378 @@
+/*
+ * sip_options.c
+ *
+ * Created on: Jan 25, 2013
+ * Author: mjordan
+ */
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+#include <pjlib.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/cli.h"
+#include "include/res_sip_private.h"
+
+#define DEFAULT_LANGUAGE "en"
+#define DEFAULT_ENCODING "text/plain"
+#define QUALIFIED_BUCKETS 211
+
+/*! \brief Scheduling context for qualifies */
+static struct ast_sched_context *sched; /* XXX move this to registrar */
+
+struct ao2_container *scheduled_qualifies;
+
+struct qualify_info {
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(endpoint_id);
+ );
+ char *scheduler_data;
+ int scheduler_id;
+};
+
+static pj_bool_t options_module_start(void);
+static pj_bool_t options_module_stop(void);
+static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata);
+static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata);
+
+static pjsip_module options_module = {
+ .name = {"Options Module", 14},
+ .id = -1,
+ .priority = PJSIP_MOD_PRIORITY_APPLICATION,
+ .start = options_module_start,
+ .stop = options_module_stop,
+ .on_rx_request = options_module_on_rx_request,
+ .on_rx_response = options_module_on_rx_response,
+};
+
+static pj_bool_t options_module_start(void)
+{
+ if (!(sched = ast_sched_context_create()) ||
+ ast_sched_start_thread(sched)) {
+ return -1;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_bool_t options_module_stop(void)
+{
+ ao2_t_ref(scheduled_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, pjsip_dialog *pj_dlg, int code)
+{
+ pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint();
+ pjsip_transaction *pj_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 */
+ status = pjsip_endpt_create_response(endpt, rdata, code, NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ 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 (pj_dlg && pj_trans) {
+ status = pjsip_dlg_send_response(pj_dlg, pj_trans, tdata);
+ } else {
+ /* Get where to send request. */
+ status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+ status = pjsip_endpt_send_response(endpt, &res_addr, tdata, NULL, NULL);
+ }
+
+ return status;
+}
+
+static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata)
+{
+ RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
+ pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
+ 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;
+ }
+ endpoint = ast_pjsip_rdata_get_endpoint(rdata);
+ ast_assert(endpoint != NULL);
+
+ 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, dlg, 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, dlg, 503);
+ } else if (!ast_exists_extension(NULL, endpoint->context, exten, 1, NULL)) {
+ send_options_response(rdata, dlg, 404);
+ } else {
+ send_options_response(rdata, dlg, 200);
+ }
+ return PJ_TRUE;
+}
+
+static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata)
+{
+
+ return PJ_SUCCESS;
+}
+
+static int qualify_info_hash_fn(const void *obj, int flags)
+{
+ const struct qualify_info *info = obj;
+ const char *endpoint_id = flags & OBJ_KEY ? obj : info->endpoint_id;
+
+ return ast_str_hash(endpoint_id);
+}
+
+static int qualify_info_cmp_fn(void *obj, void *arg, int flags)
+{
+ struct qualify_info *left = obj;
+ struct qualify_info *right = arg;
+ const char *right_endpoint_id = flags & OBJ_KEY ? arg : right->endpoint_id;
+
+ return strcmp(left->endpoint_id, right_endpoint_id) ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+
+static void qualify_info_destructor(void *obj)
+{
+ struct qualify_info *info = obj;
+ if (!info) {
+ return;
+ }
+ ast_string_field_free_memory(info);
+ /* Cancel the qualify */
+ if (!AST_SCHED_DEL(sched, info->scheduler_id)) {
+ /* If we successfully deleted the qualify, we got it before it
+ * fired. We can safely delete the data that was passed to it.
+ * Otherwise, we're getting deleted while this is firing - don't
+ * touch that memory!
+ */
+ ast_free(info->scheduler_data);
+ }
+}
+
+static struct qualify_info *create_qualify_info(struct ast_sip_endpoint *endpoint)
+{
+ struct qualify_info *info;
+
+ info = ao2_alloc(sizeof(*info), qualify_info_destructor);
+ if (!info) {
+ return NULL;
+ }
+
+ if (ast_string_field_init(info, 64)) {
+ ao2_ref(info, -1);
+ return NULL;
+ }
+ ast_string_field_set(info, endpoint_id, ast_sorcery_object_get_id(endpoint));
+
+ return info;
+}
+
+static int send_qualify_request(void *data)
+{
+ struct ast_sip_endpoint *endpoint = data;
+ pjsip_tx_data *tdata;
+ /* YAY! Send an OPTIONS request. */
+
+ ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata);
+ ast_sip_send_request(tdata, NULL, endpoint);
+
+ ao2_cleanup(endpoint);
+ return 0;
+}
+
+static int qualify_endpoint_scheduler_cb(const void *data)
+{
+ RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
+ struct ast_sorcery *sorcery;
+ char *endpoint_id = (char *)data;
+
+ sorcery = ast_sip_get_sorcery();
+ if (!sorcery) {
+ ast_free(endpoint_id);
+ return 0;
+ }
+
+ endpoint = ast_sorcery_retrieve_by_id(sorcery, "endpoint", endpoint_id);
+ if (!endpoint) {
+ /* Whoops, endpoint went away */
+ ast_free(endpoint_id);
+ return 0;
+ }
+
+ ast_sip_push_task(NULL, send_qualify_request, endpoint);
+
+ return 1;
+}
+
+static void schedule_qualifies(void)
+{
+ RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup);
+ struct ao2_iterator it_endpoints;
+ struct ast_sip_endpoint *endpoint;
+ struct qualify_info *info;
+ char *endpoint_id;
+
+ endpoints = ast_res_sip_get_endpoints();
+ if (!endpoints) {
+ return;
+ }
+
+ it_endpoints = ao2_iterator_init(endpoints, 0);
+ while ((endpoint = ao2_iterator_next(&it_endpoints))) {
+ if (endpoint->qualify_frequency) {
+ /* XXX TODO: This really should only qualify registered peers,
+ * which means we need a registrar. We should check the
+ * registrar to see if this endpoint has registered and, if
+ * not, pass on it.
+ *
+ * Actually, all of this should just get moved into the registrar.
+ * Otherwise, the registar will have to kick this off when a
+ * new endpoint registers, so it just makes sense to have it
+ * all live there.
+ */
+ info = create_qualify_info(endpoint);
+ if (!info) {
+ ao2_ref(endpoint, -1);
+ break;
+ }
+ endpoint_id = ast_strdup(info->endpoint_id);
+ if (!endpoint_id) {
+ ao2_t_ref(info, -1, "Dispose of info on off nominal");
+ ao2_ref(endpoint, -1);
+ break;
+ }
+ info->scheduler_data = endpoint_id;
+ info->scheduler_id = ast_sched_add_variable(sched, endpoint->qualify_frequency * 1000, qualify_endpoint_scheduler_cb, endpoint_id, 1);
+ ao2_t_link(scheduled_qualifies, info, "Link scheduled qualify information into container");
+ ao2_t_ref(info, -1, "Dispose of creation ref");
+ }
+ ao2_t_ref(endpoint, -1, "Dispose of iterator ref");
+ }
+ ao2_iterator_destroy(&it_endpoints);
+}
+
+static char *send_options(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;
+ pjsip_tx_data *tdata;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip send options";
+ e->usage =
+ "Usage: sip send options <endpoint>\n"
+ " Send a SIP OPTIONS request to the specified endpoint.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4) {
+ return CLI_SHOWUSAGE;
+ }
+
+ endpoint_name = a->argv[3];
+
+ endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
+ if (!endpoint) {
+ ast_log(LOG_ERROR, "Unable to retrieve endpoint %s\n", endpoint_name);
+ return CLI_FAILURE;
+ }
+
+ if (ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata)) {
+ ast_log(LOG_ERROR, "Unable to create OPTIONS request to endpoint %s\n", endpoint_name);
+ return CLI_FAILURE;
+ }
+
+ if (ast_sip_send_request(tdata, NULL, endpoint)) {
+ ast_log(LOG_ERROR, "Unable to send OPTIONS request to endpoint %s\n", endpoint_name);
+ return CLI_FAILURE;
+ }
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_options[] = {
+ AST_CLI_DEFINE(send_options, "Send an OPTIONS requst to an arbitrary SIP URI"),
+};
+
+int ast_res_sip_init_options_handling(int reload)
+{
+ const pj_str_t STR_OPTIONS = { "OPTIONS", 7 };
+
+ if (scheduled_qualifies) {
+ ao2_t_ref(scheduled_qualifies, -1, "Remove old scheduled qualifies");
+ }
+ scheduled_qualifies = ao2_t_container_alloc(QUALIFIED_BUCKETS, qualify_info_hash_fn, qualify_info_cmp_fn, "Create container for scheduled qualifies");
+ if (!scheduled_qualifies) {
+ return -1;
+ }
+
+ if (reload) {
+ return 0;
+ }
+
+ if (pjsip_endpt_register_module(ast_sip_get_pjsip_endpoint(), &options_module) != PJ_SUCCESS) {
+ options_module_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;
+ }
+
+ ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));
+
+ schedule_qualifies();
+
+ return 0;
+}
diff --git a/res/res_sip/sip_outbound_auth.c b/res/res_sip/sip_outbound_auth.c
new file mode 100644
index 000000000..94352a521
--- /dev/null
+++ b/res/res_sip/sip_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_sip.h"
+#include "asterisk/module.h"
+#include "include/res_sip_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->sip_outbound_auths, endpoint->num_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_sip_acl.c b/res/res_sip_acl.c
new file mode 100644
index 000000000..405c3c1bc
--- /dev/null
+++ b/res/res_sip_acl.c
@@ -0,0 +1,222 @@
+/*
+ * 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.
+ */
+
+/*** MODULEINFO
+ <depend>pjproject</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/module.h"
+#include "asterisk/logger.h"
+#include "asterisk/sorcery.h"
+#include "asterisk/acl.h"
+
+struct sip_acl {
+ SORCERY_OBJECT(details);
+ struct ast_acl_list *acl;
+ struct ast_acl_list *contact_acl;
+};
+
+static int apply_acl(pjsip_rx_data *rdata, struct ast_acl_list *acl)
+{
+ struct ast_sockaddr addr;
+
+ if (ast_acl_list_is_empty(acl)) {
+ return 0;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ ast_sockaddr_parse(&addr, rdata->pkt_info.src_name, PARSE_PORT_FORBID);
+ ast_sockaddr_set_port(&addr, rdata->pkt_info.src_port);
+
+ if (ast_apply_acl(acl, &addr, "SIP ACL: ") != AST_SENSE_ALLOW) {
+ ast_log(LOG_WARNING, "Incoming SIP message from %s did not pass ACL test\n", ast_sockaddr_stringify(&addr));
+ return 1;
+ }
+ return 0;
+}
+
+static int extract_contact_addr(pjsip_contact_hdr *contact, struct ast_sockaddr **addrs)
+{
+ pjsip_sip_uri *sip_uri;
+ char host[256];
+
+ if (!contact) {
+ return 0;
+ }
+ if (!PJSIP_URI_SCHEME_IS_SIP(contact->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact->uri)) {
+ return 0;
+ }
+ sip_uri = pjsip_uri_get_uri(contact->uri);
+ ast_copy_pj_str(host, &sip_uri->host, sizeof(host));
+ return ast_sockaddr_resolve(addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC);
+}
+
+static int apply_contact_acl(pjsip_rx_data *rdata, struct ast_acl_list *contact_acl)
+{
+ int num_contact_addrs;
+ int forbidden = 0;
+ struct ast_sockaddr *contact_addrs;
+ int i;
+ pjsip_contact_hdr *contact = (pjsip_contact_hdr *)&rdata->msg_info.msg->hdr;
+
+ if (ast_acl_list_is_empty(contact_acl)) {
+ return 0;
+ }
+
+ while ((contact = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact->next))) {
+ num_contact_addrs = extract_contact_addr(contact, &contact_addrs);
+ if (num_contact_addrs <= 0) {
+ continue;
+ }
+ for (i = 0; i < num_contact_addrs; ++i) {
+ if (ast_apply_acl(contact_acl, &contact_addrs[i], "SIP Contact ACL: ") != AST_SENSE_ALLOW) {
+ ast_log(LOG_WARNING, "Incoming SIP message from %s did not pass ACL test\n", ast_sockaddr_stringify(&contact_addrs[i]));
+ forbidden = 1;
+ break;
+ }
+ }
+ ast_free(contact_addrs);
+ if (forbidden) {
+ /* No use checking other contacts if we already have failed ACL check */
+ break;
+ }
+ }
+
+ return forbidden;
+}
+
+static int check_acls(void *obj, void *arg, int flags)
+{
+ struct sip_acl *acl = obj;
+ pjsip_rx_data *rdata = arg;
+
+ if (apply_acl(rdata, acl->acl) || apply_contact_acl(rdata, acl->contact_acl)) {
+ return CMP_MATCH | CMP_STOP;
+ }
+ return 0;
+}
+
+static pj_bool_t acl_on_rx_msg(pjsip_rx_data *rdata)
+{
+ int forbidden = 0;
+ struct ao2_container *acls = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "acl", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+ struct sip_acl *matched_acl;
+ if (!acls) {
+ ast_log(LOG_ERROR, "Unable to retrieve ACL sorcery data\n");
+ return PJ_FALSE;
+ }
+
+ matched_acl = ao2_callback(acls, 0, check_acls, rdata);
+ if (matched_acl) {
+ forbidden = 1;
+ ao2_ref(matched_acl, -1);
+ }
+ ao2_ref(acls, -1);
+
+ if (forbidden) {
+ if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+ }
+ return PJ_TRUE;
+ }
+
+ return PJ_FALSE;
+}
+
+static pjsip_module acl_module = {
+ .name = { "ACL Module", 14 },
+ /* This should run after a logger but before anything else */
+ .priority = 1,
+ .on_rx_request = acl_on_rx_msg,
+};
+
+static int acl_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct sip_acl *acl = obj;
+ int error;
+ int ignore;
+ if (!strncmp(var->name, "contact", 7)) {
+ ast_append_acl(var->name + 7, var->value, &acl->contact_acl, &error, &ignore);
+ } else {
+ ast_append_acl(var->name, var->value, &acl->acl, &error, &ignore);
+ }
+ return error;
+}
+
+static void sip_acl_destructor(void *obj)
+{
+ struct sip_acl *acl = obj;
+ acl->acl = ast_free_acl_list(acl->acl);
+ acl->contact_acl = ast_free_acl_list(acl->contact_acl);
+}
+
+static void *sip_acl_alloc(const char *name)
+{
+ struct sip_acl *acl = ao2_alloc(sizeof(*acl), sip_acl_destructor);
+ if (!acl) {
+ return NULL;
+ }
+ return acl;
+}
+
+static int load_acls(void)
+{
+ ast_sorcery_apply_default(ast_sip_get_sorcery(), "acl", "config", "res_sip.conf,criteria=type=acl");
+ if (ast_sorcery_object_register(ast_sip_get_sorcery(), "acl", sip_acl_alloc, NULL, NULL)) {
+ ast_log(LOG_ERROR, "Failed to register SIP ACL object with sorcery\n");
+ return -1;
+ }
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "acl", "type", "", OPT_NOOP_T, 0, 0);
+ ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "acl", "permit", "", acl_handler, NULL, 0, 0);
+ ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "acl", "deny", "", acl_handler, NULL, 0, 0);
+ ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "acl", "acl", "", acl_handler, NULL, 0, 0);
+ ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "acl", "contactpermit", "", acl_handler, NULL, 0, 0);
+ ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "acl", "contactdeny", "", acl_handler, NULL, 0, 0);
+ ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "acl", "contactacl", "", acl_handler, NULL, 0, 0);
+
+ /* XXX Is there a more selective way to do this? (i.e. Just reload a specific object type?) */
+ ast_sorcery_reload(ast_sip_get_sorcery());
+ return 0;
+}
+
+static int load_module(void)
+{
+ if (load_acls()) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ ast_sip_register_service(&acl_module);
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ ast_sip_unregister_service(&acl_module);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP ACL Resource",
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_APP_DEPEND,
+ );
diff --git a/res/res_sip_authenticator_digest.c b/res/res_sip_authenticator_digest.c
new file mode 100644
index 000000000..499342309
--- /dev/null
+++ b/res/res_sip_authenticator_digest.c
@@ -0,0 +1,455 @@
+/*
+ * 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_sip.h"
+#include "asterisk/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/strings.h"
+
+/*** MODULEINFO
+ <depend>pjproject</depend>
+ <depend>res_sip</depend>
+ <support_level>core</support_level>
+ ***/
+
+AO2_GLOBAL_OBJ_STATIC(entity_id);
+
+/*!
+ * \brief Determine if authentication is required
+ *
+ * Authentication is required if the endpoint has at least one auth
+ * section specified
+ */
+static int digest_requires_authentication(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
+{
+ return endpoint->num_inbound_auths > 0;
+}
+
+static void auth_store_cleanup(void *data)
+{
+ struct ast_sip_auth **auth = data;
+
+ ao2_cleanup(*auth);
+ ast_free(data);
+}
+
+/*!
+ * \brief Thread-local storage for \ref ast_sip_auth
+ *
+ * The PJSIP authentication API is a bit annoying. When you set
+ * up an authentication server, you specify a lookup callback to
+ * call into when verifying incoming credentials. The problem
+ * with this callback is that it only gives you the realm and
+ * authentication username. In 2.0.5, there is a new version of
+ * the callback you can use that gives the pjsip_rx_data in
+ * addition.
+ *
+ * Unfortunately, the data we actually \b need is the
+ * \ref ast_sip_auth we are currently observing. So we have two
+ * choices:
+ * 1) Use the current PJSIP API and use thread-local storage
+ * to temporarily store our SIP authentication information. Then
+ * in the callback, we can retrieve the authentication info and
+ * use as needed. Given our threading model, this is safe.
+ * 2) Use the 2.0.5 API and temporarily store the authentication
+ * information in the rdata's endpoint_info. Then in the callback,
+ * we can retrieve the authentication info from the rdata.
+ *
+ * I've chosen option 1 since it does not require backporting
+ * any APIs from future versions of PJSIP, plus I feel the
+ * thread-local option is a bit cleaner.
+ */
+AST_THREADSTORAGE_CUSTOM(auth_store, NULL, auth_store_cleanup);
+
+/*!
+ * \brief Store authentication information in thread-local storage
+ */
+static int store_auth(struct ast_sip_auth *auth)
+{
+ struct ast_sip_auth **pointing;
+ pointing = ast_threadstorage_get(&auth_store, sizeof(pointing));
+ if (!pointing || *pointing) {
+ return -1;
+ }
+
+ ao2_ref(auth, +1);
+ *pointing = auth;
+ return 0;
+}
+
+/*!
+ * \brief Remove authentication information from thread-local storage
+ */
+static int remove_auth(void)
+{
+ struct ast_sip_auth **pointing;
+ pointing = ast_threadstorage_get(&auth_store, sizeof(pointing));
+ if (!pointing) {
+ return -1;
+ }
+
+ ao2_cleanup(*pointing);
+ *pointing = NULL;
+ return 0;
+}
+
+/*!
+ * \brief Retrieve authentication information from thread-local storage
+ */
+static struct ast_sip_auth *get_auth(void)
+{
+ struct ast_sip_auth **auth;
+ auth = ast_threadstorage_get(&auth_store, sizeof(auth));
+ if (auth && *auth) {
+ ao2_ref(*auth, +1);
+ return *auth;
+ }
+ return NULL;
+}
+
+/*!
+ * \brief Lookup callback for authentication verification
+ *
+ * This function is called when we call pjsip_auth_srv_verify(). It
+ * expects us to verify that the realm and account name from the
+ * Authorization header is correct. We are then supposed to supply
+ * a password or MD5 sum of credentials.
+ *
+ * \param pool A memory pool we can use for allocations
+ * \param realm The realm from the Authorization header
+ * \param acc_name the user from the Authorization header
+ * \param[out] info The credentials we need to fill in
+ * \retval PJ_SUCCESS Successful authentication
+ * \retval other Unsuccessful
+ */
+static pj_status_t digest_lookup(pj_pool_t *pool, const pj_str_t *realm,
+ const pj_str_t *acc_name, pjsip_cred_info *info)
+{
+ RAII_VAR(struct ast_sip_auth *, auth, get_auth(), ao2_cleanup);
+ if (!auth) {
+ return PJSIP_SC_FORBIDDEN;
+ }
+
+ if (pj_strcmp2(realm, auth->realm)) {
+ return PJSIP_SC_FORBIDDEN;
+ }
+ if (pj_strcmp2(acc_name, auth->auth_user)) {
+ return PJSIP_SC_FORBIDDEN;
+ }
+
+ pj_strdup2(pool, &info->realm, auth->realm);
+ pj_strdup2(pool, &info->username, auth->auth_user);
+
+ switch (auth->type) {
+ case AST_SIP_AUTH_TYPE_USER_PASS:
+ pj_strdup2(pool, &info->data, auth->auth_pass);
+ info->data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
+ break;
+ case AST_SIP_AUTH_TYPE_MD5:
+ pj_strdup2(pool, &info->data, auth->md5_creds);
+ info->data_type = PJSIP_CRED_DATA_DIGEST;
+ break;
+ default:
+ return PJSIP_SC_FORBIDDEN;
+ }
+ return PJ_SUCCESS;
+}
+
+/*!
+ * \brief Calculate a nonce
+ *
+ * We use this in order to create authentication challenges. We also use this in order
+ * to verify that an incoming request with credentials could be in response to one
+ * of our challenges.
+ *
+ * The nonce is calculated from a timestamp, the source IP address, the source port, a
+ * unique ID for us, and the realm. This helps to ensure that the incoming request
+ * is from the same source that the nonce was calculated for. Including the realm
+ * ensures that multiple challenges to the same request have different nonces.
+ *
+ * \param A UNIX timestamp expressed as a string
+ * \param rdata The incoming request
+ * \param realm The realm for which authentication should occur
+ */
+static int build_nonce(struct ast_str **nonce, const char *timestamp, const pjsip_rx_data *rdata, const char *realm)
+{
+ struct ast_str *str = ast_str_alloca(256);
+ RAII_VAR(char *, eid, ao2_global_obj_ref(entity_id), ao2_cleanup);
+ char hash[32];
+
+ ast_str_append(&str, 0, "%s", timestamp);
+ ast_str_append(&str, 0, ":%s", rdata->pkt_info.src_name);
+ ast_str_append(&str, 0, ":%d", rdata->pkt_info.src_port);
+ ast_str_append(&str, 0, ":%s", eid);
+ ast_str_append(&str, 0, ":%s", realm);
+ ast_md5_hash(hash, ast_str_buffer(str));
+
+ ast_str_append(nonce, 0, "%s/%s", timestamp, hash);
+ return 0;
+}
+
+/*!
+ * \brief Ensure that a nonce on an incoming request is sane.
+ *
+ * The nonce in an incoming Authorization header needs to pass some scrutiny in order
+ * for us to consider accepting it. What we do is re-build a nonce based on request
+ * data and a realm and see if it matches the nonce they sent us.
+ * \param candidate The nonce on an incoming request
+ * \param rdata The incoming request
+ * \param auth The auth credentials we are trying to match against.
+ * \retval 0 Nonce does not pass validity checks
+ * \retval 1 Nonce passes validity check
+ */
+static int check_nonce(const char *candidate, const pjsip_rx_data *rdata, const struct ast_sip_auth *auth)
+{
+ char *copy = ast_strdupa(candidate);
+ char *timestamp = strsep(&copy, "/");
+ int timestamp_int;
+ time_t now = time(NULL);
+ struct ast_str *calculated = ast_str_alloca(64);
+
+ if (!copy) {
+ /* Clearly a bad nonce! */
+ return 0;
+ }
+
+ if (sscanf(timestamp, "%30d", &timestamp_int) != 1) {
+ return 0;
+ }
+
+ if ((int) now - timestamp_int > auth->nonce_lifetime) {
+ return 0;
+ }
+
+ build_nonce(&calculated, timestamp, rdata, auth->realm);
+ ast_debug(3, "Calculated nonce %s. Actual nonce is %s\n", ast_str_buffer(calculated), candidate);
+ if (strcmp(ast_str_buffer(calculated), candidate)) {
+ return 0;
+ }
+ return 1;
+}
+
+static int find_challenge(const pjsip_rx_data *rdata, const struct ast_sip_auth *auth)
+{
+ struct pjsip_authorization_hdr *auth_hdr = (pjsip_authorization_hdr *) &rdata->msg_info.msg->hdr;
+ int challenge_found = 0;
+ char nonce[64];
+
+ while ((auth_hdr = (pjsip_authorization_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, auth_hdr->next))) {
+ ast_copy_pj_str(nonce, &auth_hdr->credential.digest.nonce, sizeof(nonce));
+ if (check_nonce(nonce, rdata, auth) && !pj_strcmp2(&auth_hdr->credential.digest.realm, auth->realm)) {
+ challenge_found = 1;
+ break;
+ }
+ }
+
+ return challenge_found;
+}
+
+/*!
+ * \brief Common code for initializing a pjsip_auth_srv
+ */
+static void setup_auth_srv(pj_pool_t *pool, pjsip_auth_srv *auth_server, const struct ast_sip_auth *auth)
+{
+ pj_str_t realm;
+ pj_cstr(&realm, auth->realm);
+
+ pjsip_auth_srv_init(pool, auth_server, &realm, digest_lookup, 0);
+}
+
+/*!
+ * \brief Result of digest verification
+ */
+enum digest_verify_result {
+ /*! Authentication credentials incorrect */
+ AUTH_FAIL,
+ /*! Authentication credentials correct */
+ AUTH_SUCCESS,
+ /*! Authentication credentials correct but nonce mismatch */
+ AUTH_STALE,
+};
+
+/*!
+ * \brief astobj2 callback for verifying incoming credentials
+ *
+ * \param auth The ast_sip_auth to check against
+ * \param rdata The incoming request
+ * \param pool A pool to use for the auth server
+ * \return CMP_MATCH on successful authentication
+ * \return 0 on failed authentication
+ */
+static int verify(struct ast_sip_auth *auth, pjsip_rx_data *rdata, pj_pool_t *pool)
+{
+ pj_status_t authed;
+ int response_code;
+ pjsip_auth_srv auth_server;
+ int stale = 0;
+
+ if (!find_challenge(rdata, auth)) {
+ /* Couldn't find a challenge with a sane nonce.
+ * Nonce mismatch may just be due to staleness.
+ */
+ stale = 1;
+ }
+
+ setup_auth_srv(pool, &auth_server, auth);
+
+ store_auth(auth);
+
+ authed = pjsip_auth_srv_verify(&auth_server, rdata, &response_code);
+
+ remove_auth();
+
+ if (authed == PJ_SUCCESS) {
+ if (stale) {
+ return AUTH_STALE;
+ } else {
+ return AUTH_SUCCESS;
+ }
+ }
+ return AUTH_FAIL;
+}
+
+/*!
+ * \brief astobj2 callback for adding digest challenges to responses
+ *
+ * \param auth The ast_aip_auth to build a challenge from
+ * \param tdata The response to add the challenge to
+ * \param rdata The request the challenge is in response to
+ * \param is_stale Indicates whether nonce on incoming request was stale
+ */
+static void challenge(const struct ast_sip_auth *auth, pjsip_tx_data *tdata, const pjsip_rx_data *rdata, int is_stale)
+{
+ pj_str_t qop;
+ pj_str_t pj_nonce;
+ pjsip_auth_srv auth_server;
+ struct ast_str *nonce = ast_str_alloca(256);
+ char time_buf[32];
+ time_t timestamp = time(NULL);
+ snprintf(time_buf, sizeof(time_buf), "%d", (int) timestamp);
+
+ build_nonce(&nonce, time_buf, rdata, auth->realm);
+
+ setup_auth_srv(tdata->pool, &auth_server, auth);
+
+ pj_cstr(&pj_nonce, ast_str_buffer(nonce));
+ pj_cstr(&qop, "auth");
+ pjsip_auth_srv_challenge(&auth_server, &qop, &pj_nonce, NULL, is_stale ? PJ_TRUE : PJ_FALSE, tdata);
+}
+
+/*!
+ * \brief Check authentication using Digest scheme
+ *
+ * This function will check an incoming message against configured authentication
+ * options. If \b any of the incoming Authorization headers result in successful
+ * authentication, then authentication is considered successful.
+ *
+ * \see ast_sip_check_authentication
+ */
+static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint *endpoint,
+ pjsip_rx_data *rdata, pjsip_tx_data *tdata)
+{
+ struct ast_sip_auth **auths = ast_alloca(endpoint->num_inbound_auths * sizeof(*auths));
+ enum digest_verify_result *verify_res = ast_alloca(endpoint->num_inbound_auths * sizeof(*verify_res));
+ enum ast_sip_check_auth_result res;
+ int i;
+
+ if (!auths) {
+ return AST_SIP_AUTHENTICATION_ERROR;
+ }
+
+ if (ast_sip_retrieve_auths(endpoint->sip_inbound_auths, endpoint->num_inbound_auths, auths)) {
+ res = AST_SIP_AUTHENTICATION_ERROR;
+ goto cleanup;
+ }
+
+ for (i = 0; i < endpoint->num_inbound_auths; ++i) {
+ verify_res[i] = verify(auths[i], rdata, tdata->pool);
+ if (verify_res[i] == AUTH_SUCCESS) {
+ res = AST_SIP_AUTHENTICATION_SUCCESS;
+ goto cleanup;
+ }
+ }
+
+ for (i = 0; i < endpoint->num_inbound_auths; ++i) {
+ challenge(auths[i], tdata, rdata, verify_res[i] == AUTH_STALE);
+ }
+
+ res = AST_SIP_AUTHENTICATION_CHALLENGE;
+
+cleanup:
+ ast_sip_cleanup_auths(auths, endpoint->num_inbound_auths);
+ return res;
+}
+
+static struct ast_sip_authenticator digest_authenticator = {
+ .requires_authentication = digest_requires_authentication,
+ .check_authentication = digest_check_auth,
+};
+
+static int build_entity_id(void)
+{
+ RAII_VAR(struct ast_uuid *, uu, ast_uuid_generate(), ast_free_ptr);
+ RAII_VAR(char *, eid, ao2_alloc(AST_UUID_STR_LEN, NULL), ao2_cleanup);
+
+ if (!uu || !eid) {
+ return -1;
+ }
+
+ ast_uuid_to_str(uu, eid, AST_UUID_STR_LEN);
+ ao2_global_obj_replace_unref(entity_id, eid);
+ return 0;
+}
+
+static int reload_module(void)
+{
+ if (build_entity_id()) {
+ return -1;
+ }
+ return 0;
+}
+
+static int load_module(void)
+{
+ if (build_entity_id()) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ if (ast_sip_register_authenticator(&digest_authenticator)) {
+ ao2_global_obj_release(entity_id);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ ast_sip_unregister_authenticator(&digest_authenticator);
+ ao2_global_obj_release(entity_id);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP authentication resource",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload_module,
+ .load_pri = AST_MODPRI_CHANNEL_DEPEND,
+);
diff --git a/res/res_sip_caller_id.c b/res/res_sip_caller_id.c
new file mode 100644
index 000000000..22ece0436
--- /dev/null
+++ b/res/res_sip_caller_id.c
@@ -0,0 +1,715 @@
+/*
+ * 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.
+ */
+
+/*** MODULEINFO
+ <depend>pjproject</depend>
+ <depend>res_sip</depend>
+ <depend>res_sip_session</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/res_sip_session.h"
+#include "asterisk/channel.h"
+#include "asterisk/module.h"
+#include "asterisk/callerid.h"
+
+/*!
+ * \internal
+ * \brief Set an ast_party_id name and number based on an identity header.
+ * \param hdr From, P-Asserted-Identity, or Remote-Party-ID header on incoming message
+ * \param[out] id The ID to set data on
+ */
+static void set_id_from_hdr(pjsip_fromto_hdr *hdr, struct ast_party_id *id)
+{
+ char cid_name[AST_CHANNEL_NAME];
+ char cid_num[AST_CHANNEL_NAME];
+ pjsip_sip_uri *uri;
+ pjsip_name_addr *id_name_addr = (pjsip_name_addr *) hdr->uri;
+
+ uri = pjsip_uri_get_uri(id_name_addr);
+ ast_copy_pj_str(cid_name, &id_name_addr->display, sizeof(cid_name));
+ ast_copy_pj_str(cid_num, &uri->user, sizeof(cid_num));
+
+ ast_free(id->name.str);
+ id->name.str = ast_strdup(cid_name);
+ if (!ast_strlen_zero(cid_name)) {
+ id->name.valid = 1;
+ }
+ ast_free(id->number.str);
+ id->number.str = ast_strdup(cid_num);
+ if (!ast_strlen_zero(cid_num)) {
+ id->number.valid = 1;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Get a P-Asserted-Identity or Remote-Party-ID header from an incoming message
+ *
+ * This function will parse the header as if it were a From header. This allows for us
+ * to easily manipulate the URI, as well as add, modify, or remove parameters from the
+ * header
+ *
+ * \param rdata The incoming message
+ * \param header_name The name of the ID header to find
+ * \retval NULL No ID header present or unable to parse ID header
+ * \retval non-NULL The parsed ID header
+ */
+static pjsip_fromto_hdr *get_id_header(pjsip_rx_data *rdata, const pj_str_t *header_name)
+{
+ static const pj_str_t from = { "From", 4 };
+ pj_str_t header_content;
+ pjsip_fromto_hdr *parsed_hdr;
+ pjsip_generic_string_hdr *ident = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
+ header_name, NULL);
+ int parsed_len;
+
+ if (!ident) {
+ return NULL;
+ }
+
+ pj_strdup_with_null(rdata->tp_info.pool, &header_content, &ident->hvalue);
+
+ parsed_hdr = pjsip_parse_hdr(rdata->tp_info.pool, &from, header_content.ptr,
+ pj_strlen(&header_content), &parsed_len);
+
+ if (!parsed_hdr) {
+ return NULL;
+ }
+
+ return parsed_hdr;
+}
+
+/*!
+ * \internal
+ * \brief Set an ast_party_id structure based on data in a P-Asserted-Identity header
+ *
+ * This makes use of \ref set_id_from_hdr for setting name and number. It uses
+ * the contents of a Privacy header in order to set presentation information.
+ *
+ * \param rdata The incoming message
+ * \param[out] id The ID to set
+ * \retval 0 Successfully set the party ID
+ * \retval non-zero Could not set the party ID
+ */
+static int set_id_from_pai(pjsip_rx_data *rdata, struct ast_party_id *id)
+{
+ static const pj_str_t pai_str = { "P-Asserted-Identity", 19 };
+ static const pj_str_t privacy_str = { "Privacy", 7 };
+ pjsip_fromto_hdr *pai_hdr = get_id_header(rdata, &pai_str);
+ pjsip_generic_string_hdr *privacy;
+
+ if (!pai_hdr) {
+ return -1;
+ }
+
+ set_id_from_hdr(pai_hdr, id);
+
+ if (!id->number.valid) {
+ return -1;
+ }
+
+ privacy = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &privacy_str, NULL);
+ if (!privacy) {
+ return 0;
+ }
+ if (!pj_stricmp2(&privacy->hvalue, "id")) {
+ id->number.presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED;
+ id->name.presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED;
+ }
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Set an ast_party_id structure based on data in a Remote-Party-ID header
+ *
+ * This makes use of \ref set_id_from_hdr for setting name and number. It uses
+ * the privacy and screen parameters in order to set presentation information.
+ *
+ * \param rdata The incoming message
+ * \param[out] id The ID to set
+ * \retval 0 Succesfully set the party ID
+ * \retval non-zero Could not set the party ID
+ */
+static int set_id_from_rpid(pjsip_rx_data *rdata, struct ast_party_id *id)
+{
+ static const pj_str_t rpid_str = { "Remote-Party-ID", 15 };
+ static const pj_str_t privacy_str = { "privacy", 7 };
+ static const pj_str_t screen_str = { "screen", 6 };
+ pjsip_fromto_hdr *rpid_hdr = get_id_header(rdata, &rpid_str);
+ pjsip_param *screen;
+ pjsip_param *privacy;
+
+ if (!rpid_hdr) {
+ return -1;
+ }
+
+ set_id_from_hdr(rpid_hdr, id);
+
+ if (!id->number.valid) {
+ return -1;
+ }
+
+ privacy = pjsip_param_find(&rpid_hdr->other_param, &privacy_str);
+ screen = pjsip_param_find(&rpid_hdr->other_param, &screen_str);
+ if (privacy && !pj_stricmp2(&privacy->value, "full")) {
+ id->number.presentation |= AST_PRES_RESTRICTED;
+ id->name.presentation |= AST_PRES_RESTRICTED;
+ }
+ if (screen && !pj_stricmp2(&screen->value, "yes")) {
+ id->number.presentation |= AST_PRES_USER_NUMBER_PASSED_SCREEN;
+ id->name.presentation |= AST_PRES_USER_NUMBER_PASSED_SCREEN;
+ }
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Set an ast_party_id structure based on data in a From
+ *
+ * This makes use of \ref set_id_from_hdr for setting name and number. It uses
+ * no information from the message in order to set privacy. It relies on endpoint
+ * configuration for privacy information.
+ *
+ * \param rdata The incoming message
+ * \param[out] id The ID to set
+ * \retval 0 Succesfully set the party ID
+ * \retval non-zero Could not set the party ID
+ */
+static int set_id_from_from(struct pjsip_rx_data *rdata, struct ast_party_id *id)
+{
+ pjsip_fromto_hdr *from = pjsip_msg_find_hdr(rdata->msg_info.msg,
+ PJSIP_H_FROM, rdata->msg_info.msg->hdr.next);
+
+ if (!from) {
+ /* This had better not happen */
+ return -1;
+ }
+
+ set_id_from_hdr(from, id);
+
+ if (!id->number.valid) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Determine if a connected line update should be queued
+ *
+ * This uses information about the session and the ID that would be queued
+ * in the connected line update in order to determine if we should queue
+ * a connected line update.
+ *
+ * \param session The session whose channel we wish to queue the connected line update on
+ * \param id The identification information that would be queued on the connected line update
+ * \retval 0 We should not queue a connected line update
+ * \retval non-zero We should queue a connected line update
+ */
+static int should_queue_connected_line_update(const struct ast_sip_session *session, const struct ast_party_id *id)
+{
+ /* Invalid number means no update */
+ if (!id->number.valid) {
+ return 0;
+ }
+
+ /* If the session has never communicated an update or if the
+ * new ID has a different number than the session, then we
+ * should queue an update
+ */
+ if (ast_strlen_zero(session->id.number.str) ||
+ strcmp(session->id.number.str, id->number.str)) {
+ return 1;
+ }
+
+ /* By making it to this point, it means the number is not enough
+ * to determine if an update should be sent. Now we look at
+ * the name
+ */
+
+ /* If the number couldn't warrant an update and the name is
+ * invalid, then no update
+ */
+ if (!id->name.valid) {
+ return 0;
+ }
+
+ /* If the name has changed or we don't have a name set for the
+ * session, then we should send an update
+ */
+ if (ast_strlen_zero(session->id.name.str) ||
+ strcmp(session->id.name.str, id->name.str)) {
+ return 1;
+ }
+
+ /* Neither the name nor the number have changed. No update */
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Queue a connected line update on a session's channel.
+ * \param session The session whose channel should have the connected line update queued upon.
+ * \param id The identification information to place in the connected line update
+ */
+static void queue_connected_line_update(struct ast_sip_session *session, const struct ast_party_id *id)
+{
+ struct ast_party_connected_line connected;
+ struct ast_set_party_connected_line update_connected;
+
+ ast_party_connected_line_init(&connected);
+ ast_party_id_copy(&connected.id, id);
+
+ memset(&update_connected, 0, sizeof(update_connected));
+ update_connected.id.number = 1;
+ update_connected.id.name = 1;
+
+ ast_set_party_id_all(&update_connected.priv);
+ connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER;
+ ast_party_id_copy(&session->id, &connected.id);
+ ast_channel_queue_connected_line_update(session->channel, &connected, &update_connected);
+
+ ast_party_connected_line_free(&connected);
+}
+
+/*!
+ * \internal
+ * \brief Make updates to connected line information based on an incoming request.
+ *
+ * This will get identity information from an incoming request. Once the identification is
+ * retrieved, we will check if the new information warrants a connected line update and queue
+ * a connected line update if so.
+ *
+ * \param session The session on which we received an incoming request
+ * \param rdata The incoming request
+ */
+static void update_incoming_connected_line(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+ struct ast_party_id id;
+
+ if (!session->endpoint->trust_id_inbound) {
+ return;
+ }
+
+ ast_party_id_init(&id);
+ if (set_id_from_pai(rdata, &id) && set_id_from_rpid(rdata, &id)) {
+ return;
+ }
+ if (should_queue_connected_line_update(session, &id)) {
+ queue_connected_line_update(session, &id);
+ }
+
+ ast_party_id_free(&id);
+}
+
+/*!
+ * \internal
+ * \brief Session supplement callback on an incoming INVITE request
+ *
+ * If we are receiving an initial INVITE, then we will set the session's identity
+ * based on the INVITE or configured endpoint values. If we are receiving a reinvite,
+ * then we will potentially queue a connected line update via the \ref update_incoming_connected_line
+ * function
+ *
+ * \param session The session that has received an INVITE
+ * \param rdata The incoming INVITE
+ */
+static int caller_id_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+ if (session->inv_session->state < PJSIP_INV_STATE_CONFIRMED) {
+ /* Initial inbound INVITE. Set the session ID directly */
+ if (session->endpoint->trust_id_inbound &&
+ (!set_id_from_pai(rdata, &session->id) || !set_id_from_rpid(rdata, &session->id))) {
+ return 0;
+ }
+ ast_party_id_copy(&session->id, &session->endpoint->id);
+ if (!session->endpoint->id.number.valid) {
+ set_id_from_from(rdata, &session->id);
+ }
+ } else {
+ /* Reinvite. Check for changes to the ID and queue a connected line
+ * update if necessary
+ */
+ update_incoming_connected_line(session, rdata);
+ }
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Session supplement callback on INVITE response
+ *
+ * INVITE responses could result in queuing connected line updates.
+ *
+ * \param session The session on which communication is happening
+ * \param rdata The incoming INVITE response
+ */
+static void caller_id_incoming_response(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+ if (!session->channel) {
+ return;
+ }
+
+ update_incoming_connected_line(session, rdata);
+}
+
+/*!
+ * \internal
+ * \brief Set name and number information on an identity header.
+ * \param pool Memory pool to use for string duplication
+ * \param id_hdr A From, P-Asserted-Identity, or Remote-Party-ID header to modify
+ * \param id The identity information to apply to the header
+ */
+static void modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr, const struct ast_party_id *id)
+{
+ pjsip_name_addr *id_name_addr;
+ pjsip_sip_uri *id_uri;
+
+ id_name_addr = (pjsip_name_addr *) id_hdr->uri;
+ id_uri = pjsip_uri_get_uri(id_name_addr->uri);
+
+ if (id->name.valid) {
+ pj_strdup2(pool, &id_name_addr->display, id->name.str);
+ }
+
+ pj_strdup2(pool, &id_uri->user, id->number.str);
+}
+
+/*!
+ * \internal
+ * \brief Create an identity header for an outgoing message
+ * \param hdr_name The name of the header to create
+ * \param tdata The message to place the header on
+ * \param id The identification information for the new header
+ * \return newly-created header
+ */
+static pjsip_fromto_hdr *create_new_id_hdr(const pj_str_t *hdr_name, pjsip_tx_data *tdata, const struct ast_party_id *id)
+{
+ pjsip_fromto_hdr *id_hdr;
+ pjsip_fromto_hdr *base;
+ pjsip_name_addr *id_name_addr;
+ pjsip_sip_uri *id_uri;
+
+ base = tdata->msg->type == PJSIP_REQUEST_MSG ? PJSIP_MSG_FROM_HDR(tdata->msg) :
+ PJSIP_MSG_TO_HDR(tdata->msg);
+ id_hdr = pjsip_from_hdr_create(tdata->pool);
+ id_hdr->type = PJSIP_H_OTHER;
+ pj_strdup(tdata->pool, &id_hdr->name, hdr_name);
+ id_hdr->sname.slen = 0;
+
+ id_name_addr = pjsip_uri_clone(tdata->pool, base->uri);
+ id_uri = pjsip_uri_get_uri(id_name_addr->uri);
+
+ if (id->name.valid) {
+ pj_strdup2(tdata->pool, &id_name_addr->display, id->name.str);
+ }
+
+ pj_strdup2(tdata->pool, &id_uri->user, id->number.str);
+
+ id_hdr->uri = (pjsip_uri *) id_name_addr;
+ return id_hdr;
+}
+
+/*!
+ * \internal
+ * \brief Add a Privacy header to an outbound message
+ *
+ * When sending a P-Asserted-Identity header, if privacy is requested, then we
+ * will need to indicate such by adding a Privacy header. Similarly, if no
+ * privacy is requested, and a Privacy header already exists on the message,
+ * then the old Privacy header should be removed.
+ *
+ * \param tdata The outbound message to add the Privacy header to
+ * \param id The id information used to determine privacy
+ */
+static void add_privacy_header(pjsip_tx_data *tdata, const struct ast_party_id *id)
+{
+ static const pj_str_t pj_privacy_name = { "Privacy", 7 };
+ static const pj_str_t pj_privacy_value = { "id", 2 };
+ pjsip_hdr *old_privacy;
+
+ old_privacy = pjsip_msg_find_hdr_by_name(tdata->msg, &pj_privacy_name, NULL);
+
+ if ((id->name.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED ||
+ (id->name.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED) {
+ if (!old_privacy) {
+ pjsip_generic_string_hdr *privacy_hdr = pjsip_generic_string_hdr_create(
+ tdata->pool, &pj_privacy_name, &pj_privacy_value);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)privacy_hdr);
+ }
+ } else {
+ if (old_privacy) {
+ pj_list_erase(old_privacy);
+ }
+ }
+}
+
+/*!
+ * \internal
+ * \brief Add a P-Asserted-Identity header to an outbound message
+ * \param tdata The message to add the header to
+ * \param id The identification information used to populate the header
+ */
+static void add_pai_header(pjsip_tx_data *tdata, const struct ast_party_id *id)
+{
+ static const pj_str_t pj_pai_name = { "P-Asserted-Identity", 19 };
+ pjsip_fromto_hdr *pai_hdr;
+ pjsip_fromto_hdr *old_pai;
+
+ if (!id->number.valid) {
+ return;
+ }
+
+ /* Since inv_session reuses responses, we have to make sure there's not already
+ * a P-Asserted-Identity present. If there is, we just modify the old one.
+ */
+ old_pai = pjsip_msg_find_hdr_by_name(tdata->msg, &pj_pai_name, NULL);
+ if (old_pai) {
+ modify_id_header(tdata->pool, old_pai, id);
+ add_privacy_header(tdata, id);
+ return;
+ }
+
+ pai_hdr = create_new_id_hdr(&pj_pai_name, tdata, id);
+ if (!pai_hdr) {
+ return;
+ }
+ add_privacy_header(tdata, id);
+
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)pai_hdr);
+}
+
+/*!
+ * \internal
+ * \brief Add privacy and screen parameters to a Remote-Party-ID header.
+ *
+ * If privacy is requested, then the privacy and screen parameters need to
+ * reflect this. Similarly, if no privacy or screening is to be communicated,
+ * we need to make sure that any previously set values are updated.
+ *
+ * \param tdata The message where the Remote-Party-ID header is
+ * \param hdr The header on which the parameters are being added
+ * \param id The identification information used to determine privacy
+ */
+static void add_privacy_params(pjsip_tx_data *tdata, pjsip_fromto_hdr *hdr, const struct ast_party_id *id)
+{
+ static const pj_str_t privacy_str = { "privacy", 7 };
+ static const pj_str_t screen_str = { "screen", 6 };
+ static const pj_str_t privacy_full_str = { "full", 4 };
+ static const pj_str_t privacy_off_str = { "off", 3 };
+ static const pj_str_t screen_yes_str = { "yes", 3 };
+ static const pj_str_t screen_no_str = { "no", 2 };
+ pjsip_param *old_privacy;
+ pjsip_param *old_screen;
+ pjsip_param *privacy;
+ pjsip_param *screen;
+
+ old_privacy = pjsip_param_find(&hdr->other_param, &privacy_str);
+ old_screen = pjsip_param_find(&hdr->other_param, &screen_str);
+
+ if (!old_privacy) {
+ privacy = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param);
+ privacy->name = privacy_str;
+ pj_list_insert_before(&hdr->other_param, privacy);
+ } else {
+ privacy = old_privacy;
+ }
+
+ if (!old_screen) {
+ screen = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param);
+ screen->name = screen_str;
+ pj_list_insert_before(&hdr->other_param, screen);
+ } else {
+ screen = old_screen;
+ }
+
+ if ((id->name.presentation & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED &&
+ (id->name.presentation & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) {
+ privacy->value = privacy_off_str;
+ } else {
+ privacy->value = privacy_full_str;
+ }
+
+ if ((id->name.presentation & AST_PRES_NUMBER_TYPE) == AST_PRES_USER_NUMBER_PASSED_SCREEN &&
+ (id->number.presentation & AST_PRES_NUMBER_TYPE) == AST_PRES_USER_NUMBER_PASSED_SCREEN) {
+ screen->value = screen_yes_str;
+ } else {
+ screen->value = screen_no_str;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Add a Remote-Party-ID header to an outbound message
+ * \param tdata The message to add the header to
+ * \param id The identification information used to populate the header
+ */
+static void add_rpid_header(pjsip_tx_data *tdata, const struct ast_party_id *id)
+{
+ static const pj_str_t pj_rpid_name = { "Remote-Party-ID", 15 };
+ pjsip_fromto_hdr *rpid_hdr;
+ pjsip_fromto_hdr *old_rpid;
+
+ if (!id->number.valid) {
+ return;
+ }
+
+ /* Since inv_session reuses responses, we have to make sure there's not already
+ * a P-Asserted-Identity present. If there is, we just modify the old one.
+ */
+ old_rpid = pjsip_msg_find_hdr_by_name(tdata->msg, &pj_rpid_name, NULL);
+ if (old_rpid) {
+ modify_id_header(tdata->pool, old_rpid, id);
+ add_privacy_params(tdata, old_rpid, id);
+ return;
+ }
+
+ rpid_hdr = create_new_id_hdr(&pj_rpid_name, tdata, id);
+ if (!rpid_hdr) {
+ return;
+ }
+ add_privacy_params(tdata, rpid_hdr, id);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)rpid_hdr);
+}
+
+/*!
+ * \internal
+ * \brief Add any appropriate identification headers to an outbound SIP message
+ *
+ * This will determine if an outbound message should have identification headers and
+ * will add the appropriately configured headers
+ *
+ * \param session The session on which we will be sending the message
+ * \param tdata The outbound message
+ * \param The identity information to place on the message
+ */
+static void add_id_headers(const struct ast_sip_session *session, pjsip_tx_data *tdata, const struct ast_party_id *id)
+{
+ if (((id->name.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED ||
+ (id->number.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED) &&
+ !session->endpoint->trust_id_outbound) {
+ return;
+ }
+ if (session->endpoint->send_pai) {
+ add_pai_header(tdata, id);
+ }
+ if (session->endpoint->send_rpid) {
+ add_rpid_header(tdata, id);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Session supplement callback for outgoing INVITE requests
+ *
+ * For an initial INVITE request, we may change the From header to appropriately
+ * reflect the identity information. On all INVITEs (initial and reinvite) we may
+ * add other identity headers such as P-Asserted-Identity and Remote-Party-ID based
+ * on configuration and privacy settings
+ *
+ * \param session The session on which the INVITE will be sent
+ * \param tdata The outbound INVITE request
+ */
+static void caller_id_outgoing_request(struct ast_sip_session *session, pjsip_tx_data *tdata)
+{
+ struct ast_party_id connected_id;
+
+ if (!session->channel) {
+ return;
+ }
+
+ connected_id = ast_channel_connected_effective_id(session->channel);
+ if (session->inv_session->state < PJSIP_INV_STATE_CONFIRMED &&
+ session->endpoint->trust_id_outbound &&
+ (connected_id.name.presentation & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED &&
+ (connected_id.name.presentation & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) {
+ /* Only change the From header on the initial outbound INVITE. Switching it
+ * mid-call might confuse some UAs.
+ */
+ pjsip_fromto_hdr *from;
+ pjsip_dialog *dlg;
+
+ from = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_FROM, tdata->msg->hdr.next);
+ dlg = session->inv_session->dlg;
+
+ modify_id_header(tdata->pool, from, &connected_id);
+ modify_id_header(dlg->pool, dlg->local.info, &connected_id);
+ if (should_queue_connected_line_update(session, &session->endpoint->id)) {
+ queue_connected_line_update(session, &session->endpoint->id);
+ }
+ }
+ add_id_headers(session, tdata, &connected_id);
+}
+
+/*!
+ * \internal
+ * \brief Session supplement for outgoing INVITE response
+ *
+ * This will add P-Asserted-Identity and Remote-Party-ID headers if necessary
+ *
+ * \param session The session on which the INVITE response is to be sent
+ * \param tdata The outbound INVITE response
+ */
+static void caller_id_outgoing_response(struct ast_sip_session *session, pjsip_tx_data *tdata)
+{
+ struct ast_party_id connected_id;
+
+ if (!session->channel) {
+ return;
+ }
+ connected_id = ast_channel_connected_effective_id(session->channel);
+ add_id_headers(session, tdata, &connected_id);
+}
+
+static struct ast_sip_session_supplement caller_id_supplement = {
+ .method = "INVITE",
+ .priority = AST_SIP_SESSION_SUPPLEMENT_PRIORITY_CHANNEL - 1000,
+ .incoming_request = caller_id_incoming_request,
+ .incoming_response = caller_id_incoming_response,
+ .outgoing_request = caller_id_outgoing_request,
+ .outgoing_response = caller_id_outgoing_response,
+};
+
+static int load_module(void)
+{
+ ast_sip_session_register_supplement(&caller_id_supplement);
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ ast_sip_session_unregister_supplement(&caller_id_supplement);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Caller ID Support",
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_APP_DEPEND,
+ );
diff --git a/res/res_sip_dtmf_info.c b/res/res_sip_dtmf_info.c
new file mode 100644
index 000000000..453e57d06
--- /dev/null
+++ b/res/res_sip_dtmf_info.c
@@ -0,0 +1,128 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jason Parker <jparker@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>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/res_sip_session.h"
+#include "asterisk/module.h"
+
+static int dtmf_info_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
+{
+ int res = 0;
+ pjsip_msg_body *body = rdata->msg_info.msg->body;
+
+ pjsip_tx_data *tdata;
+
+ char buf[body->len];
+ char *cur = buf;
+ char *line;
+
+ char event = '\0';
+ unsigned int duration = 0;
+
+ if (pj_strcmp2(&body->content_type.type, "application") ||
+ pj_strcmp2(&body->content_type.subtype, "dtmf-relay")) {
+ return 0;
+ }
+
+ body->print_body(body, buf, body->len);
+
+ while ((line = strsep(&cur, "\r\n"))) {
+ char *c;
+
+ if (!(c = strchr(line, '='))) {
+ continue;
+ }
+ *c++ = '\0';
+
+ c = ast_skip_blanks(c);
+
+ if (!strcasecmp(line, "signal")) {
+ if (c[0] == '!' || c[0] == '*' || c[0] == '#' ||
+ ('0' <= c[0] && c[0] <= '9') ||
+ ('A' <= c[0] && c[0] <= 'D') ||
+ ('a' <= c[0] && c[0] <= 'd')) {
+ event = c[0];
+ } else {
+ ast_log(LOG_ERROR, "Invalid DTMF event signal in INFO message.\n");
+ res = -1;
+ break;
+ }
+ } else if (!strcasecmp(line, "duration")) {
+ sscanf(c, "%30u", &duration);
+ }
+ }
+
+ if (!duration) {
+ duration = 100;
+ }
+
+ if (event == '!') {
+ struct ast_frame f = { AST_FRAME_CONTROL, { AST_CONTROL_FLASH, } };
+
+ ast_queue_frame(session->channel, &f);
+ } else if (event != '\0') {
+ struct ast_frame f = { AST_FRAME_DTMF, };
+ f.len = duration;
+ f.subclass.integer = event;
+
+ ast_queue_frame(session->channel, &f);
+ } else {
+ res = -1;
+ }
+
+ if (pjsip_dlg_create_response(session->inv_session->dlg, rdata, !res ? 200 : 500, NULL, &tdata) == PJ_SUCCESS) {
+ struct pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
+
+ pjsip_dlg_send_response(session->inv_session->dlg, tsx, tdata);
+ }
+
+ return res;
+}
+
+static struct ast_sip_session_supplement dtmf_info_supplement = {
+ .method = "INFO",
+ .incoming_request = dtmf_info_incoming_request,
+};
+
+static int load_module(void)
+{
+ ast_sip_session_register_supplement(&dtmf_info_supplement);
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ ast_sip_session_unregister_supplement(&dtmf_info_supplement);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP DTMF INFO Support",
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_APP_DEPEND,
+ );
diff --git a/res/res_sip_endpoint_identifier_constant.c b/res/res_sip_endpoint_identifier_constant.c
new file mode 100644
index 000000000..e519a9ee8
--- /dev/null
+++ b/res/res_sip_endpoint_identifier_constant.c
@@ -0,0 +1,67 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ *
+ * Includes code and algorithms from the Zapata library.
+ *
+ * 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>
+ <defaultenabled>no</defaultenabled>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/module.h"
+
+static struct ast_sip_endpoint *constant_identify(pjsip_rx_data *rdata)
+{
+ /* This endpoint identifier always returns the same endpoint. It's used
+ * simply for testing. It allocates an endpoint from sorcery so default values
+ * do get applied.
+ */
+ struct ast_sip_endpoint *endpoint = ast_sorcery_alloc(ast_sip_get_sorcery(), "endpoint", NULL);
+ if (!endpoint) {
+ return NULL;
+ }
+ ast_parse_allow_disallow(&endpoint->prefs, endpoint->codecs, "ulaw", 1);
+ return endpoint;
+}
+
+static struct ast_sip_endpoint_identifier constant_identifier = {
+ .identify_endpoint = constant_identify,
+};
+
+static int load_module(void)
+{
+ ast_sip_register_endpoint_identifier(&constant_identifier);
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Constant Endpoint Identifier",
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_APP_DEPEND,
+ );
diff --git a/res/res_sip_endpoint_identifier_ip.c b/res/res_sip_endpoint_identifier_ip.c
new file mode 100644
index 000000000..49c70b59d
--- /dev/null
+++ b/res/res_sip_endpoint_identifier_ip.c
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+/*** MODULEINFO
+ <depend>pjproject</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/module.h"
+#include "asterisk/acl.h"
+
+/*! \brief Structure for an IP identification matching object */
+struct ip_identify_match {
+ /*! \brief Sorcery object details */
+ SORCERY_OBJECT(details);
+ /*! \brief Stringfields */
+ AST_DECLARE_STRING_FIELDS(
+ /*! The name of the endpoint */
+ AST_STRING_FIELD(endpoint_name);
+ );
+ /*! \brief Networks or addresses that should match this */
+ struct ast_ha *matches;
+};
+
+/*! \brief Destructor function for a matching object */
+static void ip_identify_destroy(void *obj)
+{
+ struct ip_identify_match *identify = obj;
+
+ ast_string_field_free_memory(identify);
+ ast_free_ha(identify->matches);
+}
+
+/*! \brief Allocator function for a matching object */
+static void *ip_identify_alloc(const char *name)
+{
+ struct ip_identify_match *identify = ao2_alloc(sizeof(*identify), ip_identify_destroy);
+
+ if (!identify || ast_string_field_init(identify, 256)) {
+ ao2_cleanup(identify);
+ return NULL;
+ }
+
+ return identify;
+}
+
+/*! \brief Comparator function for a matching object */
+static int ip_identify_match_check(void *obj, void *arg, int flags)
+{
+ struct ip_identify_match *identify = obj;
+ struct ast_sockaddr *addr = arg;
+
+ return (ast_apply_ha(identify->matches, addr) != AST_SENSE_ALLOW) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static struct ast_sip_endpoint *ip_identify(pjsip_rx_data *rdata)
+{
+ struct ast_sockaddr addr = { { 0, } };
+ RAII_VAR(struct ao2_container *, candidates, NULL, ao2_cleanup);
+ RAII_VAR(struct ip_identify_match *, match, NULL, ao2_cleanup);
+
+ /* If no possibilities exist return early to save some time */
+ if (!(candidates = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "identify", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL)) ||
+ !ao2_container_count(candidates)) {
+ return NULL;
+ }
+
+ ast_sockaddr_parse(&addr, rdata->pkt_info.src_name, PARSE_PORT_FORBID);
+ ast_sockaddr_set_port(&addr, rdata->pkt_info.src_port);
+
+ if (!(match = ao2_callback(candidates, 0, ip_identify_match_check, &addr))) {
+ return NULL;
+ }
+
+ return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", match->endpoint_name);
+}
+
+static struct ast_sip_endpoint_identifier ip_identifier = {
+ .identify_endpoint = ip_identify,
+};
+
+/*! \brief Custom handler for match field */
+static int ip_identify_match_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct ip_identify_match *identify = obj;
+ int error = 0;
+
+ /* We deny what we actually want to match because there is an implicit permit all rule for ACLs */
+ if (!(identify->matches = ast_append_ha("d", var->value, identify->matches, &error))) {
+ return -1;
+ }
+
+ return error;
+}
+
+static int load_module(void)
+{
+ ast_sorcery_apply_default(ast_sip_get_sorcery(), "identify", "config", "res_sip.conf,criteria=type=identify");
+
+ if (ast_sorcery_object_register(ast_sip_get_sorcery(), "identify", ip_identify_alloc, NULL, NULL)) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "type", "", OPT_NOOP_T, 0, 0);
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "endpoint", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, endpoint_name));
+ ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "identify", "match", "", ip_identify_match_handler, NULL, 0, 0);
+ ast_sorcery_reload_object(ast_sip_get_sorcery(), "identify");
+
+ ast_sip_register_endpoint_identifier(&ip_identifier);
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int reload_module(void)
+{
+ ast_sorcery_reload_object(ast_sip_get_sorcery(), "identify");
+ return 0;
+}
+
+static int unload_module(void)
+{
+ ast_sip_unregister_endpoint_identifier(&ip_identifier);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP IP endpoint identifier",
+ .load = load_module,
+ .reload = reload_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_APP_DEPEND,
+ );
diff --git a/res/res_sip_endpoint_identifier_user.c b/res/res_sip_endpoint_identifier_user.c
new file mode 100644
index 000000000..cd1f76bb1
--- /dev/null
+++ b/res/res_sip_endpoint_identifier_user.c
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+/*** MODULEINFO
+ <depend>pjproject</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/module.h"
+
+static int get_endpoint_details(pjsip_rx_data *rdata, char *endpoint, size_t endpoint_size, char *domain, size_t domain_size)
+{
+ pjsip_uri *from = rdata->msg_info.from->uri;
+ pjsip_sip_uri *sip_from;
+ if (!PJSIP_URI_SCHEME_IS_SIP(from) && !PJSIP_URI_SCHEME_IS_SIPS(from)) {
+ return -1;
+ }
+ sip_from = (pjsip_sip_uri *) pjsip_uri_get_uri(from);
+ ast_copy_pj_str(endpoint, &sip_from->user, endpoint_size);
+ ast_copy_pj_str(domain, &sip_from->host, domain_size);
+ return 0;
+}
+
+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 struct ast_sip_endpoint *username_identify(pjsip_rx_data *rdata)
+{
+ char endpoint_name[64], domain_name[64], id[AST_UUID_STR_LEN];
+ struct ast_sip_endpoint *endpoint;
+ RAII_VAR(struct ast_sip_domain_alias *, alias, NULL, ao2_cleanup);
+ RAII_VAR(struct ao2_container *, transports, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
+
+ if (get_endpoint_details(rdata, endpoint_name, sizeof(endpoint_name), domain_name, sizeof(domain_name))) {
+ return NULL;
+ }
+
+ /* Attempt to find the endpoint given the name and domain provided */
+ snprintf(id, sizeof(id), "%s@%s", endpoint_name, domain_name);
+ if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
+ goto done;
+ }
+
+ /* See if an alias exists for the domain provided */
+ if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) {
+ snprintf(id, sizeof(id), "%s@%s", endpoint_name, alias->domain);
+ if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
+ goto done;
+ }
+ }
+
+ /* See if the transport this came in on has a provided domain */
+ if ((transports = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL)) &&
+ (transport = ao2_callback(transports, 0, find_transport_in_use, rdata)) &&
+ !ast_strlen_zero(transport->domain)) {
+ snprintf(id, sizeof(id), "%s@%s", endpoint_name, transport->domain);
+ if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
+ goto done;
+ }
+ }
+
+ /* Fall back to no domain */
+ endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
+
+done:
+ if (endpoint) {
+ if (!(endpoint->ident_method & AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME)) {
+ ao2_ref(endpoint, -1);
+ return NULL;
+ }
+ ast_debug(3, "Retrieved endpoint %s\n", ast_sorcery_object_get_id(endpoint));
+ }
+ return endpoint;
+}
+
+static struct ast_sip_endpoint_identifier username_identifier = {
+ .identify_endpoint = username_identify,
+};
+
+static int load_module(void)
+{
+ ast_sip_register_endpoint_identifier(&username_identifier);
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ ast_sip_unregister_endpoint_identifier(&username_identifier);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP username endpoint identifier",
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_APP_DEPEND,
+ );
diff --git a/res/res_sip_logger.c b/res/res_sip_logger.c
new file mode 100644
index 000000000..da1719810
--- /dev/null
+++ b/res/res_sip_logger.c
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+/*** MODULEINFO
+ <depend>pjproject</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/module.h"
+#include "asterisk/logger.h"
+
+static pj_status_t logging_on_tx_msg(pjsip_tx_data *tdata)
+{
+ ast_verbose("<--- Transmitting SIP %s (%d bytes) to %s:%s:%d --->\n%.*s\n",
+ tdata->msg->type == PJSIP_REQUEST_MSG ? "request" : "response",
+ (int) (tdata->buf.cur - tdata->buf.start),
+ tdata->tp_info.transport->type_name,
+ tdata->tp_info.dst_name,
+ tdata->tp_info.dst_port,
+ (int) (tdata->buf.end - tdata->buf.start), tdata->buf.start);
+ return PJ_SUCCESS;
+}
+
+static pj_bool_t logging_on_rx_msg(pjsip_rx_data *rdata)
+{
+ ast_verbose("<--- Received SIP %s (%d bytes) from %s:%s:%d --->\n%s\n",
+ rdata->msg_info.msg->type == PJSIP_REQUEST_MSG ? "request" : "response",
+ rdata->msg_info.len,
+ rdata->tp_info.transport->type_name,
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port,
+ rdata->pkt_info.packet);
+ return PJ_FALSE;
+}
+
+static pjsip_module logging_module = {
+ .name = { "Logging Module", 14 },
+ .priority = 0,
+ .on_rx_request = logging_on_rx_msg,
+ .on_rx_response = logging_on_rx_msg,
+ .on_tx_request = logging_on_tx_msg,
+ .on_tx_response = logging_on_tx_msg,
+};
+
+static int load_module(void)
+{
+ ast_sip_register_service(&logging_module);
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ ast_sip_unregister_service(&logging_module);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Packet Logger",
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_APP_DEPEND,
+ );
diff --git a/res/res_sip_mwi.c b/res/res_sip_mwi.c
new file mode 100644
index 000000000..7d62816d0
--- /dev/null
+++ b/res/res_sip_mwi.c
@@ -0,0 +1,709 @@
+/*
+ * 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 <pjsip_simple.h>
+#include <pjlib.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/res_sip_pubsub.h"
+#include "asterisk/module.h"
+#include "asterisk/logger.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/sorcery.h"
+#include "asterisk/stasis.h"
+#include "asterisk/app.h"
+
+/*** MODULEINFO
+ <depend>pjproject</depend>
+ <depend>res_sip</depend>
+ <depend>res_sip_pubsub</depend>
+ <support_level>core</support_level>
+ ***/
+
+struct mwi_subscription;
+AO2_GLOBAL_OBJ_STATIC(unsolicited_mwi);
+
+#define STASIS_BUCKETS 13
+#define MWI_BUCKETS 53
+static void mwi_subscription_shutdown(struct ast_sip_subscription *sub);
+static struct ast_sip_subscription *mwi_new_subscribe(struct ast_sip_endpoint *endpoint,
+ pjsip_rx_data *rdata);
+static void mwi_resubscribe(struct ast_sip_subscription *sub, pjsip_rx_data *rdata,
+ struct ast_sip_subscription_response_data *response_data);
+static void mwi_subscription_timeout(struct ast_sip_subscription *sub);
+static void mwi_subscription_terminated(struct ast_sip_subscription *sub, pjsip_rx_data *rdata);
+static void mwi_notify_response(struct ast_sip_subscription *sub, pjsip_rx_data *rdata);
+static void mwi_notify_request(struct ast_sip_subscription *sub, pjsip_rx_data *rdata,
+ struct ast_sip_subscription_response_data *response_data);
+static int mwi_refresh_subscription(struct ast_sip_subscription *sub);
+
+static struct ast_sip_subscription_handler mwi_handler = {
+ .event_name = "message-summary",
+ .accept = { "application/simple-message-summary", },
+ .subscription_shutdown = mwi_subscription_shutdown,
+ .new_subscribe = mwi_new_subscribe,
+ .resubscribe = mwi_resubscribe,
+ .subscription_timeout = mwi_subscription_timeout,
+ .subscription_terminated = mwi_subscription_terminated,
+ .notify_response = mwi_notify_response,
+ .notify_request = mwi_notify_request,
+ .refresh_subscription = mwi_refresh_subscription,
+};
+
+/*!
+ * \brief Wrapper for stasis subscription
+ *
+ * An MWI subscription has a container of these. This
+ * represents a stasis subscription for MWI state.
+ */
+struct mwi_stasis_subscription {
+ /*! The MWI stasis subscription */
+ struct stasis_subscription *stasis_sub;
+ /*! The mailbox corresponding with the MWI subscription. Used as a hash key */
+ char mailbox[1];
+};
+
+/*!
+ * \brief A subscription for MWI
+ *
+ * This subscription is the basis for MWI for an endpoint. Each
+ * endpoint that uses MWI will have a corresponding mwi_subscription.
+ *
+ * This structure acts as the owner for the underlying SIP subscription.
+ * When the mwi_subscription is destroyed, the SIP subscription dies, too.
+ * The mwi_subscription's lifetime is governed by its underlying stasis
+ * subscriptions. When all stasis subscriptions are destroyed, the
+ * mwi_subscription is destroyed as well.
+ */
+struct mwi_subscription {
+ /*! Container of \ref mwi_stasis_subscription structures.
+ * A single MWI subscription may be fore multiple mailboxes, thus
+ * requiring multiple stasis subscriptions
+ */
+ struct ao2_container *stasis_subs;
+ /*! The SIP subscription. Unsolicited MWI does not use this */
+ struct ast_sip_subscription *sip_sub;
+ /*! Is the MWI solicited (i.e. Initiated with an external SUBSCRIBE) ? */
+ unsigned int is_solicited;
+ /*! Identifier for the subscription.
+ * The identifier is the same as the corresponding endpoint's stasis ID.
+ * Used as a hash key
+ */
+ char id[1];
+};
+
+static void mwi_stasis_cb(void *userdata, struct stasis_subscription *sub,
+ struct stasis_topic *topic, struct stasis_message *msg);
+
+static struct mwi_stasis_subscription *mwi_stasis_subscription_alloc(const char *mailbox, struct mwi_subscription *mwi_sub)
+{
+ struct mwi_stasis_subscription *mwi_stasis_sub;
+ struct stasis_topic *topic;
+
+ if (!mwi_sub) {
+ return NULL;
+ }
+
+ mwi_stasis_sub = ao2_alloc(sizeof(*mwi_stasis_sub) + strlen(mailbox), NULL);
+ if (!mwi_stasis_sub) {
+ return NULL;
+ }
+
+ topic = stasis_mwi_topic(mailbox);
+
+ /* Safe strcpy */
+ strcpy(mwi_stasis_sub->mailbox, mailbox);
+ ao2_ref(mwi_sub, +1);
+ ast_debug(3, "Creating stasis MWI subscription to mailbox %s for endpoint %s\n", mailbox, mwi_sub->id);
+ mwi_stasis_sub->stasis_sub = stasis_subscribe(topic, mwi_stasis_cb, mwi_sub);
+ return mwi_stasis_sub;
+}
+
+static int stasis_sub_hash(const void *obj, int flags)
+{
+ const struct mwi_stasis_subscription *mwi_stasis = obj;
+
+ return ast_str_hash(mwi_stasis->mailbox);
+}
+
+static int stasis_sub_cmp(void *obj, void *arg, int flags)
+{
+ struct mwi_stasis_subscription *mwi_stasis1 = obj;
+ struct mwi_stasis_subscription *mwi_stasis2 = arg;
+
+ return strcmp(mwi_stasis1->mailbox, mwi_stasis2->mailbox) ? 0 : CMP_MATCH;
+}
+
+static void mwi_subscription_destructor(void *obj)
+{
+ struct mwi_subscription *sub = obj;
+
+ ast_debug(3, "Destroying MWI subscription for endpoint %s\n", sub->id);
+ ao2_cleanup(sub->sip_sub);
+ ao2_cleanup(sub->stasis_subs);
+}
+
+static struct mwi_subscription *mwi_subscription_alloc(struct ast_sip_endpoint *endpoint,
+ enum ast_sip_subscription_role role, unsigned int is_solicited, pjsip_rx_data *rdata)
+{
+ struct mwi_subscription *sub;
+ const char *endpoint_id = ast_sorcery_object_get_id(endpoint);
+
+ sub = ao2_alloc(sizeof(*sub) + strlen(endpoint_id),
+ mwi_subscription_destructor);
+
+ if (!sub) {
+ return NULL;
+ }
+
+ /* Safe strcpy */
+ strcpy(sub->id, endpoint_id);
+ /* Unsolicited MWI doesn't actually result in a SIP subscription being
+ * created. This is because a SIP subscription associates with a dialog.
+ * Most devices expect unsolicited MWI NOTIFYs to appear out of dialog. If
+ * they receive an in-dialog MWI NOTIFY (i.e. with a to-tag), then they
+ * will reject the NOTIFY with a 481, thus resulting in message-waiting
+ * state not being updated on the device
+ */
+ if (is_solicited) {
+ sub->sip_sub = ast_sip_create_subscription(&mwi_handler,
+ role, endpoint, rdata);
+ if (!sub->sip_sub) {
+ ast_log(LOG_WARNING, "Unable to create MWI SIP subscription for endpoint %s\n", sub->id);
+ ao2_cleanup(sub);
+ return NULL;
+ }
+ }
+
+ sub->stasis_subs = ao2_container_alloc(STASIS_BUCKETS, stasis_sub_hash, stasis_sub_cmp);
+ if (!sub->stasis_subs) {
+ ao2_cleanup(sub);
+ return NULL;
+ }
+ sub->is_solicited = is_solicited;
+
+ ast_debug(3, "Created %s MWI subscription for endpoint %s\n", is_solicited ? "solicited" : "unsolicited", sub->id);
+
+ return sub;
+}
+
+static int mwi_sub_hash(const void *obj, int flags)
+{
+ const struct mwi_subscription *mwi_sub = obj;
+
+ return ast_str_hash(mwi_sub->id);
+}
+
+static int mwi_sub_cmp(void *obj, void *arg, int flags)
+{
+ struct mwi_subscription *mwi_sub1 = obj;
+ struct mwi_subscription *mwi_sub2 = arg;
+
+ return strcmp(mwi_sub1->id, mwi_sub2->id) ? 0 : CMP_MATCH;
+}
+
+struct message_accumulator {
+ int old_msgs;
+ int new_msgs;
+ const char *reason;
+};
+
+static int get_message_count(void *obj, void *arg, int flags)
+{
+ RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+ struct mwi_stasis_subscription *mwi_stasis = obj;
+ struct message_accumulator *counter = arg;
+ struct stasis_mwi_state *mwi_state;
+
+ msg = stasis_cache_get(stasis_mwi_topic_cached(), stasis_mwi_state_type(), mwi_stasis->mailbox);
+ if (!msg) {
+ return 0;
+ }
+
+ mwi_state = stasis_message_data(msg);
+ counter->old_msgs += mwi_state->old_msgs;
+ counter->new_msgs += mwi_state->new_msgs;
+ return 0;
+}
+
+struct unsolicited_mwi_data {
+ struct mwi_subscription *sub;
+ struct ast_sip_endpoint *endpoint;
+ pjsip_evsub_state state;
+ const char *reason;
+ const pjsip_media_type *mwi_type;
+ const pj_str_t *body_text;
+};
+
+static int send_unsolicited_mwi_notify_to_contact(void *obj, void *arg, int flags)
+{
+ struct unsolicited_mwi_data *mwi_data = arg;
+ struct mwi_subscription *sub = mwi_data->sub;
+ struct ast_sip_endpoint *endpoint = mwi_data->endpoint;
+ pjsip_evsub_state state = mwi_data->state;
+ const char *reason = mwi_data->reason;
+ const pjsip_media_type *mwi_type = mwi_data->mwi_type;
+ const pj_str_t *body_text = mwi_data->body_text;
+ struct ast_sip_contact *contact = obj;
+ const char *state_name;
+ pjsip_tx_data *tdata;
+ pjsip_msg_body *msg_body;
+ pjsip_sub_state_hdr *sub_state;
+ pjsip_event_hdr *event;
+ const pjsip_hdr *allow_events = pjsip_evsub_get_allow_events_hdr(NULL);
+
+ if (ast_sip_create_request("NOTIFY", NULL, endpoint, contact->uri, &tdata)) {
+ ast_log(LOG_WARNING, "Unable to create unsolicited NOTIFY request to endpoint %s URI %s\n", sub->id, contact->uri);
+ return 0;
+ }
+
+ switch (state) {
+ case PJSIP_EVSUB_STATE_ACTIVE:
+ state_name = "active";
+ break;
+ case PJSIP_EVSUB_STATE_TERMINATED:
+ default:
+ state_name = "terminated";
+ break;
+ }
+
+ sub_state = pjsip_sub_state_hdr_create(tdata->pool);
+ pj_cstr(&sub_state->sub_state, state_name);
+ if (reason) {
+ pj_cstr(&sub_state->reason_param, reason);
+ }
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) sub_state);
+
+ event = pjsip_event_hdr_create(tdata->pool);
+ pj_cstr(&event->event_type, "message-summary");
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) event);
+
+ pjsip_msg_add_hdr(tdata->msg, pjsip_hdr_shallow_clone(tdata->pool, allow_events));
+ msg_body = pjsip_msg_body_create(tdata->pool, &mwi_type->type, &mwi_type->subtype, body_text);
+ tdata->msg->body = msg_body;
+ ast_sip_send_request(tdata, NULL, endpoint);
+
+ return 0;
+}
+
+static void send_unsolicited_mwi_notify(struct mwi_subscription *sub, pjsip_evsub_state state, const char *reason,
+ const pjsip_media_type *mwi_type, const pj_str_t *body_text)
+{
+ RAII_VAR(struct ast_sip_endpoint *, endpoint, ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(),
+ "endpoint", sub->id), ao2_cleanup);
+ char *endpoint_aors;
+ char *aor_name;
+
+ if (!endpoint) {
+ ast_log(LOG_WARNING, "Unable to send unsolicited MWI to %s because endpoint does not exist\n",
+ sub->id);
+ return;
+ }
+ if (ast_strlen_zero(endpoint->aors)) {
+ ast_log(LOG_WARNING, "Unable to send unsolicited MWI to %s because the endpoint has no"
+ " configured AORs\n", sub->id);
+ return;
+ }
+
+ endpoint_aors = ast_strdupa(endpoint->aors);
+
+ while ((aor_name = strsep(&endpoint_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);
+ struct unsolicited_mwi_data mwi_data = {
+ .sub = sub,
+ .endpoint = endpoint,
+ .state = state,
+ .reason = reason,
+ .mwi_type = mwi_type,
+ .body_text = body_text,
+ };
+
+ if (!aor) {
+ ast_log(LOG_WARNING, "Unable to locate AOR %s for unsolicited MWI\n", aor_name);
+ continue;
+ }
+
+ contacts = ast_sip_location_retrieve_aor_contacts(aor);
+ if (!contacts || (ao2_container_count(contacts) == 0)) {
+ ast_log(LOG_WARNING, "No contacts bound to AOR %s. Cannot send unsolicited MWI.\n", aor_name);
+ continue;
+ }
+
+ ao2_callback(contacts, OBJ_NODATA, send_unsolicited_mwi_notify_to_contact, &mwi_data);
+ }
+}
+
+static void send_mwi_notify(struct mwi_subscription *sub, pjsip_evsub_state state, const char *reason)
+{
+ const pj_str_t *reason_str_ptr = NULL;
+ static pjsip_media_type mwi_type = {
+ .type = { "application", 11 },
+ .subtype = { "simple-message-summary", 22 },
+ };
+ struct message_accumulator counter = {
+ .old_msgs = 0,
+ .new_msgs = 0,
+ };
+ RAII_VAR(struct ast_str *, body, ast_str_create(64), ast_free_ptr);
+ pjsip_tx_data *tdata;
+ pj_str_t reason_str;
+ pj_str_t pj_body;
+
+ ao2_callback(sub->stasis_subs, OBJ_NODATA, get_message_count, &counter);
+
+ if (reason) {
+ pj_cstr(&reason_str, reason);
+ reason_str_ptr = &reason_str;
+ }
+ ast_str_append(&body, 0, "Messages-Waiting: %s\r\n", counter.new_msgs ? "yes" : "no");
+ ast_str_append(&body, 0, "Voice-Message: %d/%d (0/0)\r\n", counter.new_msgs, counter.old_msgs);
+ pj_cstr(&pj_body, ast_str_buffer(body));
+
+ ast_debug(5, "Sending %s MWI NOTIFY to endpoint %s, new messages: %d, old messages: %d\n",
+ sub->is_solicited ? "solicited" : "unsolicited", sub->id, counter.new_msgs,
+ counter.old_msgs);
+
+ if (sub->is_solicited) {
+ if (pjsip_mwi_notify(ast_sip_subscription_get_evsub(sub->sip_sub),
+ state,
+ NULL,
+ reason_str_ptr,
+ &mwi_type,
+ &pj_body,
+ &tdata) != PJ_SUCCESS) {
+ ast_log(LOG_WARNING, "Unable to create MWI NOTIFY request to %s.\n", sub->id);
+ return;
+ }
+ if (ast_sip_subscription_send_request(sub->sip_sub, tdata) != PJ_SUCCESS) {
+ ast_log(LOG_WARNING, "Unable to send MWI NOTIFY request to %s\n", sub->id);
+ return;
+ }
+ } else {
+ send_unsolicited_mwi_notify(sub, state, reason, &mwi_type, &pj_body);
+ }
+}
+
+static int unsubscribe_stasis(void *obj, void *arg, int flags)
+{
+ struct mwi_stasis_subscription *mwi_stasis = obj;
+ if (mwi_stasis->stasis_sub) {
+ ast_debug(3, "Removing stasis subscription to mailbox %s\n", mwi_stasis->mailbox);
+ mwi_stasis->stasis_sub = stasis_unsubscribe(mwi_stasis->stasis_sub);
+ }
+ return CMP_MATCH;
+}
+
+static void mwi_subscription_shutdown(struct ast_sip_subscription *sub)
+{
+ struct mwi_subscription *mwi_sub;
+ RAII_VAR(struct ast_datastore *, mwi_datastore,
+ ast_sip_subscription_get_datastore(sub, "MWI datastore"), ao2_cleanup);
+
+ if (!mwi_datastore) {
+ return;
+ }
+
+ mwi_sub = mwi_datastore->data;
+ ao2_callback(mwi_sub->stasis_subs, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, unsubscribe_stasis, NULL);
+}
+
+static struct ast_datastore_info mwi_ds_info = { };
+
+static int add_mwi_datastore(struct mwi_subscription *sub)
+{
+ RAII_VAR(struct ast_datastore *, mwi_datastore, NULL, ao2_cleanup);
+
+ mwi_datastore = ast_sip_subscription_alloc_datastore(&mwi_ds_info, "MWI datastore");
+ if (!mwi_datastore) {
+ return -1;
+ }
+ mwi_datastore->data = sub;
+
+ ast_sip_subscription_add_datastore(sub->sip_sub, mwi_datastore);
+ return 0;
+}
+
+static struct ast_sip_subscription *mwi_new_subscribe(struct ast_sip_endpoint *endpoint,
+ pjsip_rx_data *rdata)
+{
+ RAII_VAR(struct ast_sip_aor *, aor, NULL, ao2_cleanup);
+ /* It's not obvious here, but the reference(s) to this subscription,
+ * once this function exits, is held by the stasis subscription(s)
+ * created in mwi_stasis_subscription_alloc()
+ */
+ RAII_VAR(struct mwi_subscription *, sub, NULL, ao2_cleanup);
+ pjsip_uri *ruri = rdata->msg_info.msg->line.req.uri;
+ pjsip_sip_uri *sip_ruri;
+ pjsip_evsub *evsub;
+ char aor_name[80];
+ char *mailboxes;
+ char *mailbox;
+
+ if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) {
+ ast_log(LOG_WARNING, "Attempt to SUBSCRIBE to a non-SIP URI\n");
+ return NULL;
+ }
+ sip_ruri = pjsip_uri_get_uri(ruri);
+ ast_copy_pj_str(aor_name, &sip_ruri->user, sizeof(aor_name));
+
+ aor = ast_sip_location_retrieve_aor(aor_name);
+ if (!aor) {
+ ast_log(LOG_WARNING, "Unable to locate aor %s. MWI subscription failed.\n", aor_name);
+ return NULL;
+ }
+
+ if (ast_strlen_zero(aor->mailboxes)) {
+ ast_log(LOG_WARNING, "AOR %s has no configured mailboxes. MWI subscription failed\n", aor_name);
+ return NULL;
+ }
+
+ sub = mwi_subscription_alloc(endpoint, AST_SIP_NOTIFIER, 1, rdata);
+ if (!sub) {
+ return NULL;
+ }
+
+ if (add_mwi_datastore(sub)) {
+ ast_log(LOG_WARNING, "Unable to allocate datastore on MWI subscription from %s\n", sub->id);
+ return NULL;
+ }
+
+ mailboxes = ast_strdupa(aor->mailboxes);
+ while ((mailbox = strsep(&mailboxes, ","))) {
+ RAII_VAR(struct mwi_stasis_subscription *, mwi_stasis_sub,
+ mwi_stasis_subscription_alloc(mailbox, sub), ao2_cleanup);
+ if (mwi_stasis_sub) {
+ ao2_link(sub->stasis_subs, mwi_stasis_sub);
+ }
+ }
+
+ evsub = ast_sip_subscription_get_evsub(sub->sip_sub);
+ pjsip_evsub_accept(evsub, rdata, 200, NULL);
+ send_mwi_notify(sub, PJSIP_EVSUB_STATE_ACTIVE, NULL);
+
+ return sub->sip_sub;
+}
+
+static void mwi_resubscribe(struct ast_sip_subscription *sub,
+ pjsip_rx_data *rdata, struct ast_sip_subscription_response_data *response_data)
+{
+ pjsip_tx_data *tdata;
+
+ pjsip_mwi_current_notify(ast_sip_subscription_get_evsub(sub), &tdata);
+ ast_sip_subscription_send_request(sub, tdata);
+}
+
+static void mwi_subscription_timeout(struct ast_sip_subscription *sub)
+{
+ struct mwi_subscription *mwi_sub;
+ RAII_VAR(struct ast_datastore *, mwi_datastore,
+ ast_sip_subscription_get_datastore(sub, "MWI datastore"), ao2_cleanup);
+
+ if (!mwi_datastore) {
+ return;
+ }
+
+
+ mwi_sub = mwi_datastore->data;
+
+ ast_log(LOG_NOTICE, "MWI subscription for %s has timed out.\n", mwi_sub->id);
+
+ send_mwi_notify(mwi_sub, PJSIP_EVSUB_STATE_TERMINATED, "timeout");
+}
+
+static void mwi_subscription_terminated(struct ast_sip_subscription *sub, pjsip_rx_data *rdata)
+{
+ struct mwi_subscription *mwi_sub;
+ RAII_VAR(struct ast_datastore *, mwi_datastore,
+ ast_sip_subscription_get_datastore(sub, "MWI datastore"), ao2_cleanup);
+
+ if (!mwi_datastore) {
+ return;
+ }
+
+ mwi_sub = mwi_datastore->data;
+
+ ast_log(LOG_NOTICE, "MWI subscription for %s has been terminated\n", mwi_sub->id);
+
+ send_mwi_notify(mwi_sub, PJSIP_EVSUB_STATE_TERMINATED, NULL);
+}
+
+static void mwi_notify_response(struct ast_sip_subscription *sub, pjsip_rx_data *rdata)
+{
+ /* We don't really care about NOTIFY responses for the moment */
+}
+
+static void mwi_notify_request(struct ast_sip_subscription *sub, pjsip_rx_data *rdata,
+ struct ast_sip_subscription_response_data *response_data)
+{
+ ast_log(LOG_WARNING, "Received an MWI NOTIFY request? This should not happen\n");
+}
+
+static int mwi_refresh_subscription(struct ast_sip_subscription *sub)
+{
+ ast_log(LOG_WARNING, "Being told to refresh an MWI subscription? This should not happen\n");
+ return 0;
+}
+
+static int serialized_notify(void *userdata)
+{
+ struct mwi_subscription *mwi_sub = userdata;
+
+ send_mwi_notify(mwi_sub, PJSIP_EVSUB_STATE_ACTIVE, NULL);
+ ao2_ref(mwi_sub, -1);
+ return 0;
+}
+
+static int serialized_cleanup(void *userdata)
+{
+ struct mwi_subscription *mwi_sub = userdata;
+
+ /* This is getting rid of the reference that was added
+ * just before this serialized task was pushed.
+ */
+ ao2_cleanup(mwi_sub);
+ /* This is getting rid of the reference held by the
+ * stasis subscription
+ */
+ ao2_cleanup(mwi_sub);
+ return 0;
+}
+
+static void mwi_stasis_cb(void *userdata, struct stasis_subscription *sub,
+ struct stasis_topic *topic, struct stasis_message *msg)
+{
+ struct mwi_subscription *mwi_sub = userdata;
+
+ if (stasis_subscription_final_message(sub, msg)) {
+ ao2_ref(mwi_sub, +1);
+ ast_sip_push_task(NULL, serialized_cleanup, mwi_sub);
+ return;
+ }
+
+ if (stasis_mwi_state_type() == stasis_message_type(msg)) {
+ struct ast_taskprocessor *serializer = mwi_sub->is_solicited ? ast_sip_subscription_get_serializer(mwi_sub->sip_sub) : NULL;
+ ao2_ref(mwi_sub, +1);
+ ast_sip_push_task(serializer, serialized_notify, mwi_sub);
+ }
+}
+
+static int create_mwi_subscriptions_for_endpoint(void *obj, void *arg, int flags)
+{
+ RAII_VAR(struct mwi_subscription *, aggregate_sub, NULL, ao2_cleanup);
+ struct ast_sip_endpoint *endpoint = obj;
+ struct ao2_container *mwi_subscriptions = arg;
+ char *mailboxes;
+ char *mailbox;
+
+ if (ast_strlen_zero(endpoint->mailboxes)) {
+ return 0;
+ }
+
+ if (endpoint->aggregate_mwi) {
+ aggregate_sub = mwi_subscription_alloc(endpoint, AST_SIP_NOTIFIER, 0, NULL);
+ if (!aggregate_sub) {
+ return 0;
+ }
+ }
+
+ mailboxes = ast_strdupa(endpoint->mailboxes);
+ while ((mailbox = strsep(&mailboxes, ","))) {
+ struct mwi_subscription *sub = aggregate_sub ?:
+ mwi_subscription_alloc(endpoint, AST_SIP_SUBSCRIBER, 0, NULL);
+ RAII_VAR(struct mwi_stasis_subscription *, mwi_stasis_sub,
+ mwi_stasis_subscription_alloc(mailbox, sub), ao2_cleanup);
+ if (mwi_stasis_sub) {
+ ao2_link(sub->stasis_subs, mwi_stasis_sub);
+ }
+ if (!aggregate_sub) {
+ ao2_link(mwi_subscriptions, sub);
+ ao2_cleanup(sub);
+ }
+ }
+ ao2_link(mwi_subscriptions, aggregate_sub);
+ return 0;
+}
+
+static int unsubscribe(void *obj, void *arg, int flags)
+{
+ struct mwi_subscription *mwi_sub = obj;
+
+ ao2_callback(mwi_sub->stasis_subs, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, unsubscribe_stasis, NULL);
+ return CMP_MATCH;
+}
+
+static void create_mwi_subscriptions(void)
+{
+ struct ao2_container *mwi_subscriptions = ao2_container_alloc(MWI_BUCKETS, mwi_sub_hash, mwi_sub_cmp);
+ RAII_VAR(struct ao2_container *, old_mwi_subscriptions, ao2_global_obj_ref(unsolicited_mwi), ao2_cleanup);
+ RAII_VAR(struct ao2_container *, endpoints, ast_sorcery_retrieve_by_fields(
+ ast_sip_get_sorcery(), "endpoint", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL),
+ ao2_cleanup);
+
+ if (!mwi_subscriptions) {
+ return;
+ }
+
+ /* We remove all the old stasis subscriptions first before applying the new configuration. This
+ * prevents a situation where there might be multiple overlapping stasis subscriptions for an
+ * endpoint for mailboxes. Though there may be mailbox changes during the gap between unsubscribing
+ * and resubscribing, up-to-date mailbox state will be sent out to the endpoint when the
+ * new stasis subscription is established
+ */
+ if (old_mwi_subscriptions) {
+ ao2_callback(old_mwi_subscriptions, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, unsubscribe, NULL);
+ }
+ ao2_callback(endpoints, OBJ_NODATA, create_mwi_subscriptions_for_endpoint, mwi_subscriptions);
+ ao2_global_obj_replace_unref(unsolicited_mwi, mwi_subscriptions);
+}
+
+static int reload(void)
+{
+ create_mwi_subscriptions();
+ return 0;
+}
+
+static int load_module(void)
+{
+ if (ast_sip_register_subscription_handler(&mwi_handler)) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ create_mwi_subscriptions();
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ RAII_VAR(struct ao2_container *, mwi_subscriptions, ao2_global_obj_ref(unsolicited_mwi), ao2_cleanup);
+ if (mwi_subscriptions) {
+ ao2_callback(mwi_subscriptions, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, unsubscribe, NULL);
+ ao2_global_obj_release(unsolicited_mwi);
+ }
+ ast_sip_unregister_subscription_handler(&mwi_handler);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP MWI resource",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ .load_pri = AST_MODPRI_CHANNEL_DEPEND,
+);
diff --git a/res/res_sip_nat.c b/res/res_sip_nat.c
new file mode 100644
index 000000000..6c924af68
--- /dev/null
+++ b/res/res_sip_nat.c
@@ -0,0 +1,235 @@
+/*
+ * 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.
+ */
+
+/*** MODULEINFO
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/module.h"
+#include "asterisk/acl.h"
+
+static pj_bool_t nat_on_rx_request(pjsip_rx_data *rdata)
+{
+ RAII_VAR(struct ast_sip_endpoint *, endpoint, ast_pjsip_rdata_get_endpoint(rdata), ao2_cleanup);
+ pjsip_contact_hdr *contact;
+
+ if (!endpoint) {
+ return PJ_FALSE;
+ }
+
+ if (endpoint->rewrite_contact && (contact = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, NULL)) &&
+ (PJSIP_URI_SCHEME_IS_SIP(contact->uri) || PJSIP_URI_SCHEME_IS_SIPS(contact->uri))) {
+ pjsip_sip_uri *uri = pjsip_uri_get_uri(contact->uri);
+
+ pj_cstr(&uri->host, rdata->pkt_info.src_name);
+ uri->port = rdata->pkt_info.src_port;
+ }
+
+ if (endpoint->force_rport) {
+ rdata->msg_info.via->rport_param = 0;
+ }
+
+ return PJ_FALSE;
+}
+
+/*! \brief Structure which contains information about a transport */
+struct request_transport_details {
+ /*! \brief Type of transport */
+ enum ast_sip_transport_type type;
+ /*! \brief Potential pointer to the transport itself, if UDP */
+ pjsip_transport *transport;
+ /*! \brief Potential pointer to the transport factory itself, if TCP/TLS */
+ pjsip_tpfactory *factory;
+ /*! \brief Local address for transport */
+ pj_str_t local_address;
+ /*! \brief Local port for transport */
+ int local_port;
+};
+
+/*! \brief Callback function for finding the transport the request is going out on */
+static int find_transport_in_use(void *obj, void *arg, int flags)
+{
+ struct ast_sip_transport *transport = obj;
+ struct request_transport_details *details = arg;
+
+ /* If an explicit transport or factory matches then this is what is in use, if we are unavailable
+ * to compare based on that we make sure that the type is the same and the source IP address/port are the same
+ */
+ if ((details->transport && details->transport == transport->state->transport) ||
+ (details->factory && details->factory == transport->state->factory) ||
+ ((details->type == transport->type) && (transport->state->factory) &&
+ !pj_strcmp(&transport->state->factory->addr_name.host, &details->local_address) &&
+ transport->state->factory->addr_name.port == details->local_port)) {
+ return CMP_MATCH | CMP_STOP;
+ }
+
+ return 0;
+}
+
+/*! \brief Helper function which returns the SIP URI of a Contact header */
+static pjsip_sip_uri *nat_get_contact_sip_uri(pjsip_tx_data *tdata)
+{
+ pjsip_contact_hdr *contact = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
+
+ if (!contact || (!PJSIP_URI_SCHEME_IS_SIP(contact->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact->uri))) {
+ return NULL;
+ }
+
+ return pjsip_uri_get_uri(contact->uri);
+}
+
+/*! \brief Structure which contains hook details */
+struct nat_hook_details {
+ /*! \brief Outgoing message itself */
+ pjsip_tx_data *tdata;
+ /*! \brief Chosen transport */
+ struct ast_sip_transport *transport;
+};
+
+/*! \brief Callback function for invoking hooks */
+static int nat_invoke_hook(void *obj, void *arg, int flags)
+{
+ struct ast_sip_nat_hook *hook = obj;
+ struct nat_hook_details *details = arg;
+
+ if (hook->outgoing_external_message) {
+ hook->outgoing_external_message(details->tdata, details->transport);
+ }
+
+ return 0;
+}
+
+static pj_status_t nat_on_tx_message(pjsip_tx_data *tdata)
+{
+ RAII_VAR(struct ao2_container *, transports, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
+ struct request_transport_details details = { 0, };
+ pjsip_via_hdr *via = NULL;
+ struct ast_sockaddr addr = { { 0, } };
+ pjsip_sip_uri *uri = NULL;
+ RAII_VAR(struct ao2_container *, hooks, NULL, ao2_cleanup);
+
+ /* If a transport selector is in use we know the transport or factory, so explicitly find it */
+ if (tdata->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
+ details.transport = tdata->tp_sel.u.transport;
+ } else if (tdata->tp_sel.type == PJSIP_TPSELECTOR_LISTENER) {
+ details.factory = tdata->tp_sel.u.listener;
+ } else if (tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_UDP || tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_UDP6) {
+ /* Connectionless uses the same transport for all requests */
+ details.type = AST_SIP_TRANSPORT_UDP;
+ details.transport = tdata->tp_info.transport;
+ } else {
+ if (tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_TCP) {
+ details.type = AST_SIP_TRANSPORT_TCP;
+ } else if (tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_TLS) {
+ details.type = AST_SIP_TRANSPORT_TLS;
+ } else {
+ /* Unknown transport type, we can't map and thus can't apply NAT changes */
+ return PJ_SUCCESS;
+ }
+
+ if ((uri = nat_get_contact_sip_uri(tdata))) {
+ details.local_address = uri->host;
+ details.local_port = uri->port;
+ } else if ((tdata->msg->type == PJSIP_REQUEST_MSG) &&
+ (via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL))) {
+ details.local_address = via->sent_by.host;
+ details.local_port = via->sent_by.port;
+ } else {
+ return PJ_SUCCESS;
+ }
+
+ if (!details.local_port) {
+ details.local_port = (details.type == AST_SIP_TRANSPORT_TLS) ? 5061 : 5060;
+ }
+ }
+
+ if (!(transports = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL)) ||
+ !(transport = ao2_callback(transports, 0, find_transport_in_use, &details)) || !transport->localnet ||
+ ast_sockaddr_isnull(&transport->external_address)) {
+ return PJ_SUCCESS;
+ }
+
+ ast_sockaddr_parse(&addr, tdata->tp_info.dst_name, PARSE_PORT_FORBID);
+ ast_sockaddr_set_port(&addr, tdata->tp_info.dst_port);
+
+ /* See if where we are sending this request is local or not, and if not that we can get a Contact URI to modify */
+ if (ast_apply_ha(transport->localnet, &addr) != AST_SENSE_ALLOW) {
+ return PJ_SUCCESS;
+ }
+
+ /* Update the contact header with the external address */
+ if (uri || (uri = nat_get_contact_sip_uri(tdata))) {
+ pj_strdup2(tdata->pool, &uri->host, ast_sockaddr_stringify_host(&transport->external_address));
+ if (transport->external_signaling_port) {
+ uri->port = transport->external_signaling_port;
+ }
+ }
+
+ /* Update the via header if relevant */
+ if ((tdata->msg->type == PJSIP_REQUEST_MSG) && (via || (via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL)))) {
+ pj_strdup2(tdata->pool, &via->sent_by.host, ast_sockaddr_stringify_host(&transport->external_address));
+ if (transport->external_signaling_port) {
+ via->sent_by.port = transport->external_signaling_port;
+ }
+ }
+
+ /* Invoke any additional hooks that may be registered */
+ if ((hooks = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "nat_hook", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL))) {
+ struct nat_hook_details hook_details = {
+ .tdata = tdata,
+ .transport = transport,
+ };
+ ao2_callback(hooks, 0, nat_invoke_hook, &hook_details);
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pjsip_module nat_module = {
+ .name = { "NAT", 3 },
+ .id = -1,
+ .priority = PJSIP_MOD_PRIORITY_TSX_LAYER - 2,
+ .on_rx_request = nat_on_rx_request,
+ .on_tx_request = nat_on_tx_message,
+ .on_tx_response = nat_on_tx_message,
+};
+
+static int load_module(void)
+{
+ ast_sip_register_service(&nat_module);
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ ast_sip_unregister_service(&nat_module);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP NAT Support",
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_APP_DEPEND,
+ );
diff --git a/res/res_sip_outbound_authenticator_digest.c b/res/res_sip_outbound_authenticator_digest.c
new file mode 100644
index 000000000..180c05e27
--- /dev/null
+++ b/res/res_sip_outbound_authenticator_digest.c
@@ -0,0 +1,110 @@
+/*
+ * 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_sip.h"
+#include "asterisk/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/strings.h"
+
+static int set_outbound_authentication_credentials(pjsip_auth_clt_sess *auth_sess, const char **auth_strs, size_t num_auths)
+{
+ struct ast_sip_auth **auths = ast_alloca(num_auths * sizeof(*auths));
+ pjsip_cred_info *auth_creds = ast_alloca(num_auths * sizeof(*auth_creds));
+ int res = 0;
+ int i;
+
+ if (ast_sip_retrieve_auths(auth_strs, num_auths, auths)) {
+ res = -1;
+ goto cleanup;
+ }
+
+ for (i = 0; i < num_auths; ++i) {
+ pj_cstr(&auth_creds[i].realm, auths[i]->realm);
+ pj_cstr(&auth_creds[i].username, auths[i]->auth_user);
+ pj_cstr(&auth_creds[i].scheme, "digest");
+ switch (auths[i]->type) {
+ case AST_SIP_AUTH_TYPE_USER_PASS:
+ pj_cstr(&auth_creds[i].data, auths[i]->auth_pass);
+ auth_creds[i].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
+ break;
+ case AST_SIP_AUTH_TYPE_MD5:
+ pj_cstr(&auth_creds[i].data, auths[i]->md5_creds);
+ auth_creds[i].data_type = PJSIP_CRED_DATA_DIGEST;
+ break;
+ }
+ }
+
+ pjsip_auth_clt_set_credentials(auth_sess, num_auths, auth_creds);
+
+cleanup:
+ ast_sip_cleanup_auths(auths, num_auths);
+ return res;
+}
+
+static int digest_create_request_with_auth(const char **auths, size_t num_auths, pjsip_rx_data *challenge,
+ pjsip_transaction *tsx, pjsip_tx_data **new_request)
+{
+ pjsip_auth_clt_sess auth_sess;
+
+ if (pjsip_auth_clt_init(&auth_sess, ast_sip_get_pjsip_endpoint(),
+ tsx->pool, 0) != PJ_SUCCESS) {
+ ast_log(LOG_WARNING, "Failed to initialize client authentication session\n");
+ return -1;
+ }
+
+ if (set_outbound_authentication_credentials(&auth_sess, auths, num_auths)) {
+ ast_log(LOG_WARNING, "Failed to set authentication credentials\n");
+ return -1;
+ }
+
+ if (pjsip_auth_clt_reinit_req(&auth_sess, challenge,
+ tsx->last_tx, new_request) != PJ_SUCCESS) {
+ ast_log(LOG_WARNING, "Failed to create new request with authentication credentials\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct ast_sip_outbound_authenticator digest_authenticator = {
+ .create_request_with_auth = digest_create_request_with_auth,
+};
+
+static int load_module(void)
+{
+ if (ast_sip_register_outbound_authenticator(&digest_authenticator)) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ ast_sip_unregister_outbound_authenticator(&digest_authenticator);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP authentication resource",
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_CHANNEL_DEPEND,
+);
diff --git a/res/res_sip_outbound_registration.c b/res/res_sip_outbound_registration.c
new file mode 100644
index 000000000..8f1108df5
--- /dev/null
+++ b/res/res_sip_outbound_registration.c
@@ -0,0 +1,708 @@
+/*
+ * 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.
+ */
+
+/*** MODULEINFO
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/module.h"
+#include "asterisk/taskprocessor.h"
+
+/*! \brief Amount of buffer time (in seconds) before expiration that we re-register at */
+#define REREGISTER_BUFFER_TIME 10
+
+/*! \brief Various states that an outbound registration may be in */
+enum sip_outbound_registration_status {
+ /*! \brief Currently unregistered */
+ SIP_REGISTRATION_UNREGISTERED = 0,
+ /*! \brief Registered, yay! */
+ SIP_REGISTRATION_REGISTERED,
+ /*! \brief Registration was rejected, but response was temporal */
+ SIP_REGISTRATION_REJECTED_TEMPORARY,
+ /*! \brief Registration was rejected, permanently */
+ SIP_REGISTRATION_REJECTED_PERMANENT,
+ /*! \brief Registration has been stopped */
+ SIP_REGISTRATION_STOPPED,
+};
+
+/*! \brief Outbound registration client state information (persists for lifetime of regc) */
+struct sip_outbound_registration_client_state {
+ /*! \brief Current status of this registration */
+ enum sip_outbound_registration_status status;
+ /*! \brief Outbound registration client */
+ pjsip_regc *client;
+ /*! \brief Timer entry for retrying on temporal responses */
+ pj_timer_entry timer;
+ /*! \brief Current number of retries */
+ unsigned int retries;
+ /*! \brief Maximum number of retries permitted */
+ unsigned int max_retries;
+ /*! \brief Interval at which retries should occur for temporal responses */
+ unsigned int retry_interval;
+ /*! \brief Treat authentication challenges that we cannot handle as permanent failures */
+ unsigned int auth_rejection_permanent;
+ /*! \brief Serializer for stuff and things */
+ struct ast_taskprocessor *serializer;
+ /*! \brief Configured authentication credentials */
+ const char **sip_outbound_auths;
+ /*! \brief Number of configured auths */
+ size_t num_outbound_auths;
+ /*! \brief Registration should be destroyed after completion of transaction */
+ unsigned int destroy:1;
+};
+
+/*! \brief Outbound registration state information (persists for lifetime that registration should exist) */
+struct sip_outbound_registration_state {
+ /*! \brief Client state information */
+ struct sip_outbound_registration_client_state *client_state;
+};
+
+/*! \brief Outbound registration information */
+struct sip_outbound_registration {
+ /*! \brief Sorcery object details */
+ SORCERY_OBJECT(details);
+ /*! \brief Stringfields */
+ AST_DECLARE_STRING_FIELDS(
+ /*! \brief URI for the registrar */
+ AST_STRING_FIELD(server_uri);
+ /*! \brief URI for the AOR */
+ AST_STRING_FIELD(client_uri);
+ /*! \brief Optional user for contact header */
+ AST_STRING_FIELD(contact_user);
+ /*! \brief Explicit transport to use for registration */
+ AST_STRING_FIELD(transport);
+ /*! \brief Outbound proxy to use */
+ AST_STRING_FIELD(outbound_proxy);
+ );
+ /*! \brief Requested expiration time */
+ unsigned int expiration;
+ /*! \brief Interval at which retries should occur for temporal responses */
+ unsigned int retry_interval;
+ /*! \brief Treat authentication challenges that we cannot handle as permanent failures */
+ unsigned int auth_rejection_permanent;
+ /*! \brief Maximum number of retries permitted */
+ unsigned int max_retries;
+ /*! \brief Outbound registration state */
+ struct sip_outbound_registration_state *state;
+ /*! \brief Configured authentication credentials */
+ const char **sip_outbound_auths;
+ /*! \brief Number of configured auths */
+ size_t num_outbound_auths;
+};
+
+static void destroy_auths(const char **auths, size_t num_auths)
+{
+ int i;
+ for (i = 0; i < num_auths; ++i) {
+ ast_free((char *) auths[i]);
+ }
+ ast_free(auths);
+}
+
+/*! \brief Helper function which cancels the timer on a client */
+static void cancel_registration(struct sip_outbound_registration_client_state *client_state)
+{
+ if (pj_timer_heap_cancel(pjsip_endpt_get_timer_heap(ast_sip_get_pjsip_endpoint()), &client_state->timer)) {
+ /* The timer was successfully cancelled, drop the refcount of client_state */
+ ao2_ref(client_state, -1);
+ }
+}
+
+/*! \brief Callback function for registering */
+static int handle_client_registration(void *data)
+{
+ RAII_VAR(struct sip_outbound_registration_client_state *, client_state, data, ao2_cleanup);
+ pjsip_tx_data *tdata;
+
+ cancel_registration(client_state);
+
+ if ((client_state->status == SIP_REGISTRATION_STOPPED) ||
+ (pjsip_regc_register(client_state->client, PJ_FALSE, &tdata) != PJ_SUCCESS)) {
+ return 0;
+ }
+
+ /* Due to the registration the callback may now get called, so bump the ref count */
+ ao2_ref(client_state, +1);
+ if (pjsip_regc_send(client_state->client, tdata) != PJ_SUCCESS) {
+ ao2_ref(client_state, -1);
+ pjsip_tx_data_dec_ref(tdata);
+ }
+
+ return 0;
+}
+
+/*! \brief Timer callback function, used just for registrations */
+static void sip_outbound_registration_timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
+{
+ RAII_VAR(struct sip_outbound_registration_client_state *, client_state, entry->user_data, ao2_cleanup);
+
+ ao2_ref(client_state, +1);
+ if (ast_sip_push_task(client_state->serializer, handle_client_registration, client_state)) {
+ ast_log(LOG_WARNING, "Failed to pass outbound registration to threadpool\n");
+ ao2_ref(client_state, -1);
+ }
+
+ entry->id = 0;
+}
+
+/*! \brief Helper function which sets up the timer to re-register in a specific amount of time */
+static void schedule_registration(struct sip_outbound_registration_client_state *client_state, unsigned int seconds)
+{
+ pj_time_val delay = { .sec = seconds, };
+
+ cancel_registration(client_state);
+
+ ao2_ref(client_state, +1);
+ if (pjsip_endpt_schedule_timer(ast_sip_get_pjsip_endpoint(), &client_state->timer, &delay) != PJ_SUCCESS) {
+ ast_log(LOG_WARNING, "Failed to pass timed registration to scheduler\n");
+ ao2_ref(client_state, -1);
+ }
+}
+
+/*! \brief Callback function for unregistering (potentially) and destroying state */
+static int handle_client_state_destruction(void *data)
+{
+ RAII_VAR(struct sip_outbound_registration_client_state *, client_state, data, ao2_cleanup);
+ pjsip_regc_info info;
+
+ cancel_registration(client_state);
+
+ pjsip_regc_get_info(client_state->client, &info);
+
+ if (info.is_busy == PJ_TRUE) {
+ /* If a client transaction is in progress we defer until it is complete */
+ client_state->destroy = 1;
+ return 0;
+ }
+
+ if (client_state->status != SIP_REGISTRATION_UNREGISTERED && client_state->status != SIP_REGISTRATION_REJECTED_PERMANENT) {
+ pjsip_tx_data *tdata;
+
+ if (pjsip_regc_unregister(client_state->client, &tdata) == PJ_SUCCESS) {
+ pjsip_regc_send(client_state->client, tdata);
+ }
+ }
+
+ pjsip_regc_destroy(client_state->client);
+
+ client_state->status = SIP_REGISTRATION_STOPPED;
+ destroy_auths(client_state->sip_outbound_auths, client_state->num_outbound_auths);
+
+ return 0;
+}
+
+/*! \brief Structure for registration response */
+struct registration_response {
+ /*! \brief Response code for the registration attempt */
+ int code;
+ /*! \brief Expiration time for registration */
+ int expiration;
+ /*! \brief Retry-After value */
+ int retry_after;
+ /*! \brief Outbound registration client state */
+ struct sip_outbound_registration_client_state *client_state;
+ /*! \brief The response message */
+ pjsip_rx_data *rdata;
+ /*! \brief The response transaction */
+ pjsip_transaction *tsx;
+};
+
+/*! \brief Registration response structure destructor */
+static void registration_response_destroy(void *obj)
+{
+ struct registration_response *response = obj;
+
+ pjsip_rx_data_free_cloned(response->rdata);
+ ao2_cleanup(response->client_state);
+}
+
+/* \brief Helper funtion which determines if a response code is temporal or not */
+static int sip_outbound_registration_is_temporal(unsigned int code,
+ struct sip_outbound_registration_client_state *client_state)
+{
+ /* Shamelessly taken from pjsua */
+ if (code == PJSIP_SC_REQUEST_TIMEOUT ||
+ code == PJSIP_SC_INTERNAL_SERVER_ERROR ||
+ code == PJSIP_SC_BAD_GATEWAY ||
+ code == PJSIP_SC_SERVICE_UNAVAILABLE ||
+ code == PJSIP_SC_SERVER_TIMEOUT ||
+ ((code == PJSIP_SC_UNAUTHORIZED ||
+ code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED) &&
+ !client_state->auth_rejection_permanent) ||
+ PJSIP_IS_STATUS_IN_CLASS(code, 600)) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/*! \brief Callback function for handling a response to a registration attempt */
+static int handle_registration_response(void *data)
+{
+ RAII_VAR(struct registration_response *, response, data, ao2_cleanup);
+ pjsip_regc_info info;
+ char server_uri[PJSIP_MAX_URL_SIZE], client_uri[PJSIP_MAX_URL_SIZE];
+
+ if (response->client_state->status == SIP_REGISTRATION_STOPPED) {
+ return 0;
+ }
+
+ pjsip_regc_get_info(response->client_state->client, &info);
+ ast_copy_pj_str(server_uri, &info.server_uri, sizeof(server_uri));
+ ast_copy_pj_str(client_uri, &info.client_uri, sizeof(client_uri));
+
+ if (response->code == 401 || response->code == 407) {
+ pjsip_tx_data *tdata;
+ if (!ast_sip_create_request_with_auth(response->client_state->sip_outbound_auths, response->client_state->num_outbound_auths,
+ response->rdata, response->tsx, &tdata)) {
+ pjsip_regc_send(response->client_state->client, tdata);
+ return 0;
+ }
+ /* Otherwise, fall through so the failure is processed appropriately */
+ }
+
+ if (PJSIP_IS_STATUS_IN_CLASS(response->code, 200)) {
+ /* If the registration went fine simply reschedule registration for the future */
+ response->client_state->status = SIP_REGISTRATION_REGISTERED;
+ response->client_state->retries = 0;
+ schedule_registration(response->client_state, response->expiration - REREGISTER_BUFFER_TIME);
+ } else if (response->retry_after) {
+ /* If we have been instructed to retry after a period of time, schedule it as such */
+ response->client_state->status = SIP_REGISTRATION_REJECTED_TEMPORARY;
+ schedule_registration(response->client_state, response->retry_after);
+ ast_log(LOG_WARNING, "Temporal response '%d' received from '%s' on registration attempt to '%s', instructed to retry in '%d'\n",
+ response->code, server_uri, client_uri, response->retry_after);
+ } else if (response->client_state->retry_interval && sip_outbound_registration_is_temporal(response->code, response->client_state)) {
+ if (response->client_state->retries == response->client_state->max_retries) {
+ /* If we received enough temporal responses to exceed our maximum give up permanently */
+ response->client_state->status = SIP_REGISTRATION_REJECTED_PERMANENT;
+ ast_log(LOG_WARNING, "Maximum retries reached when attempting outbound registration to '%s' with client '%s', stopping registration attempt\n",
+ server_uri, client_uri);
+ } else {
+ /* On the other hand if we can still try some more do so */
+ response->client_state->status = SIP_REGISTRATION_REJECTED_TEMPORARY;
+ response->client_state->retries++;
+ schedule_registration(response->client_state, response->client_state->retry_interval);
+ ast_log(LOG_WARNING, "Temporal response '%d' received from '%s' on registration attempt to '%s', retrying in '%d' seconds\n",
+ response->code, server_uri, client_uri, response->client_state->retry_interval);
+ }
+ } else {
+ /* Finally if there's no hope of registering give up */
+ response->client_state->status = SIP_REGISTRATION_REJECTED_PERMANENT;
+ ast_log(LOG_WARNING, "Fatal response '%d' received from '%s' on registration attempt to '%s', stopping outbound registration\n",
+ response->code, server_uri, client_uri);
+ }
+
+ /* If deferred destruction is in use see if we need to destroy now */
+ if (response->client_state->destroy) {
+ handle_client_state_destruction(response->client_state);
+ }
+
+ return 0;
+}
+
+/*! \brief Callback function for outbound registration client */
+static void sip_outbound_registration_response_cb(struct pjsip_regc_cbparam *param)
+{
+ RAII_VAR(struct sip_outbound_registration_client_state *, client_state, param->token, ao2_cleanup);
+ struct registration_response *response = ao2_alloc(sizeof(*response), registration_response_destroy);
+ struct pjsip_retry_after_hdr *retry_after = pjsip_msg_find_hdr(param->rdata->msg_info.msg, PJSIP_H_RETRY_AFTER, NULL);
+
+ response->code = param->code;
+ response->expiration = param->expiration;
+ response->retry_after = retry_after ? retry_after->ivalue : 0;
+ response->client_state = client_state;
+ response->tsx = pjsip_rdata_get_tsx(param->rdata);
+ pjsip_rx_data_clone(param->rdata, 0, &response->rdata);
+ ao2_ref(response->client_state, +1);
+
+ if (ast_sip_push_task(client_state->serializer, handle_registration_response, response)) {
+ ast_log(LOG_WARNING, "Failed to pass incoming registration response to threadpool\n");
+ ao2_cleanup(response);
+ }
+}
+
+/*! \brief Destructor function for registration state */
+static void sip_outbound_registration_state_destroy(void *obj)
+{
+ struct sip_outbound_registration_state *state = obj;
+
+ if (!state->client_state) {
+ return;
+ }
+
+ if (state->client_state->serializer && ast_sip_push_task(state->client_state->serializer, handle_client_state_destruction, state->client_state)) {
+ ast_log(LOG_WARNING, "Failed to pass outbound registration client destruction to threadpool\n");
+ ao2_ref(state->client_state, -1);
+ }
+}
+
+/*! \brief Destructor function for client registration state */
+static void sip_outbound_registration_client_state_destroy(void *obj)
+{
+ struct sip_outbound_registration_client_state *client_state = obj;
+
+ ast_taskprocessor_unreference(client_state->serializer);
+}
+
+/*! \brief Allocator function for registration state */
+static struct sip_outbound_registration_state *sip_outbound_registration_state_alloc(void)
+{
+ struct sip_outbound_registration_state *state = ao2_alloc(sizeof(*state), sip_outbound_registration_state_destroy);
+
+ if (!state || !(state->client_state = ao2_alloc(sizeof(*state->client_state), sip_outbound_registration_client_state_destroy))) {
+ ao2_cleanup(state);
+ return NULL;
+ }
+
+ if ((pjsip_regc_create(ast_sip_get_pjsip_endpoint(), state->client_state, sip_outbound_registration_response_cb, &state->client_state->client) != PJ_SUCCESS) ||
+ !(state->client_state->serializer = ast_sip_create_serializer())) {
+ /* This is on purpose, normal operation will have it be deallocated within the serializer */
+ pjsip_regc_destroy(state->client_state->client);
+ ao2_cleanup(state->client_state);
+ ao2_cleanup(state);
+ return NULL;
+ }
+
+ state->client_state->status = SIP_REGISTRATION_UNREGISTERED;
+ state->client_state->timer.user_data = state->client_state;
+ state->client_state->timer.cb = sip_outbound_registration_timer_cb;
+
+ return state;
+}
+
+/*! \brief Destructor function for registration information */
+static void sip_outbound_registration_destroy(void *obj)
+{
+ struct sip_outbound_registration *registration = obj;
+
+ ao2_cleanup(registration->state);
+ destroy_auths(registration->sip_outbound_auths, registration->num_outbound_auths);
+
+ ast_string_field_free_memory(registration);
+}
+
+/*! \brief Allocator function for registration information */
+static void *sip_outbound_registration_alloc(const char *name)
+{
+ struct sip_outbound_registration *registration = ao2_alloc(sizeof(*registration), sip_outbound_registration_destroy);
+
+ if (!registration || ast_string_field_init(registration, 256)) {
+ ao2_cleanup(registration);
+ return NULL;
+ }
+
+ return registration;
+}
+
+/*! \brief Helper function which populates a pj_str_t with a contact header */
+static int sip_dialog_create_contact(pj_pool_t *pool, pj_str_t *contact, const char *user, const pj_str_t *target, pjsip_tpselector *selector)
+{
+ pj_str_t tmp, local_addr;
+ pjsip_uri *uri;
+ pjsip_sip_uri *sip_uri;
+ pjsip_transport_type_e type = PJSIP_TRANSPORT_UNSPECIFIED;
+ int local_port;
+
+ pj_strdup_with_null(pool, &tmp, target);
+
+ if (!(uri = pjsip_parse_uri(pool, tmp.ptr, tmp.slen, 0)) ||
+ (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri))) {
+ return -1;
+ }
+
+ sip_uri = pjsip_uri_get_uri(uri);
+
+ if (PJSIP_URI_SCHEME_IS_SIPS(sip_uri)) {
+ type = PJSIP_TRANSPORT_TLS;
+ } else if (!sip_uri->transport_param.slen) {
+ type = PJSIP_TRANSPORT_UDP;
+ } else {
+ type = pjsip_transport_get_type_from_name(&sip_uri->transport_param);
+ }
+
+ if (type == PJSIP_TRANSPORT_UNSPECIFIED) {
+ return -1;
+ }
+
+ if (pj_strchr(&sip_uri->host, ':')) {
+ type = (pjsip_transport_type_e)(((int)type) + PJSIP_TRANSPORT_IPV6);
+ }
+
+ if (pjsip_tpmgr_find_local_addr(pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()), pool, type, selector,
+ &local_addr, &local_port) != PJ_SUCCESS) {
+ return -1;
+ }
+
+ if (!pj_strchr(&sip_uri->host, ':') && pj_strchr(&local_addr, ':')) {
+ type = (pjsip_transport_type_e)(((int)type) + PJSIP_TRANSPORT_IPV6);
+ }
+
+ contact->ptr = pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
+ contact->slen = pj_ansi_snprintf(contact->ptr, PJSIP_MAX_URL_SIZE,
+ "<%s:%s@%s%.*s%s:%d%s%s>",
+ (pjsip_transport_get_flag_from_type(type) & PJSIP_TRANSPORT_SECURE) ? "sips" : "sip",
+ user,
+ (type & PJSIP_TRANSPORT_IPV6) ? "[" : "",
+ (int)local_addr.slen,
+ local_addr.ptr,
+ (type & PJSIP_TRANSPORT_IPV6) ? "]" : "",
+ local_port,
+ (type != PJSIP_TRANSPORT_UDP && type != PJSIP_TRANSPORT_UDP6) ? ";transport=" : "",
+ (type != PJSIP_TRANSPORT_UDP && type != PJSIP_TRANSPORT_UDP6) ? pjsip_transport_get_type_name(type) : "");
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Check if a registration can be reused
+ *
+ * This checks if the existing outbound registration's configuration differs from a newly-applied
+ * outbound registration to see if the applied one.
+ *
+ * \param existing The pre-existing outbound registration
+ * \param applied The newly-created registration
+ */
+static int can_reuse_registration(struct sip_outbound_registration *existing, struct sip_outbound_registration *applied)
+{
+ int i;
+
+ if (strcmp(existing->server_uri, applied->server_uri) || strcmp(existing->client_uri, applied->client_uri) ||
+ strcmp(existing->transport, applied->transport) || strcmp(existing->contact_user, applied->contact_user) ||
+ strcmp(existing->outbound_proxy, applied->outbound_proxy) || existing->num_outbound_auths != applied->num_outbound_auths ||
+ existing->auth_rejection_permanent != applied->auth_rejection_permanent) {
+ return 0;
+ }
+
+ for (i = 0; i < existing->num_outbound_auths; ++i) {
+ if (strcmp(existing->sip_outbound_auths[i], applied->sip_outbound_auths[i])) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/*! \brief Apply function which finds or allocates a state structure */
+static int sip_outbound_registration_apply(const struct ast_sorcery *sorcery, void *obj)
+{
+ RAII_VAR(struct sip_outbound_registration *, existing, ast_sorcery_retrieve_by_id(sorcery, "registration", ast_sorcery_object_get_id(obj)), ao2_cleanup);
+ struct sip_outbound_registration *applied = obj;
+ pj_str_t server_uri, client_uri, contact_uri;
+ pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
+
+ if (!existing) {
+ /* If no existing registration exists we can just start fresh easily */
+ applied->state = sip_outbound_registration_state_alloc();
+ } else {
+ /* If there is an existing registration things are more complicated, we can immediately reuse this state if most stuff remains unchanged */
+ if (can_reuse_registration(existing, applied)) {
+ applied->state = existing->state;
+ ao2_ref(applied->state, +1);
+ return 0;
+ }
+ applied->state = sip_outbound_registration_state_alloc();
+ }
+
+ if (!applied->state) {
+ return -1;
+ }
+
+ if (!ast_strlen_zero(applied->transport)) {
+ RAII_VAR(struct ast_sip_transport *, transport, ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", applied->transport), ao2_cleanup);
+
+ if (!transport || !transport->state) {
+ return -1;
+ }
+
+ if (transport->type == AST_SIP_TRANSPORT_UDP) {
+ selector.type = PJSIP_TPSELECTOR_TRANSPORT;
+ selector.u.transport = transport->state->transport;
+ } else if (transport->type == AST_SIP_TRANSPORT_TCP || transport->type == AST_SIP_TRANSPORT_TLS) {
+ selector.type = PJSIP_TPSELECTOR_LISTENER;
+ selector.u.listener = transport->state->factory;
+ } else {
+ return -1;
+ }
+ }
+
+ pjsip_regc_set_transport(applied->state->client_state->client, &selector);
+
+ if (!ast_strlen_zero(applied->outbound_proxy)) {
+ pjsip_route_hdr route_set, *route;
+ static const pj_str_t ROUTE_HNAME = { "Route", 5 };
+ pj_str_t tmp;
+
+ pj_list_init(&route_set);
+
+ pj_strdup2_with_null(pjsip_regc_get_pool(applied->state->client_state->client), &tmp, applied->outbound_proxy);
+ if (!(route = pjsip_parse_hdr(pjsip_regc_get_pool(applied->state->client_state->client), &ROUTE_HNAME, tmp.ptr, tmp.slen, NULL))) {
+ return -1;
+ }
+ pj_list_push_back(&route_set, route);
+
+ pjsip_regc_set_route_set(applied->state->client_state->client, &route_set);
+ }
+
+ pj_cstr(&server_uri, applied->server_uri);
+
+ if (sip_dialog_create_contact(pjsip_regc_get_pool(applied->state->client_state->client), &contact_uri, S_OR(applied->contact_user, "s"), &server_uri, &selector)) {
+ return -1;
+ }
+
+ pj_cstr(&client_uri, applied->client_uri);
+
+ if (pjsip_regc_init(applied->state->client_state->client, &server_uri, &client_uri, &client_uri, 1, &contact_uri, applied->expiration) != PJ_SUCCESS) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*! \brief Helper function which performs a single registration */
+static int sip_outbound_registration_perform(void *obj, void *arg, int flags)
+{
+ struct sip_outbound_registration *registration = obj;
+ size_t i;
+
+ /* Just in case the client state is being reused for this registration, free the auth information */
+ destroy_auths(registration->state->client_state->sip_outbound_auths,
+ registration->state->client_state->num_outbound_auths);
+ registration->state->client_state->num_outbound_auths = 0;
+
+ registration->state->client_state->sip_outbound_auths = ast_calloc(registration->num_outbound_auths, sizeof(char *));
+ for (i = 0; i < registration->num_outbound_auths; ++i) {
+ registration->state->client_state->sip_outbound_auths[i] = ast_strdup(registration->sip_outbound_auths[i]);
+ }
+ registration->state->client_state->num_outbound_auths = registration->num_outbound_auths;
+ registration->state->client_state->retry_interval = registration->retry_interval;
+ registration->state->client_state->max_retries = registration->max_retries;
+ registration->state->client_state->retries = 0;
+
+ pjsip_regc_update_expires(registration->state->client_state->client, registration->expiration);
+
+ schedule_registration(registration->state->client_state, (ast_random() % 10) + 1);
+
+ return 0;
+}
+
+/*! \brief Helper function which performs all registrations */
+static void sip_outbound_registration_perform_all(void)
+{
+ RAII_VAR(struct ao2_container *, registrations, ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "registration", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL), ao2_cleanup);
+
+ if (!registrations) {
+ return;
+ }
+
+ ao2_callback(registrations, OBJ_NODATA, sip_outbound_registration_perform, NULL);
+}
+
+#define AUTH_INCREMENT 4
+
+static const char **auth_alloc(const char *value, size_t *num_auths)
+{
+ char *auths = ast_strdupa(value);
+ char *val;
+ int num_alloced = 0;
+ const char **alloced_auths = NULL;
+
+ while ((val = strsep(&auths, ","))) {
+ if (*num_auths >= num_alloced) {
+ size_t size;
+ num_alloced += AUTH_INCREMENT;
+ size = num_alloced * sizeof(char *);
+ alloced_auths = ast_realloc(alloced_auths, size);
+ if (!alloced_auths) {
+ goto failure;
+ }
+ }
+ alloced_auths[*num_auths] = ast_strdup(val);
+ if (!alloced_auths[*num_auths]) {
+ goto failure;
+ }
+ ++(*num_auths);
+ }
+ return alloced_auths;
+
+failure:
+ destroy_auths(alloced_auths, *num_auths);
+ return NULL;
+}
+
+static int outbound_auth_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct sip_outbound_registration *registration = obj;
+
+ registration->sip_outbound_auths = auth_alloc(var->value, &registration->num_outbound_auths);
+ if (!registration->sip_outbound_auths) {
+ return -1;
+ }
+ return 0;
+}
+
+static int load_module(void)
+{
+ ast_sorcery_apply_default(ast_sip_get_sorcery(), "registration", "config", "res_sip.conf,criteria=type=registration");
+
+ if (ast_sorcery_object_register(ast_sip_get_sorcery(), "registration", sip_outbound_registration_alloc, NULL, sip_outbound_registration_apply)) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "type", "", OPT_NOOP_T, 0, 0);
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "server_uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, server_uri));
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "client_uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, client_uri));
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "contact_user", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, contact_user));
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "transport", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, transport));
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, outbound_proxy));
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "expiration", "3600", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, expiration));
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "retry_interval", "60", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, retry_interval));
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "max_retries", "10", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, max_retries));
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "auth_rejection_permanent", "yes", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, auth_rejection_permanent));
+ ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "registration", "outbound_auth", "", outbound_auth_handler, NULL, 0, 0);
+ ast_sorcery_reload_object(ast_sip_get_sorcery(), "registration");
+ sip_outbound_registration_perform_all();
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int reload_module(void)
+{
+ ast_sorcery_reload_object(ast_sip_get_sorcery(), "registration");
+ sip_outbound_registration_perform_all();
+ return 0;
+}
+
+static int unload_module(void)
+{
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Outbound Registration Support",
+ .load = load_module,
+ .reload = reload_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_APP_DEPEND,
+ );
diff --git a/res/res_sip_pubsub.c b/res/res_sip_pubsub.c
new file mode 100644
index 000000000..2983d563e
--- /dev/null
+++ b/res/res_sip_pubsub.c
@@ -0,0 +1,662 @@
+/*
+ * 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.
+ */
+/*!
+ * \brief Opaque structure representing an RFC 3265 SIP subscription
+ */
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_simple.h>
+#include <pjlib.h>
+
+#include "asterisk/res_sip_pubsub.h"
+#include "asterisk/module.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/datastore.h"
+#include "asterisk/uuid.h"
+#include "asterisk/taskprocessor.h"
+#include "asterisk/res_sip.h"
+
+static pj_bool_t sub_on_rx_request(pjsip_rx_data *rdata);
+
+static struct pjsip_module sub_module = {
+ .name = { "PubSub Module", 13 },
+ .priority = PJSIP_MOD_PRIORITY_APPLICATION,
+ .on_rx_request = sub_on_rx_request,
+};
+
+/*!
+ * \brief Structure representing a SIP subscription
+ */
+struct ast_sip_subscription {
+ /*! Subscription datastores set up by handlers */
+ struct ao2_container *datastores;
+ /*! The endpoint with which the subscription is communicating */
+ struct ast_sip_endpoint *endpoint;
+ /*! Serializer on which to place operations for this subscription */
+ struct ast_taskprocessor *serializer;
+ /*! The handler for this subscription */
+ const struct ast_sip_subscription_handler *handler;
+ /*! The role for this subscription */
+ enum ast_sip_subscription_role role;
+ /*! The underlying PJSIP event subscription structure */
+ pjsip_evsub *evsub;
+ /*! The underlying PJSIP dialog */
+ pjsip_dialog *dlg;
+};
+
+#define DATASTORE_BUCKETS 53
+
+#define DEFAULT_EXPIRES 3600
+
+static int datastore_hash(const void *obj, int flags)
+{
+ const struct ast_datastore *datastore = obj;
+ const char *uid = flags & OBJ_KEY ? obj : datastore->uid;
+
+ ast_assert(uid != NULL);
+
+ return ast_str_hash(uid);
+}
+
+static int datastore_cmp(void *obj, void *arg, int flags)
+{
+ const struct ast_datastore *datastore1 = obj;
+ const struct ast_datastore *datastore2 = arg;
+ const char *uid2 = flags & OBJ_KEY ? arg : datastore2->uid;
+
+ ast_assert(datastore1->uid != NULL);
+ ast_assert(uid2 != NULL);
+
+ return strcmp(datastore1->uid, uid2) ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+static void subscription_destructor(void *obj)
+{
+ struct ast_sip_subscription *sub = obj;
+
+ ast_debug(3, "Destroying SIP subscription\n");
+
+ ao2_cleanup(sub->datastores);
+ ao2_cleanup(sub->endpoint);
+
+ if (sub->dlg) {
+ /* This is why we keep the dialog on the subscription. When the subscription
+ * is destroyed, there is no guarantee that the underlying dialog is ready
+ * to be destroyed. Furthermore, there's no guarantee in the opposite direction
+ * either. The dialog could be destroyed before our subscription is. We fix
+ * this problem by keeping a reference to the dialog until it is time to
+ * destroy the subscription. We need to have the dialog available when the
+ * subscription is destroyed so that we can guarantee that our attempt to
+ * remove the serializer will be successful.
+ */
+ ast_sip_dialog_set_serializer(sub->dlg, NULL);
+ pjsip_dlg_dec_session(sub->dlg, &sub_module);
+ }
+ ast_taskprocessor_unreference(sub->serializer);
+}
+
+static void pubsub_on_evsub_state(pjsip_evsub *sub, pjsip_event *event);
+static void pubsub_on_tsx_state(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event);
+static void pubsub_on_rx_refresh(pjsip_evsub *sub, pjsip_rx_data *rdata,
+ int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body);
+static void pubsub_on_rx_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code,
+ pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body);
+static void pubsub_on_client_refresh(pjsip_evsub *sub);
+static void pubsub_on_server_timeout(pjsip_evsub *sub);
+
+
+static pjsip_evsub_user pubsub_cb = {
+ .on_evsub_state = pubsub_on_evsub_state,
+ .on_tsx_state = pubsub_on_tsx_state,
+ .on_rx_refresh = pubsub_on_rx_refresh,
+ .on_rx_notify = pubsub_on_rx_notify,
+ .on_client_refresh = pubsub_on_client_refresh,
+ .on_server_timeout = pubsub_on_server_timeout,
+};
+
+static pjsip_evsub *allocate_evsub(const char *event, enum ast_sip_subscription_role role,
+ struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, pjsip_dialog *dlg)
+{
+ pjsip_evsub *evsub;
+ /* PJSIP is kind enough to have some built-in support for certain
+ * events. We need to use the correct initialization function for the
+ * built-in events
+ */
+ if (role == AST_SIP_NOTIFIER) {
+ if (!strcmp(event, "message-summary")) {
+ pjsip_mwi_create_uas(dlg, &pubsub_cb, rdata, &evsub);
+ } else if (!strcmp(event, "presence")) {
+ pjsip_pres_create_uas(dlg, &pubsub_cb, rdata, &evsub);
+ } else {
+ pjsip_evsub_create_uas(dlg, &pubsub_cb, rdata, 0, &evsub);
+ }
+ } else {
+ if (!strcmp(event, "message-summary")) {
+ pjsip_mwi_create_uac(dlg, &pubsub_cb, 0, &evsub);
+ } else if (!strcmp(event, "presence")) {
+ pjsip_pres_create_uac(dlg, &pubsub_cb, 0, &evsub);
+ } else {
+ pj_str_t pj_event;
+ pj_cstr(&pj_event, event);
+ pjsip_evsub_create_uac(dlg, &pubsub_cb, &pj_event, 0, &evsub);
+ }
+ }
+ return evsub;
+}
+
+struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_subscription_handler *handler,
+ enum ast_sip_subscription_role role, struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
+{
+ struct ast_sip_subscription *sub = ao2_alloc(sizeof(*sub), subscription_destructor);
+ pjsip_dialog *dlg;
+
+ if (!sub) {
+ return NULL;
+ }
+ sub->datastores = ao2_container_alloc(DATASTORE_BUCKETS, datastore_hash, datastore_cmp);
+ if (!sub->datastores) {
+ ao2_ref(sub, -1);
+ return NULL;
+ }
+ sub->serializer = ast_sip_create_serializer();
+ if (!sub->serializer) {
+ ao2_ref(sub, -1);
+ return NULL;
+ }
+ sub->role = role;
+ if (role == AST_SIP_NOTIFIER) {
+ pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, NULL, &dlg);
+ } else {
+ RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
+
+ contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
+ if (!contact || ast_strlen_zero(contact->uri)) {
+ ast_log(LOG_WARNING, "No contacts configured for endpoint %s. Unable to create SIP subsription\n",
+ ast_sorcery_object_get_id(endpoint));
+ ao2_ref(sub, -1);
+ return NULL;
+ }
+ dlg = ast_sip_create_dialog(endpoint, contact->uri, NULL);
+ }
+ if (!dlg) {
+ ast_log(LOG_WARNING, "Unable to create dialog for SIP subscription\n");
+ ao2_ref(sub, -1);
+ return NULL;
+ }
+ sub->evsub = allocate_evsub(handler->event_name, role, endpoint, rdata, dlg);
+ /* We keep a reference to the dialog until our subscription is destroyed. See
+ * the subscription_destructor for more details
+ */
+ pjsip_dlg_inc_session(dlg, &sub_module);
+ sub->dlg = dlg;
+ ast_sip_dialog_set_serializer(dlg, sub->serializer);
+ pjsip_evsub_set_mod_data(sub->evsub, sub_module.id, sub);
+ ao2_ref(endpoint, +1);
+ sub->endpoint = endpoint;
+ sub->handler = handler;
+ return sub;
+}
+
+struct ast_sip_endpoint *ast_sip_subscription_get_endpoint(struct ast_sip_subscription *sub)
+{
+ ast_assert(sub->endpoint != NULL);
+ ao2_ref(sub->endpoint, +1);
+ return sub->endpoint;
+}
+
+struct ast_taskprocessor *ast_sip_subscription_get_serializer(struct ast_sip_subscription *sub)
+{
+ ast_assert(sub->serializer != NULL);
+ return sub->serializer;
+}
+
+pjsip_evsub *ast_sip_subscription_get_evsub(struct ast_sip_subscription *sub)
+{
+ return sub->evsub;
+}
+
+int ast_sip_subscription_send_request(struct ast_sip_subscription *sub, pjsip_tx_data *tdata)
+{
+ return pjsip_evsub_send_request(ast_sip_subscription_get_evsub(sub),
+ tdata) == PJ_SUCCESS ? 0 : -1;
+}
+
+static void subscription_datastore_destroy(void *obj)
+{
+ struct ast_datastore *datastore = obj;
+
+ /* Using the destroy function (if present) destroy the data */
+ if (datastore->info->destroy != NULL && datastore->data != NULL) {
+ datastore->info->destroy(datastore->data);
+ datastore->data = NULL;
+ }
+
+ ast_free((void *) datastore->uid);
+ datastore->uid = NULL;
+}
+
+struct ast_datastore *ast_sip_subscription_alloc_datastore(const struct ast_datastore_info *info, const char *uid)
+{
+ RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
+ const char *uid_ptr = uid;
+
+ if (!info) {
+ return NULL;
+ }
+
+ datastore = ao2_alloc(sizeof(*datastore), subscription_datastore_destroy);
+ if (!datastore) {
+ return NULL;
+ }
+
+ datastore->info = info;
+ if (ast_strlen_zero(uid)) {
+ /* They didn't provide an ID so we'll provide one ourself */
+ struct ast_uuid *uuid = ast_uuid_generate();
+ char uuid_buf[AST_UUID_STR_LEN];
+ if (!uuid) {
+ return NULL;
+ }
+ uid_ptr = ast_uuid_to_str(uuid, uuid_buf, sizeof(uuid_buf));
+ ast_free(uuid);
+ }
+
+ datastore->uid = ast_strdup(uid_ptr);
+ if (!datastore->uid) {
+ return NULL;
+ }
+
+ ao2_ref(datastore, +1);
+ return datastore;
+}
+
+int ast_sip_subscription_add_datastore(struct ast_sip_subscription *subscription, struct ast_datastore *datastore)
+{
+ ast_assert(datastore != NULL);
+ ast_assert(datastore->info != NULL);
+ ast_assert(ast_strlen_zero(datastore->uid) == 0);
+
+ if (!ao2_link(subscription->datastores, datastore)) {
+ return -1;
+ }
+ return 0;
+}
+
+struct ast_datastore *ast_sip_subscription_get_datastore(struct ast_sip_subscription *subscription, const char *name)
+{
+ return ao2_find(subscription->datastores, name, OBJ_KEY);
+}
+
+void ast_sip_subscription_remove_datastore(struct ast_sip_subscription *subscription, const char *name)
+{
+ ao2_callback(subscription->datastores, OBJ_KEY | OBJ_UNLINK | OBJ_NODATA, NULL, (void *) name);
+}
+
+AST_RWLIST_HEAD_STATIC(subscription_handlers, ast_sip_subscription_handler);
+
+static void add_handler(struct ast_sip_subscription_handler *handler)
+{
+ SCOPED_LOCK(lock, &subscription_handlers, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+ AST_RWLIST_INSERT_TAIL(&subscription_handlers, handler, next);
+ ast_module_ref(ast_module_info->self);
+}
+
+static int handler_exists_for_event_name(const char *event_name)
+{
+ struct ast_sip_subscription_handler *iter;
+ SCOPED_LOCK(lock, &subscription_handlers, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
+
+ AST_RWLIST_TRAVERSE(&subscription_handlers, iter, next) {
+ if (!strcmp(iter->event_name, event_name)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int ast_sip_register_subscription_handler(struct ast_sip_subscription_handler *handler)
+{
+ pj_str_t event;
+ pj_str_t accept[AST_SIP_MAX_ACCEPT];
+ int i;
+
+ if (ast_strlen_zero(handler->event_name)) {
+ ast_log(LOG_ERROR, "No event package specifief for subscription handler. Cannot register\n");
+ return -1;
+ }
+
+ if (ast_strlen_zero(handler->accept[0])) {
+ ast_log(LOG_ERROR, "Subscription handler must supply at least one 'Accept' format\n");
+ return -1;
+ }
+
+ if (handler_exists_for_event_name(handler->event_name)) {
+ ast_log(LOG_ERROR, "A subscription handler for event %s already exists. Not registering "
+ "new subscription handler\n", handler->event_name);
+ return -1;
+ }
+
+ pj_cstr(&event, handler->event_name);
+ for (i = 0; i < AST_SIP_MAX_ACCEPT && !ast_strlen_zero(handler->accept[i]); ++i) {
+ pj_cstr(&accept[i], handler->accept[i]);
+ }
+
+ if (!strcmp(handler->event_name, "message-summary")) {
+ pjsip_mwi_init_module(ast_sip_get_pjsip_endpoint(), pjsip_evsub_instance());
+ } else if (!strcmp(handler->event_name, "presence")) {
+ pjsip_pres_init_module(ast_sip_get_pjsip_endpoint(), pjsip_evsub_instance());
+ } else {
+ pjsip_evsub_register_pkg(&sub_module, &event, DEFAULT_EXPIRES, i, accept);
+ }
+
+ add_handler(handler);
+ return 0;
+}
+
+void ast_sip_unregister_subscription_handler(struct ast_sip_subscription_handler *handler)
+{
+ struct ast_sip_subscription_handler *iter;
+ SCOPED_LOCK(lock, &subscription_handlers, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&subscription_handlers, iter, next) {
+ if (handler == iter) {
+ AST_RWLIST_REMOVE_CURRENT(next);
+ ast_module_unref(ast_module_info->self);
+ break;
+ }
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+}
+
+static struct ast_sip_subscription_handler *find_handler(const char *event, char accept[AST_SIP_MAX_ACCEPT][64], size_t num_accept)
+{
+ struct ast_sip_subscription_handler *iter;
+ int match = 0;
+ SCOPED_LOCK(lock, &subscription_handlers, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
+ AST_RWLIST_TRAVERSE(&subscription_handlers, iter, next) {
+ int i;
+ int j;
+ if (strcmp(event, iter->event_name)) {
+ ast_debug(3, "Event %s does not match %s\n", event, iter->event_name);
+ continue;
+ }
+ ast_debug(3, "Event name match: %s = %s\n", event, iter->event_name);
+ for (i = 0; i < num_accept; ++i) {
+ for (j = 0; j < num_accept; ++j) {
+ if (ast_strlen_zero(iter->accept[i])) {
+ ast_debug(3, "Breaking because subscription handler has run out of 'accept' types\n");
+ break;
+ }
+ if (!strcmp(accept[j], iter->accept[i])) {
+ ast_debug(3, "Accept headers match: %s = %s\n", accept[j], iter->accept[i]);
+ match = 1;
+ break;
+ }
+ ast_debug(3, "Accept %s does not match %s\n", accept[j], iter->accept[i]);
+ }
+ if (match) {
+ break;
+ }
+ }
+ if (match) {
+ break;
+ }
+ }
+
+ return iter;
+}
+
+static pj_bool_t sub_on_rx_request(pjsip_rx_data *rdata)
+{
+ static const pj_str_t event_name = { "Event", 5 };
+ char event[32];
+ char accept[AST_SIP_MAX_ACCEPT][64];
+ pjsip_accept_hdr *accept_header;
+ pjsip_event_hdr *event_header;
+ struct ast_sip_subscription_handler *handler;
+ RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
+ struct ast_sip_subscription *sub;
+ int i;
+
+ if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_subscribe_method())) {
+ return PJ_FALSE;
+ }
+
+ endpoint = ast_pjsip_rdata_get_endpoint(rdata);
+ ast_assert(endpoint != NULL);
+
+ event_header = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &event_name, rdata->msg_info.msg->hdr.next);
+ if (!event_header) {
+ ast_log(LOG_WARNING, "Incoming SUBSCRIBE request with no Event header\n");
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 489, NULL, NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ accept_header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, rdata->msg_info.msg->hdr.next);
+ if (!accept_header) {
+ ast_log(LOG_WARNING, "Incoming SUBSCRIBE request with no Accept header\n");
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 400, NULL, NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ ast_copy_pj_str(event, &event_header->event_type, sizeof(event));
+ for (i = 0; i < accept_header->count; ++i) {
+ ast_copy_pj_str(accept[i], &accept_header->values[i], sizeof(accept[i]));
+ }
+
+ handler = find_handler(event, accept, accept_header->count);
+ if (!handler) {
+ ast_log(LOG_WARNING, "No registered handler for event %s\n", event);
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 489, NULL, NULL, NULL);
+ return PJ_TRUE;
+ }
+ sub = handler->new_subscribe(endpoint, rdata);
+ if (!sub) {
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
+ }
+ return PJ_TRUE;
+}
+
+static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event)
+{
+ struct ast_sip_subscription *sub;
+ if (pjsip_evsub_get_state(evsub) != PJSIP_EVSUB_STATE_TERMINATED) {
+ return;
+ }
+
+ sub = pjsip_evsub_get_mod_data(evsub, sub_module.id);
+ if (!sub) {
+ return;
+ }
+
+ if (event->type == PJSIP_EVENT_RX_MSG) {
+ sub->handler->subscription_terminated(sub, event->body.rx_msg.rdata);
+ }
+
+ if (event->type == PJSIP_EVENT_TSX_STATE &&
+ event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+ sub->handler->subscription_terminated(sub, event->body.tsx_state.src.rdata);
+ }
+
+ if (sub->handler->subscription_shutdown) {
+ sub->handler->subscription_shutdown(sub);
+ }
+ pjsip_evsub_set_mod_data(evsub, sub_module.id, NULL);
+}
+
+static void pubsub_on_tsx_state(pjsip_evsub *evsub, pjsip_transaction *tsx, pjsip_event *event)
+{
+ struct ast_sip_subscription *sub = pjsip_evsub_get_mod_data(evsub, sub_module.id);
+
+ if (!sub) {
+ return;
+ }
+
+ if (tsx->role == PJSIP_ROLE_UAC && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+ sub->handler->notify_response(sub, event->body.tsx_state.src.rdata);
+ }
+}
+
+static void set_parameters_from_response_data(pj_pool_t *pool, int *p_st_code,
+ pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body,
+ struct ast_sip_subscription_response_data *response_data)
+{
+ ast_assert(response_data->status_code >= 200 && response_data->status_code <= 699);
+ *p_st_code = response_data->status_code;
+
+ if (!ast_strlen_zero(response_data->status_text)) {
+ pj_strdup2(pool, *p_st_text, response_data->status_text);
+ }
+
+ if (response_data->headers) {
+ struct ast_variable *iter;
+ for (iter = response_data->headers; iter; iter = iter->next) {
+ pj_str_t header_name;
+ pj_str_t header_value;
+ pjsip_generic_string_hdr *hdr;
+
+ pj_cstr(&header_name, iter->name);
+ pj_cstr(&header_value, iter->value);
+ hdr = pjsip_generic_string_hdr_create(pool, &header_name, &header_value);
+ pj_list_insert_before(res_hdr, hdr);
+ }
+ }
+
+ if (response_data->body) {
+ pj_str_t type;
+ pj_str_t subtype;
+ pj_str_t body_text;
+
+ pj_cstr(&type, response_data->body->type);
+ pj_cstr(&subtype, response_data->body->subtype);
+ pj_cstr(&body_text, response_data->body->body_text);
+
+ *p_body = pjsip_msg_body_create(pool, &type, &subtype, &body_text);
+ }
+}
+
+static int response_data_changed(struct ast_sip_subscription_response_data *response_data)
+{
+ if (response_data->status_code != 200 ||
+ !ast_strlen_zero(response_data->status_text) ||
+ response_data->headers ||
+ response_data->body) {
+ return 1;
+ }
+ return 0;
+}
+
+static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata,
+ int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body)
+{
+ struct ast_sip_subscription *sub = pjsip_evsub_get_mod_data(evsub, sub_module.id);
+ struct ast_sip_subscription_response_data response_data = {
+ .status_code = 200,
+ };
+
+ if (!sub) {
+ return;
+ }
+
+ sub->handler->resubscribe(sub, rdata, &response_data);
+
+ if (!response_data_changed(&response_data)) {
+ return;
+ }
+
+ set_parameters_from_response_data(rdata->tp_info.pool, p_st_code, p_st_text,
+ res_hdr, p_body, &response_data);
+}
+
+static void pubsub_on_rx_notify(pjsip_evsub *evsub, pjsip_rx_data *rdata, int *p_st_code,
+ pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body)
+{
+ struct ast_sip_subscription *sub = pjsip_evsub_get_mod_data(evsub, sub_module.id);
+ struct ast_sip_subscription_response_data response_data = {
+ .status_code = 200,
+ };
+
+ if (!sub|| !sub->handler->notify_request) {
+ return;
+ }
+
+ sub->handler->notify_request(sub, rdata, &response_data);
+
+ if (!response_data_changed(&response_data)) {
+ return;
+ }
+
+ set_parameters_from_response_data(rdata->tp_info.pool, p_st_code, p_st_text,
+ res_hdr, p_body, &response_data);
+}
+
+static int serialized_pubsub_on_client_refresh(void *userdata)
+{
+ struct ast_sip_subscription *sub = userdata;
+
+ sub->handler->refresh_subscription(sub);
+ ao2_cleanup(sub);
+ return 0;
+}
+
+static void pubsub_on_client_refresh(pjsip_evsub *evsub)
+{
+ struct ast_sip_subscription *sub = pjsip_evsub_get_mod_data(evsub, sub_module.id);
+
+ ao2_ref(sub, +1);
+ ast_sip_push_task(sub->serializer, serialized_pubsub_on_client_refresh, sub);
+}
+
+static int serialized_pubsub_on_server_timeout(void *userdata)
+{
+ struct ast_sip_subscription *sub = userdata;
+
+ sub->handler->subscription_timeout(sub);
+ ao2_cleanup(sub);
+ return 0;
+}
+
+static void pubsub_on_server_timeout(pjsip_evsub *evsub)
+{
+ struct ast_sip_subscription *sub = pjsip_evsub_get_mod_data(evsub, sub_module.id);
+
+ ao2_ref(sub, +1);
+ ast_sip_push_task(sub->serializer, serialized_pubsub_on_server_timeout, sub);
+}
+
+static int load_module(void)
+{
+ pjsip_evsub_init_module(ast_sip_get_pjsip_endpoint());
+ if (ast_sip_register_service(&sub_module)) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "SIP event resource",
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_CHANNEL_DEPEND,
+);
diff --git a/res/res_sip_pubsub.exports.in b/res/res_sip_pubsub.exports.in
new file mode 100644
index 000000000..55308746a
--- /dev/null
+++ b/res/res_sip_pubsub.exports.in
@@ -0,0 +1,16 @@
+{
+ global:
+ LINKER_SYMBOL_PREFIXast_sip_create_subscription;
+ LINKER_SYMBOL_PREFIXast_sip_subsription_get_endpoint;
+ LINKER_SYMBOL_PREFIXast_sip_subscription_get_serializer;
+ LINKER_SYMBOL_PREFIXast_sip_subscription_get_evsub;
+ LINKER_SYMBOL_PREFIXast_sip_subscription_send_request;
+ LINKER_SYMBOL_PREFIXast_sip_subscription_alloc_datastore;
+ LINKER_SYMBOL_PREFIXast_sip_subscription_add_datastore;
+ LINKER_SYMBOL_PREFIXast_sip_subscription_get_datastore;
+ LINKER_SYMBOL_PREFIXast_sip_subscription_remove_datastore;
+ LINKER_SYMBOL_PREFIXast_sip_register_subscription_handler;
+ LINKER_SYMBOL_PREFIXast_sip_unregister_subscription_handler;
+ local:
+ *;
+};
diff --git a/res/res_sip_registrar.c b/res/res_sip_registrar.c
new file mode 100644
index 000000000..e5a2e888b
--- /dev/null
+++ b/res/res_sip_registrar.c
@@ -0,0 +1,382 @@
+/*
+ * 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.
+ */
+
+/*** MODULEINFO
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/module.h"
+
+/*! \brief Internal function which returns the expiration time for a contact */
+static int registrar_get_expiration(const struct ast_sip_aor *aor, const pjsip_contact_hdr *contact, const pjsip_rx_data *rdata)
+{
+ pjsip_expires_hdr *expires;
+ int expiration = aor->default_expiration;
+
+ if (contact->expires != -1) {
+ /* Expiration was provided with the contact itself */
+ expiration = contact->expires;
+ } else if ((expires = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL))) {
+ /* Expiration was provided using the Expires header */
+ expiration = expires->ivalue;
+ }
+
+ /* If the value has explicitly been set to 0, do not enforce */
+ if (!expiration) {
+ return expiration;
+ }
+
+ /* Enforce the range that we will allow for expiration */
+ if (expiration < aor->minimum_expiration) {
+ expiration = aor->minimum_expiration;
+ } else if (expiration > aor->maximum_expiration) {
+ expiration = aor->maximum_expiration;
+ }
+
+ return expiration;
+}
+
+/*! \brief Structure used for finding contact */
+struct registrar_contact_details {
+ /*! \brief Pool used for parsing URI */
+ pj_pool_t *pool;
+ /*! \brief URI being looked for */
+ pjsip_uri *uri;
+};
+
+/*! \brief Callback function for finding a contact */
+static int registrar_find_contact(void *obj, void *arg, int flags)
+{
+ struct ast_sip_contact *contact = obj;
+ const struct registrar_contact_details *details = arg;
+ pjsip_uri *contact_uri = pjsip_parse_uri(details->pool, (char*)contact->uri, strlen(contact->uri), 0);
+
+ return (pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, details->uri, contact_uri) == PJ_SUCCESS) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+/*! \brief Internal function which validates provided Contact headers to confirm that they are acceptable, and returns number of contacts */
+static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_container *contacts, struct ast_sip_aor *aor, int *added, int *updated, int *deleted)
+{
+ pjsip_contact_hdr *previous = NULL, *contact = (pjsip_contact_hdr *)&rdata->msg_info.msg->hdr;
+ struct registrar_contact_details details = {
+ .pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Contact Comparison", 256, 256),
+ };
+
+ if (!details.pool) {
+ return -1;
+ }
+
+ while ((contact = (pjsip_contact_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact->next))) {
+ int expiration;
+ RAII_VAR(struct ast_sip_contact *, existing, NULL, ao2_cleanup);
+
+ if (contact->star) {
+ /* The expiration MUST be 0 when a '*' contact is used and there must be no other contact */
+ if ((contact->expires != 0) || previous) {
+ pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
+ return -1;
+ }
+ } else if (previous && previous->star) {
+ /* If there is a previous contact and it is a '*' this is a deal breaker */
+ pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
+ return -1;
+ }
+ previous = contact;
+
+ if (!PJSIP_URI_SCHEME_IS_SIP(contact->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact->uri)) {
+ continue;
+ }
+
+ details.uri = pjsip_uri_get_uri(contact->uri);
+ expiration = registrar_get_expiration(aor, contact, rdata);
+
+ /* Determine if this is an add, update, or delete for policy enforcement purposes */
+ if (!(existing = ao2_callback(contacts, 0, registrar_find_contact, &details))) {
+ if (expiration) {
+ (*added)++;
+ }
+ } else if (expiration) {
+ (*updated)++;
+ } else {
+ (*deleted)++;
+ }
+ }
+
+ /* The provided contacts are acceptable, huzzah! */
+ pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
+ return 0;
+}
+
+/*! \brief Callback function which prunes static contacts */
+static int registrar_prune_static(void *obj, void *arg, int flags)
+{
+ struct ast_sip_contact *contact = obj;
+
+ return ast_tvzero(contact->expiration_time) ? CMP_MATCH : 0;
+}
+
+/*! \brief Internal function used to delete all contacts from an AOR */
+static int registrar_delete_contact(void *obj, void *arg, int flags)
+{
+ struct ast_sip_contact *contact = obj;
+
+ ast_sip_location_delete_contact(contact);
+
+ return 0;
+}
+
+/*! \brief Internal function which adds a contact to a response */
+static int registrar_add_contact(void *obj, void *arg, int flags)
+{
+ struct ast_sip_contact *contact = obj;
+ pjsip_tx_data *tdata = arg;
+ pjsip_contact_hdr *hdr = pjsip_contact_hdr_create(tdata->pool);
+ pj_str_t uri;
+
+ pj_strdup2_with_null(tdata->pool, &uri, contact->uri);
+ hdr->uri = pjsip_parse_uri(tdata->pool, uri.ptr, uri.slen, PJSIP_PARSE_URI_AS_NAMEADDR);
+ hdr->expires = ast_tvdiff_ms(contact->expiration_time, ast_tvnow()) / 1000;
+
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
+
+ return 0;
+}
+
+/*! \brief Helper function which adds a Date header to a response */
+static void registrar_add_date_header(pjsip_tx_data *tdata)
+{
+ char date[256];
+ struct tm tm;
+ time_t t = time(NULL);
+
+ gmtime_r(&t, &tm);
+ strftime(date, sizeof(date), "%a, %d %b %Y %T GMT", &tm);
+
+ ast_sip_add_header(tdata, "Date", date);
+}
+
+static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
+{
+ struct ast_sip_endpoint *endpoint = ast_pjsip_rdata_get_endpoint(rdata);
+ pjsip_sip_uri *uri;
+ char user_name[64], domain_name[64];
+ char *configured_aors, *aor_name;
+ RAII_VAR(struct ast_sip_aor *, aor, NULL, ao2_cleanup);
+ RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
+ int added = 0, updated = 0, deleted = 0;
+ pjsip_contact_hdr *contact_hdr = NULL;
+ struct registrar_contact_details details = { 0, };
+ pjsip_tx_data *tdata;
+ pjsip_response_addr addr;
+
+ if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_register_method) || !endpoint) {
+ return PJ_FALSE;
+ }
+
+ if (ast_strlen_zero(endpoint->aors)) {
+ /* Short circuit early if the endpoint has no AORs configured on it, which means no registration possible */
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ if (!PJSIP_URI_SCHEME_IS_SIP(rdata->msg_info.to->uri) && !PJSIP_URI_SCHEME_IS_SIPS(rdata->msg_info.to->uri)) {
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 416, NULL, NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ uri = pjsip_uri_get_uri(rdata->msg_info.to->uri);
+ ast_copy_pj_str(user_name, &uri->user, sizeof(user_name));
+ ast_copy_pj_str(domain_name, &uri->host, sizeof(domain_name));
+
+ configured_aors = ast_strdupa(endpoint->aors);
+
+ /* Iterate the configured AORs to see if the user or the user+domain match */
+ while ((aor_name = strsep(&configured_aors, ","))) {
+ char id[AST_UUID_STR_LEN];
+ RAII_VAR(struct ast_sip_domain_alias *, alias, NULL, ao2_cleanup);
+
+ snprintf(id, sizeof(id), "%s@%s", user_name, domain_name);
+ if (!strcmp(aor_name, id)) {
+ break;
+ }
+
+ if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) {
+ snprintf(id, sizeof(id), "%s@%s", user_name, alias->domain);
+ if (!strcmp(aor_name, id)) {
+ break;
+ }
+ }
+
+ if (!strcmp(aor_name, user_name)) {
+ break;
+ }
+ }
+
+ if (ast_strlen_zero(aor_name) || !(aor = ast_sip_location_retrieve_aor(aor_name))) {
+ /* The provided AOR name was not found (be it within the configuration or sorcery itself) */
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ if (!aor->max_contacts) {
+ /* Registration is not permitted for this AOR */
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ /* Retrieve the current contacts, we'll need to know whether to update or not */
+ contacts = ast_sip_location_retrieve_aor_contacts(aor);
+
+ /* So we don't count static contacts against max_contacts we prune them out from the container */
+ ao2_callback(contacts, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, registrar_prune_static, NULL);
+
+ if (registrar_validate_contacts(rdata, contacts, aor, &added, &updated, &deleted)) {
+ /* The provided Contact headers do not conform to the specification */
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 400, NULL, NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ if (((added - deleted) + (!aor->remove_existing ? ao2_container_count(contacts) : 0)) > aor->max_contacts) {
+ /* Enforce the maximum number of contacts */
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ if (!(details.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Contact Comparison", 256, 256))) {
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ /* Iterate each provided Contact header and add, update, or delete */
+ while ((contact_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact_hdr ? contact_hdr->next : NULL))) {
+ int expiration;
+ char contact_uri[PJSIP_MAX_URL_SIZE];
+ RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
+
+ if (contact_hdr->star) {
+ /* A star means to unregister everything, so do so for the possible contacts */
+ ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, registrar_delete_contact, NULL);
+ break;
+ }
+
+ if (!PJSIP_URI_SCHEME_IS_SIP(contact_hdr->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact_hdr->uri)) {
+ /* This registrar only currently supports sip: and sips: URI schemes */
+ continue;
+ }
+
+ expiration = registrar_get_expiration(aor, contact_hdr, rdata);
+ details.uri = pjsip_uri_get_uri(contact_hdr->uri);
+ pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, details.uri, contact_uri, sizeof(contact_uri));
+
+ if (!(contact = ao2_callback(contacts, OBJ_UNLINK, registrar_find_contact, &details))) {
+ /* If they are actually trying to delete a contact that does not exist... be forgiving */
+ if (!expiration) {
+ ast_verb(3, "Attempted to remove non-existent contact '%s' from AOR '%s' by request\n",
+ contact_uri, aor_name);
+ continue;
+ }
+
+ ast_sip_location_add_contact(aor, contact_uri, ast_tvadd(ast_tvnow(), ast_samp2tv(expiration, 1)));
+ ast_verb(3, "Added contact '%s' to AOR '%s' with expiration of %d seconds\n",
+ contact_uri, aor_name, expiration);
+ } else if (expiration) {
+ RAII_VAR(struct ast_sip_contact *, updated, ast_sorcery_copy(ast_sip_get_sorcery(), contact), ao2_cleanup);
+
+ updated->expiration_time = ast_tvadd(ast_tvnow(), ast_samp2tv(expiration, 1));
+
+ ast_sip_location_update_contact(updated);
+ ast_debug(3, "Refreshed contact '%s' on AOR '%s' with new expiration of %d seconds\n",
+ contact_uri, aor_name, expiration);
+ } else {
+ ast_sip_location_delete_contact(contact);
+ ast_verb(3, "Removed contact '%s' from AOR '%s' due to request\n", contact_uri, aor_name);
+ }
+ }
+
+ pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
+
+ /* If the AOR is configured to remove any existing contacts that have not been updated/added as a result of this REGISTER
+ * do so
+ */
+ if (aor->remove_existing) {
+ ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, registrar_delete_contact, NULL);
+ }
+
+ /* Update the contacts as things will probably have changed */
+ ao2_cleanup(contacts);
+ contacts = ast_sip_location_retrieve_aor_contacts(aor);
+
+ /* Send a response containing all of the contacts (including static) that are present on this AOR */
+ if (pjsip_endpt_create_response(ast_sip_get_pjsip_endpoint(), rdata, 200, NULL, &tdata) != PJ_SUCCESS) {
+ return PJ_TRUE;
+ }
+
+ /* Add the date header to the response, some UAs use this to set their date and time */
+ registrar_add_date_header(tdata);
+
+ ao2_callback(contacts, 0, registrar_add_contact, tdata);
+
+ if (pjsip_get_response_addr(tdata->pool, rdata, &addr) == PJ_SUCCESS) {
+ pjsip_endpt_send_response(ast_sip_get_pjsip_endpoint(), &addr, tdata, NULL, NULL);
+ } else {
+ pjsip_tx_data_dec_ref(tdata);
+ }
+
+ return PJ_TRUE;
+}
+
+static pjsip_module registrar_module = {
+ .name = { "Registrar", 9 },
+ .id = -1,
+ .priority = PJSIP_MOD_PRIORITY_APPLICATION,
+ .on_rx_request = registrar_on_rx_request,
+};
+
+static int load_module(void)
+{
+ const pj_str_t STR_REGISTER = { "REGISTER", 8 };
+
+ if (ast_sip_register_service(&registrar_module)) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ if (pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_ALLOW, NULL, 1, &STR_REGISTER) != PJ_SUCCESS) {
+ ast_sip_unregister_service(&registrar_module);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ ast_sip_unregister_service(&registrar_module);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Registrar Support",
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_APP_DEPEND,
+ );
diff --git a/res/res_sip_rfc3326.c b/res/res_sip_rfc3326.c
new file mode 100644
index 000000000..1c9ec6154
--- /dev/null
+++ b/res/res_sip_rfc3326.c
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+
+/*** MODULEINFO
+ <depend>pjproject</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/res_sip_session.h"
+#include "asterisk/module.h"
+#include "asterisk/causes.h"
+
+static void rfc3326_use_reason_header(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
+{
+ const pj_str_t str_reason = { "Reason", 6 };
+ pjsip_generic_string_hdr *header = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_reason, NULL);
+ char buf[20], *cause, *text;
+ int code;
+
+ if (!header) {
+ return;
+ }
+
+ ast_copy_pj_str(buf, &header->hvalue, sizeof(buf));
+ cause = ast_skip_blanks(buf);
+
+ if (strncasecmp(cause, "Q.850", 5) || !(cause = strstr(cause, "cause="))) {
+ return;
+ }
+
+ /* If text is present get rid of it */
+ if ((text = strstr(cause, ";"))) {
+ *text = '\0';
+ }
+
+ if (sscanf(cause, "cause=%30d", &code) != 1) {
+ return;
+ }
+
+ ast_channel_hangupcause_set(session->channel, code & 0x7f);
+}
+
+static int rfc3326_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
+{
+ if ((pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_bye_method) &&
+ pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_cancel_method)) ||
+ !session->channel) {
+ return 0;
+ }
+
+ rfc3326_use_reason_header(session, rdata);
+
+ return 0;
+}
+
+static void rfc3326_incoming_response(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
+{
+ struct pjsip_status_line status = rdata->msg_info.msg->line.status;
+
+ if ((status.code < 300) || !session->channel) {
+ return;
+ }
+
+ rfc3326_use_reason_header(session, rdata);
+}
+
+static void rfc3326_add_reason_header(struct ast_sip_session *session, struct pjsip_tx_data *tdata)
+{
+ char buf[20];
+
+ snprintf(buf, sizeof(buf), "Q.850;cause=%i", ast_channel_hangupcause(session->channel) & 0x7f);
+ ast_sip_add_header(tdata, "Reason", buf);
+
+ if (ast_channel_hangupcause(session->channel) == AST_CAUSE_ANSWERED_ELSEWHERE) {
+ ast_sip_add_header(tdata, "Reason", "SIP;cause=200;text=\"Call completed elsewhere\"");
+ }
+}
+
+static void rfc3326_outgoing_request(struct ast_sip_session *session, struct pjsip_tx_data *tdata)
+{
+ if ((pjsip_method_cmp(&tdata->msg->line.req.method, &pjsip_bye_method) &&
+ pjsip_method_cmp(&tdata->msg->line.req.method, &pjsip_cancel_method)) ||
+ !session->channel) {
+ return;
+ }
+
+ rfc3326_add_reason_header(session, tdata);
+}
+
+static void rfc3326_outgoing_response(struct ast_sip_session *session, struct pjsip_tx_data *tdata)
+{
+ struct pjsip_status_line status = tdata->msg->line.status;
+
+ if ((status.code < 300) || !session->channel) {
+ return;
+ }
+
+ rfc3326_add_reason_header(session, tdata);
+}
+
+static struct ast_sip_session_supplement rfc3326_supplement = {
+ .incoming_request = rfc3326_incoming_request,
+ .incoming_response = rfc3326_incoming_response,
+ .outgoing_request = rfc3326_outgoing_request,
+ .outgoing_response = rfc3326_outgoing_response,
+};
+
+static int load_module(void)
+{
+ ast_sip_session_register_supplement(&rfc3326_supplement);
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ ast_sip_session_unregister_supplement(&rfc3326_supplement);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP RFC3326 Support",
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_APP_DEPEND,
+ );
diff --git a/res/res_sip_sdp_rtp.c b/res/res_sip_sdp_rtp.c
new file mode 100644
index 000000000..13e6aa1aa
--- /dev/null
+++ b/res/res_sip_sdp_rtp.c
@@ -0,0 +1,848 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Joshua Colp <jcolp@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.
+ */
+
+/*! \file
+ *
+ * \author Joshua Colp <jcolp@digium.com>
+ *
+ * \brief SIP SDP media stream handling
+ */
+
+/*** MODULEINFO
+ <depend>pjproject</depend>
+ <depend>res_sip</depend>
+ <depend>res_sip_session</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+#include <pjmedia.h>
+#include <pjlib.h>
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "asterisk/rtp_engine.h"
+#include "asterisk/netsock2.h"
+#include "asterisk/channel.h"
+#include "asterisk/causes.h"
+#include "asterisk/sched.h"
+#include "asterisk/acl.h"
+
+#include "asterisk/res_sip.h"
+#include "asterisk/res_sip_session.h"
+
+/*! \brief Scheduler for RTCP purposes */
+static struct ast_sched_context *sched;
+
+/*! \brief Address for IPv4 RTP */
+static struct ast_sockaddr address_ipv4;
+
+/*! \brief Address for IPv6 RTP */
+static struct ast_sockaddr address_ipv6;
+
+static const char STR_AUDIO[] = "audio";
+static const int FD_AUDIO = 0;
+
+static const char STR_VIDEO[] = "video";
+static const int FD_VIDEO = 2;
+
+/*! \brief Retrieves an ast_format_type based on the given stream_type */
+static enum ast_format_type stream_to_media_type(const char *stream_type)
+{
+ if (!strcasecmp(stream_type, STR_AUDIO)) {
+ return AST_FORMAT_TYPE_AUDIO;
+ } else if (!strcasecmp(stream_type, STR_VIDEO)) {
+ return AST_FORMAT_TYPE_VIDEO;
+ }
+
+ return 0;
+}
+
+/*! \brief Get the starting descriptor for a media type */
+static int media_type_to_fdno(enum ast_format_type media_type)
+{
+ switch (media_type) {
+ case AST_FORMAT_TYPE_AUDIO: return FD_AUDIO;
+ case AST_FORMAT_TYPE_VIDEO: return FD_VIDEO;
+ case AST_FORMAT_TYPE_TEXT:
+ case AST_FORMAT_TYPE_IMAGE: break;
+ }
+ return -1;
+}
+
+/*! \brief Remove all other cap types but the one given */
+static void format_cap_only_type(struct ast_format_cap *caps, enum ast_format_type media_type)
+{
+ int i = AST_FORMAT_INC;
+ while (i <= AST_FORMAT_TYPE_TEXT) {
+ if (i != media_type) {
+ ast_format_cap_remove_bytype(caps, i);
+ }
+ i += AST_FORMAT_INC;
+ }
+}
+
+/*! \brief Internal function which creates an RTP instance */
+static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_media *session_media, unsigned int ipv6)
+{
+ struct ast_rtp_engine_ice *ice;
+
+ if (!(session_media->rtp = ast_rtp_instance_new("asterisk", sched, ipv6 ? &address_ipv6 : &address_ipv4, NULL))) {
+ return -1;
+ }
+
+ ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RTCP, 1);
+ ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_NAT, session->endpoint->rtp_symmetric);
+
+ ast_rtp_codecs_packetization_set(ast_rtp_instance_get_codecs(session_media->rtp),
+ session_media->rtp, &session->endpoint->prefs);
+
+ if (!session->endpoint->ice_support && (ice = ast_rtp_instance_get_ice(session_media->rtp))) {
+ ice->stop(session_media->rtp);
+ }
+
+ return 0;
+}
+
+static void get_codecs(struct ast_sip_session *session, const struct pjmedia_sdp_media *stream, struct ast_rtp_codecs *codecs)
+{
+ pjmedia_sdp_attr *attr;
+ pjmedia_sdp_rtpmap *rtpmap;
+ pjmedia_sdp_fmtp fmtp;
+ struct ast_format *format;
+ int i, num = 0;
+ char name[256];
+ char media[20];
+ char fmt_param[256];
+
+ ast_rtp_codecs_payloads_initialize(codecs);
+
+ /* Iterate through provided formats */
+ for (i = 0; i < stream->desc.fmt_count; ++i) {
+ /* The payload is kept as a string for things like t38 but for video it is always numerical */
+ ast_rtp_codecs_payloads_set_m_type(codecs, NULL, pj_strtoul(&stream->desc.fmt[i]));
+ /* Look for the optional rtpmap attribute */
+ if (!(attr = pjmedia_sdp_media_find_attr2(stream, "rtpmap", &stream->desc.fmt[i]))) {
+ continue;
+ }
+
+ /* Interpret the attribute as an rtpmap */
+ if ((pjmedia_sdp_attr_to_rtpmap(session->inv_session->pool_prov, attr, &rtpmap)) != PJ_SUCCESS) {
+ continue;
+ }
+
+ ast_copy_pj_str(name, &rtpmap->enc_name, sizeof(name));
+ ast_copy_pj_str(media, (pj_str_t*)&stream->desc.media, sizeof(media));
+ ast_rtp_codecs_payloads_set_rtpmap_type_rate(codecs, NULL, pj_strtoul(&stream->desc.fmt[i]),
+ media, name, 0, rtpmap->clock_rate);
+ /* Look for an optional associated fmtp attribute */
+ if (!(attr = pjmedia_sdp_media_find_attr2(stream, "fmtp", &rtpmap->pt))) {
+ continue;
+ }
+
+ if ((pjmedia_sdp_attr_get_fmtp(attr, &fmtp)) == PJ_SUCCESS) {
+ sscanf(pj_strbuf(&fmtp.fmt), "%d", &num);
+ if ((format = ast_rtp_codecs_get_payload_format(codecs, num))) {
+ ast_copy_pj_str(fmt_param, &fmtp.fmt_param, sizeof(fmt_param));
+ ast_format_sdp_parse(format, fmt_param);
+ }
+ }
+ }
+}
+
+static int set_caps(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ const struct pjmedia_sdp_media *stream)
+{
+ RAII_VAR(struct ast_format_cap *, caps, NULL, ast_format_cap_destroy);
+ RAII_VAR(struct ast_format_cap *, peer, NULL, ast_format_cap_destroy);
+ RAII_VAR(struct ast_format_cap *, joint, NULL, ast_format_cap_destroy);
+ enum ast_format_type media_type = stream_to_media_type(session_media->stream_type);
+ struct ast_rtp_codecs codecs;
+ struct ast_format fmt;
+ int fmts = 0;
+ int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&
+ !ast_format_cap_is_empty(session->direct_media_cap);
+
+ if (!(caps = ast_format_cap_alloc_nolock()) ||
+ !(peer = ast_format_cap_alloc_nolock())) {
+ ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n", session_media->stream_type);
+ return -1;
+ }
+
+ /* get the endpoint capabilities */
+ if (direct_media_enabled) {
+ ast_format_cap_joint_copy(session->endpoint->codecs, session->direct_media_cap, caps);
+ } else {
+ ast_format_cap_copy(caps, session->endpoint->codecs);
+ }
+ format_cap_only_type(caps, media_type);
+
+ /* get the capabilities on the peer */
+ get_codecs(session, stream, &codecs);
+ ast_rtp_codecs_payload_formats(&codecs, peer, &fmts);
+
+ /* get the joint capabilities between peer and endpoint */
+ if (!(joint = ast_format_cap_joint(caps, peer))) {
+ char usbuf[64], thembuf[64];
+
+ ast_rtp_codecs_payloads_destroy(&codecs);
+
+ ast_getformatname_multiple(usbuf, sizeof(usbuf), caps);
+ ast_getformatname_multiple(thembuf, sizeof(thembuf), peer);
+ ast_log(LOG_WARNING, "No joint capabilities between our configuration(%s) and incoming SDP(%s)\n", usbuf, thembuf);
+ return -1;
+ }
+
+ ast_rtp_codecs_payloads_copy(&codecs, ast_rtp_instance_get_codecs(session_media->rtp),
+ session_media->rtp);
+
+ ast_format_cap_copy(caps, session->req_caps);
+ ast_format_cap_remove_bytype(caps, media_type);
+ ast_format_cap_append(caps, joint);
+ ast_format_cap_append(session->req_caps, caps);
+
+ if (session->channel) {
+ ast_format_cap_copy(caps, ast_channel_nativeformats(session->channel));
+ ast_format_cap_remove_bytype(caps, media_type);
+ ast_format_cap_append(caps, joint);
+
+ /* Apply the new formats to the channel, potentially changing read/write formats while doing so */
+ ast_format_cap_append(ast_channel_nativeformats(session->channel), caps);
+ ast_codec_choose(&session->endpoint->prefs, caps, 0, &fmt);
+ ast_format_copy(ast_channel_rawwriteformat(session->channel), &fmt);
+ ast_format_copy(ast_channel_rawreadformat(session->channel), &fmt);
+ ast_set_read_format(session->channel, ast_channel_readformat(session->channel));
+ ast_set_write_format(session->channel, ast_channel_writeformat(session->channel));
+ }
+
+ ast_rtp_codecs_payloads_destroy(&codecs);
+ return 1;
+}
+
+static pjmedia_sdp_attr* generate_rtpmap_attr(pjmedia_sdp_media *media, pj_pool_t *pool, int rtp_code,
+ int asterisk_format, struct ast_format *format, int code)
+{
+ pjmedia_sdp_rtpmap rtpmap;
+ pjmedia_sdp_attr *attr = NULL;
+ char tmp[64];
+
+ snprintf(tmp, sizeof(tmp), "%d", rtp_code);
+ pj_strdup2(pool, &media->desc.fmt[media->desc.fmt_count++], tmp);
+ rtpmap.pt = media->desc.fmt[media->desc.fmt_count - 1];
+ rtpmap.clock_rate = ast_rtp_lookup_sample_rate2(asterisk_format, format, code);
+ pj_strdup2(pool, &rtpmap.enc_name, ast_rtp_lookup_mime_subtype2(asterisk_format, format, code, 0));
+ rtpmap.param.slen = 0;
+
+ pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
+
+ return attr;
+}
+
+static pjmedia_sdp_attr* generate_fmtp_attr(pj_pool_t *pool, struct ast_format *format, int rtp_code)
+{
+ struct ast_str *fmtp0 = ast_str_alloca(256);
+ pj_str_t fmtp1;
+ pjmedia_sdp_attr *attr = NULL;
+ char *tmp;
+
+ ast_format_sdp_generate(format, rtp_code, &fmtp0);
+ if (ast_str_strlen(fmtp0)) {
+ tmp = ast_str_buffer(fmtp0) + ast_str_strlen(fmtp0) - 1;
+ /* remove any carriage return line feeds */
+ while (*tmp == '\r' || *tmp == '\n') --tmp;
+ *++tmp = '\0';
+ /* ast...generate gives us everything, just need value */
+ tmp = strchr(ast_str_buffer(fmtp0), ':');
+ if (tmp && tmp + 1) {
+ fmtp1 = pj_str(tmp + 1);
+ } else {
+ fmtp1 = pj_str(ast_str_buffer(fmtp0));
+ }
+ attr = pjmedia_sdp_attr_create(pool, "fmtp", &fmtp1);
+ }
+ return attr;
+}
+
+/*! \brief Function which adds ICE attributes to a media stream */
+static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media)
+{
+ struct ast_rtp_engine_ice *ice;
+ struct ao2_container *candidates;
+ const char *username, *password;
+ pj_str_t stmp;
+ pjmedia_sdp_attr *attr;
+ struct ao2_iterator it_candidates;
+ struct ast_rtp_engine_ice_candidate *candidate;
+
+ if (!session->endpoint->ice_support || !(ice = ast_rtp_instance_get_ice(session_media->rtp)) ||
+ !(candidates = ice->get_local_candidates(session_media->rtp))) {
+ return;
+ }
+
+ if ((username = ice->get_ufrag(session_media->rtp))) {
+ attr = pjmedia_sdp_attr_create(pool, "ice-ufrag", pj_cstr(&stmp, username));
+ media->attr[media->attr_count++] = attr;
+ }
+
+ if ((password = ice->get_password(session_media->rtp))) {
+ attr = pjmedia_sdp_attr_create(pool, "ice-pwd", pj_cstr(&stmp, password));
+ media->attr[media->attr_count++] = attr;
+ }
+
+ it_candidates = ao2_iterator_init(candidates, 0);
+ for (; (candidate = ao2_iterator_next(&it_candidates)); ao2_ref(candidate, -1)) {
+ struct ast_str *attr_candidate = ast_str_create(128);
+
+ ast_str_set(&attr_candidate, -1, "%s %d %s %d %s ", candidate->foundation, candidate->id, candidate->transport,
+ candidate->priority, ast_sockaddr_stringify_host(&candidate->address));
+ ast_str_append(&attr_candidate, -1, "%s typ ", ast_sockaddr_stringify_port(&candidate->address));
+
+ switch (candidate->type) {
+ case AST_RTP_ICE_CANDIDATE_TYPE_HOST:
+ ast_str_append(&attr_candidate, -1, "host");
+ break;
+ case AST_RTP_ICE_CANDIDATE_TYPE_SRFLX:
+ ast_str_append(&attr_candidate, -1, "srflx");
+ break;
+ case AST_RTP_ICE_CANDIDATE_TYPE_RELAYED:
+ ast_str_append(&attr_candidate, -1, "relay");
+ break;
+ }
+
+ if (!ast_sockaddr_isnull(&candidate->relay_address)) {
+ ast_str_append(&attr_candidate, -1, " raddr %s rport ", ast_sockaddr_stringify_host(&candidate->relay_address));
+ ast_str_append(&attr_candidate, -1, " %s", ast_sockaddr_stringify_port(&candidate->relay_address));
+ }
+
+ attr = pjmedia_sdp_attr_create(pool, "candidate", pj_cstr(&stmp, ast_str_buffer(attr_candidate)));
+ media->attr[media->attr_count++] = attr;
+
+ ast_free(attr_candidate);
+ }
+
+ ao2_iterator_destroy(&it_candidates);
+}
+
+/*! \brief Function which processes ICE attributes in an audio stream */
+static void process_ice_attributes(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream)
+{
+ struct ast_rtp_engine_ice *ice;
+ const pjmedia_sdp_attr *attr;
+ char attr_value[256];
+ unsigned int attr_i;
+
+ /* If ICE support is not enabled or available exit early */
+ if (!session->endpoint->ice_support || !(ice = ast_rtp_instance_get_ice(session_media->rtp))) {
+ return;
+ }
+
+ if ((attr = pjmedia_sdp_media_find_attr2(remote_stream, "ice-ufrag", NULL))) {
+ ast_copy_pj_str(attr_value, (pj_str_t*)&attr->value, sizeof(attr_value));
+ ice->set_authentication(session_media->rtp, attr_value, NULL);
+ }
+
+ if ((attr = pjmedia_sdp_media_find_attr2(remote_stream, "ice-pwd", NULL))) {
+ ast_copy_pj_str(attr_value, (pj_str_t*)&attr->value, sizeof(attr_value));
+ ice->set_authentication(session_media->rtp, NULL, attr_value);
+ }
+
+ if (pjmedia_sdp_media_find_attr2(remote_stream, "ice-lite", NULL)) {
+ ice->ice_lite(session_media->rtp);
+ }
+
+ /* Find all of the candidates */
+ for (attr_i = 0; attr_i < remote_stream->attr_count; ++attr_i) {
+ char foundation[32], transport[32], address[PJ_INET6_ADDRSTRLEN + 1], cand_type[6], relay_address[PJ_INET6_ADDRSTRLEN + 1] = "";
+ int port, relay_port = 0;
+ struct ast_rtp_engine_ice_candidate candidate = { 0, };
+
+ attr = remote_stream->attr[attr_i];
+
+ /* If this is not a candidate line skip it */
+ if (pj_strcmp2(&attr->name, "candidate")) {
+ continue;
+ }
+
+ ast_copy_pj_str(attr_value, (pj_str_t*)&attr->value, sizeof(attr_value));
+
+ if (sscanf(attr_value, "%31s %30u %31s %30u %46s %30u typ %5s %*s %23s %*s %30u", foundation, &candidate.id, transport,
+ &candidate.priority, address, &port, cand_type, relay_address, &relay_port) < 7) {
+ /* Candidate did not parse properly */
+ continue;
+ }
+
+ candidate.foundation = foundation;
+ candidate.transport = transport;
+
+ ast_sockaddr_parse(&candidate.address, address, PARSE_PORT_FORBID);
+ ast_sockaddr_set_port(&candidate.address, port);
+
+ if (!strcasecmp(cand_type, "host")) {
+ candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_HOST;
+ } else if (!strcasecmp(cand_type, "srflx")) {
+ candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_SRFLX;
+ } else if (!strcasecmp(cand_type, "relay")) {
+ candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_RELAYED;
+ } else {
+ continue;
+ }
+
+ if (!ast_strlen_zero(relay_address)) {
+ ast_sockaddr_parse(&candidate.relay_address, relay_address, PARSE_PORT_FORBID);
+ }
+
+ if (relay_port) {
+ ast_sockaddr_set_port(&candidate.relay_address, relay_port);
+ }
+
+ ice->add_remote_candidate(session_media->rtp, &candidate);
+ }
+
+ ice->start(session_media->rtp);
+}
+
+static void apply_packetization(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ const struct pjmedia_sdp_media *remote_stream)
+{
+ pjmedia_sdp_attr *attr;
+ pj_str_t value;
+ unsigned long framing;
+ int codec;
+ struct ast_codec_pref *pref = &ast_rtp_instance_get_codecs(session_media->rtp)->pref;
+
+ /* Apply packetization if available and configured to do so */
+ if (!session->endpoint->use_ptime || !(attr = pjmedia_sdp_media_find_attr2(remote_stream, "ptime", NULL))) {
+ return;
+ }
+
+ value = attr->value;
+ framing = pj_strtoul(pj_strltrim(&value));
+
+ for (codec = 0; codec < AST_RTP_MAX_PT; codec++) {
+ struct ast_rtp_payload_type format = ast_rtp_codecs_payload_lookup(ast_rtp_instance_get_codecs(
+ session_media->rtp), codec);
+
+ if (!format.asterisk_format) {
+ continue;
+ }
+
+ ast_codec_pref_setsize(pref, &format.format, framing);
+ }
+
+ ast_rtp_codecs_packetization_set(ast_rtp_instance_get_codecs(session_media->rtp),
+ session_media->rtp, pref);
+}
+
+/*! \brief Function which negotiates an incoming media stream */
+static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream)
+{
+ char host[NI_MAXHOST];
+ RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free_ptr);
+ enum ast_format_type media_type = stream_to_media_type(session_media->stream_type);
+
+ /* If no type formats have been configured reject this stream */
+ if (!ast_format_cap_has_type(session->endpoint->codecs, media_type)) {
+ return 0;
+ }
+
+ ast_copy_pj_str(host, stream->conn ? &stream->conn->addr : &sdp->conn->addr, sizeof(host));
+
+ /* Ensure that the address provided is valid */
+ if (ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC) <= 0) {
+ /* The provided host was actually invalid so we error out this negotiation */
+ return -1;
+ }
+
+ /* Using the connection information create an appropriate RTP instance */
+ if (!session_media->rtp && create_rtp(session, session_media, ast_sockaddr_is_ipv6(addrs))) {
+ return -1;
+ }
+
+ return set_caps(session, session_media, stream);
+}
+
+/*! \brief Function which creates an outgoing stream */
+static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ struct pjmedia_sdp_session *sdp)
+{
+ pj_pool_t *pool = session->inv_session->pool_prov;
+ static const pj_str_t STR_IN = { "IN", 2 };
+ static const pj_str_t STR_IP4 = { "IP4", 3};
+ static const pj_str_t STR_IP6 = { "IP6", 3};
+ static const pj_str_t STR_RTP_AVP = { "RTP/AVP", 7 };
+ static const pj_str_t STR_SENDRECV = { "sendrecv", 8 };
+ pjmedia_sdp_media *media;
+ char hostip[PJ_INET6_ADDRSTRLEN+2];
+ struct ast_sockaddr addr;
+ char tmp[512];
+ pj_str_t stmp;
+ pjmedia_sdp_attr *attr;
+ int index = 0, min_packet_size = 0, noncodec = (session->endpoint->dtmf == AST_SIP_DTMF_RFC_4733) ? AST_RTP_DTMF : 0;
+ int rtp_code;
+ struct ast_format format;
+ struct ast_format compat_format;
+ RAII_VAR(struct ast_format_cap *, caps, NULL, ast_format_cap_destroy);
+ enum ast_format_type media_type = stream_to_media_type(session_media->stream_type);
+
+ int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&
+ !ast_format_cap_is_empty(session->direct_media_cap);
+
+ if (!ast_format_cap_has_type(session->endpoint->codecs, media_type)) {
+ /* If no type formats are configured don't add a stream */
+ return 0;
+ } else if (!session_media->rtp && create_rtp(session, session_media, session->endpoint->rtp_ipv6)) {
+ return -1;
+ }
+
+ if (!(media = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_media))) ||
+ !(media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn)))) {
+ return -1;
+ }
+
+ /* TODO: This should eventually support SRTP */
+ media->desc.media = pj_str(session_media->stream_type);
+ media->desc.transport = STR_RTP_AVP;
+
+ /* Add connection level details */
+ if (direct_media_enabled) {
+ ast_copy_string(hostip, ast_sockaddr_stringify_fmt(&session_media->direct_media_addr, AST_SOCKADDR_STR_ADDR), sizeof(hostip));
+ } else if (ast_strlen_zero(session->endpoint->external_media_address)) {
+ pj_sockaddr localaddr;
+
+ if (pj_gethostip(session->endpoint->rtp_ipv6 ? pj_AF_INET6() : pj_AF_INET(), &localaddr)) {
+ return -1;
+ }
+ pj_sockaddr_print(&localaddr, hostip, sizeof(hostip), 2);
+ } else {
+ ast_copy_string(hostip, session->endpoint->external_media_address, sizeof(hostip));
+ }
+
+ media->conn->net_type = STR_IN;
+ media->conn->addr_type = session->endpoint->rtp_ipv6 ? STR_IP6 : STR_IP4;
+ pj_strdup2(pool, &media->conn->addr, hostip);
+ ast_rtp_instance_get_local_address(session_media->rtp, &addr);
+ media->desc.port = direct_media_enabled ? ast_sockaddr_port(&session_media->direct_media_addr) : (pj_uint16_t) ast_sockaddr_port(&addr);
+ media->desc.port_count = 1;
+
+ /* Add ICE attributes and candidates */
+ add_ice_to_stream(session, session_media, pool, media);
+
+ if (!(caps = ast_format_cap_alloc_nolock())) {
+ ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n", session_media->stream_type);
+ return -1;
+ }
+
+ if (direct_media_enabled) {
+ ast_format_cap_joint_copy(session->endpoint->codecs, session->direct_media_cap, caps);
+ } else if (ast_format_cap_is_empty(session->req_caps)) {
+ ast_format_cap_copy(caps, session->endpoint->codecs);
+ } else {
+ ast_format_cap_joint_copy(session->endpoint->codecs, session->req_caps, caps);
+ }
+
+ for (index = 0; ast_codec_pref_index(&session->endpoint->prefs, index, &format); ++index) {
+ struct ast_codec_pref *pref = &ast_rtp_instance_get_codecs(session_media->rtp)->pref;
+
+ if (AST_FORMAT_GET_TYPE(format.id) != media_type) {
+ continue;
+ }
+
+ if (!ast_format_cap_get_compatible_format(caps, &format, &compat_format)) {
+ continue;
+ }
+
+ if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp), 1, &compat_format, 0)) == -1) {
+ return -1;
+ }
+
+ if (!(attr = generate_rtpmap_attr(media, pool, rtp_code, 1, &compat_format, 0))) {
+ continue;
+ }
+
+ media->attr[media->attr_count++] = attr;
+
+ if ((attr = generate_fmtp_attr(pool, &compat_format, rtp_code))) {
+ media->attr[media->attr_count++] = attr;
+ }
+
+ if (pref && media_type != AST_FORMAT_TYPE_VIDEO) {
+ struct ast_format_list fmt = ast_codec_pref_getsize(pref, &compat_format);
+ if (fmt.cur_ms && ((fmt.cur_ms < min_packet_size) || !min_packet_size)) {
+ min_packet_size = fmt.cur_ms;
+ }
+ }
+ }
+
+ /* Add non-codec formats */
+ if (media_type != AST_FORMAT_TYPE_VIDEO) {
+ for (index = 1LL; index <= AST_RTP_MAX; index <<= 1) {
+ if (!(noncodec & index) || (rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp),
+ 0, NULL, index)) == -1) {
+ continue;
+ }
+
+ if (!(attr = generate_rtpmap_attr(media, pool, rtp_code, 0, NULL, index))) {
+ continue;
+ }
+
+ media->attr[media->attr_count++] = attr;
+
+ if (index == AST_RTP_DTMF) {
+ snprintf(tmp, sizeof(tmp), "%d 0-16", rtp_code);
+ attr = pjmedia_sdp_attr_create(pool, "fmtp", pj_cstr(&stmp, tmp));
+ media->attr[media->attr_count++] = attr;
+ }
+ }
+ }
+
+ /* If ptime is set add it as an attribute */
+ if (min_packet_size) {
+ snprintf(tmp, sizeof(tmp), "%d", min_packet_size);
+ attr = pjmedia_sdp_attr_create(pool, "ptime", pj_cstr(&stmp, tmp));
+ media->attr[media->attr_count++] = attr;
+ }
+
+ /* Add the sendrecv attribute - we purposely don't keep track because pjmedia-sdp will automatically change our offer for us */
+ attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr);
+ attr->name = STR_SENDRECV;
+ media->attr[media->attr_count++] = attr;
+
+ /* Add the media stream to the SDP */
+ sdp->media[sdp->media_count++] = media;
+
+ return 1;
+}
+
+static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream,
+ const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream)
+{
+ RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free_ptr);
+ enum ast_format_type media_type = stream_to_media_type(session_media->stream_type);
+ char host[NI_MAXHOST];
+ int fdno;
+
+ if (!session->channel) {
+ return 1;
+ }
+
+ /* Create an RTP instance if need be */
+ if (!session_media->rtp && create_rtp(session, session_media, session->endpoint->rtp_ipv6)) {
+ return -1;
+ }
+
+ ast_copy_pj_str(host, remote_stream->conn ? &remote_stream->conn->addr : &remote->conn->addr, sizeof(host));
+
+ /* Ensure that the address provided is valid */
+ if (ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC) <= 0) {
+ /* The provided host was actually invalid so we error out this negotiation */
+ return -1;
+ }
+
+ /* Apply connection information to the RTP instance */
+ ast_sockaddr_set_port(addrs, remote_stream->desc.port);
+ ast_rtp_instance_set_remote_address(session_media->rtp, addrs);
+
+ if (set_caps(session, session_media, local_stream) < 1) {
+ return -1;
+ }
+
+ if (media_type == AST_FORMAT_TYPE_AUDIO) {
+ apply_packetization(session, session_media, remote_stream);
+ }
+
+ if ((fdno = media_type_to_fdno(media_type)) < 0) {
+ return -1;
+ }
+ ast_channel_set_fd(session->channel, fdno, ast_rtp_instance_fd(session_media->rtp, 0));
+ ast_channel_set_fd(session->channel, fdno + 1, ast_rtp_instance_fd(session_media->rtp, 1));
+
+ /* If ICE support is enabled find all the needed attributes */
+ process_ice_attributes(session, session_media, remote, remote_stream);
+
+ /* audio stream handles music on hold */
+ if (media_type != AST_FORMAT_TYPE_AUDIO) {
+ return 1;
+ }
+
+ /* Music on hold for audio streams only */
+ if (session_media->held &&
+ (!ast_sockaddr_isnull(addrs) ||
+ !pjmedia_sdp_media_find_attr2(remote_stream, "sendonly", NULL))) {
+ /* The remote side has taken us off hold */
+ ast_queue_control(session->channel, AST_CONTROL_UNHOLD);
+ ast_queue_frame(session->channel, &ast_null_frame);
+ session_media->held = 0;
+ } else if (ast_sockaddr_isnull(addrs) ||
+ ast_sockaddr_is_any(addrs) ||
+ pjmedia_sdp_media_find_attr2(remote_stream, "sendonly", NULL)) {
+ /* The remote side has put us on hold */
+ ast_queue_control_data(session->channel, AST_CONTROL_HOLD, S_OR(session->endpoint->mohsuggest, NULL),
+ !ast_strlen_zero(session->endpoint->mohsuggest) ? strlen(session->endpoint->mohsuggest) + 1 : 0);
+ ast_rtp_instance_stop(session_media->rtp);
+ ast_queue_frame(session->channel, &ast_null_frame);
+ session_media->held = 1;
+ } else {
+ /* The remote side has not changed state, but make sure the instance is active */
+ ast_rtp_instance_activate(session_media->rtp);
+ }
+
+ return 1;
+}
+
+/*! \brief Function which updates the media stream with external media address, if applicable */
+static void change_outgoing_sdp_stream_media_address(pjsip_tx_data *tdata, struct pjmedia_sdp_media *stream, struct ast_sip_transport *transport)
+{
+ char host[NI_MAXHOST];
+ struct ast_sockaddr addr = { { 0, } };
+
+ ast_copy_pj_str(host, &stream->conn->addr, sizeof(host));
+ ast_sockaddr_parse(&addr, host, PARSE_PORT_FORBID);
+
+ /* Is the address within the SDP inside the same network? */
+ if (ast_apply_ha(transport->localnet, &addr) == AST_SENSE_ALLOW) {
+ return;
+ }
+
+ pj_strdup2(tdata->pool, &stream->conn->addr, transport->external_media_address);
+}
+
+/*! \brief Function which destroys the RTP instance when session ends */
+static void stream_destroy(struct ast_sip_session_media *session_media)
+{
+ if (session_media->rtp) {
+ ast_rtp_instance_stop(session_media->rtp);
+ ast_rtp_instance_destroy(session_media->rtp);
+ }
+}
+
+/*! \brief SDP handler for 'audio' media stream */
+static struct ast_sip_session_sdp_handler audio_sdp_handler = {
+ .id = STR_AUDIO,
+ .negotiate_incoming_sdp_stream = negotiate_incoming_sdp_stream,
+ .create_outgoing_sdp_stream = create_outgoing_sdp_stream,
+ .apply_negotiated_sdp_stream = apply_negotiated_sdp_stream,
+ .change_outgoing_sdp_stream_media_address = change_outgoing_sdp_stream_media_address,
+ .stream_destroy = stream_destroy,
+};
+
+/*! \brief SDP handler for 'video' media stream */
+static struct ast_sip_session_sdp_handler video_sdp_handler = {
+ .id = STR_VIDEO,
+ .negotiate_incoming_sdp_stream = negotiate_incoming_sdp_stream,
+ .create_outgoing_sdp_stream = create_outgoing_sdp_stream,
+ .apply_negotiated_sdp_stream = apply_negotiated_sdp_stream,
+ .change_outgoing_sdp_stream_media_address = change_outgoing_sdp_stream_media_address,
+ .stream_destroy = stream_destroy,
+};
+
+static int video_info_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
+{
+ struct pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
+ pjsip_tx_data *tdata;
+
+ if (pj_strcmp2(&rdata->msg_info.msg->body->content_type.type, "application") ||
+ pj_strcmp2(&rdata->msg_info.msg->body->content_type.subtype, "media_control+xml")) {
+
+ return 0;
+ }
+
+ ast_queue_control(session->channel, AST_CONTROL_VIDUPDATE);
+
+ if (pjsip_dlg_create_response(session->inv_session->dlg, rdata, 200, NULL, &tdata) == PJ_SUCCESS) {
+ pjsip_dlg_send_response(session->inv_session->dlg, tsx, tdata);
+ }
+
+ return 0;
+}
+
+static struct ast_sip_session_supplement video_info_supplement = {
+ .method = "INFO",
+ .incoming_request = video_info_incoming_request,
+};
+
+/*! \brief Unloads the sdp RTP/AVP module from Asterisk */
+static int unload_module(void)
+{
+ ast_sip_session_unregister_supplement(&video_info_supplement);
+ ast_sip_session_unregister_sdp_handler(&video_sdp_handler, STR_VIDEO);
+ ast_sip_session_unregister_sdp_handler(&audio_sdp_handler, STR_AUDIO);
+
+ if (sched) {
+ ast_sched_context_destroy(sched);
+ }
+
+ return 0;
+}
+
+/*!
+ * \brief Load the module
+ *
+ * Module loading including tests for configuration or dependencies.
+ * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
+ * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
+ * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the
+ * configuration file or other non-critical problem return
+ * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
+ */
+static int load_module(void)
+{
+ ast_sockaddr_parse(&address_ipv4, "0.0.0.0", 0);
+ ast_sockaddr_parse(&address_ipv6, "::", 0);
+
+ if (!(sched = ast_sched_context_create())) {
+ ast_log(LOG_ERROR, "Unable to create scheduler context.\n");
+ goto end;
+ }
+
+ if (ast_sched_start_thread(sched)) {
+ ast_log(LOG_ERROR, "Unable to create scheduler context thread.\n");
+ goto end;
+ }
+
+ if (ast_sip_session_register_sdp_handler(&audio_sdp_handler, STR_AUDIO)) {
+ ast_log(LOG_ERROR, "Unable to register SDP handler for %s stream type\n", STR_AUDIO);
+ goto end;
+ }
+
+ if (ast_sip_session_register_sdp_handler(&video_sdp_handler, STR_VIDEO)) {
+ ast_log(LOG_ERROR, "Unable to register SDP handler for %s stream type\n", STR_VIDEO);
+ goto end;
+ }
+
+ ast_sip_session_register_supplement(&video_info_supplement);
+
+ return AST_MODULE_LOAD_SUCCESS;
+end:
+ unload_module();
+
+ return AST_MODULE_LOAD_FAILURE;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP SDP RTP/AVP stream handler",
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_CHANNEL_DRIVER,
+ );
diff --git a/res/res_sip_session.c b/res/res_sip_session.c
new file mode 100644
index 000000000..6a31d9448
--- /dev/null
+++ b/res/res_sip_session.c
@@ -0,0 +1,1799 @@
+/*
+* 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.
+*/
+
+/*** MODULEINFO
+ <depend>pjproject</depend>
+ <depend>res_sip</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+#include <pjlib.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/res_sip_session.h"
+#include "asterisk/datastore.h"
+#include "asterisk/module.h"
+#include "asterisk/logger.h"
+#include "asterisk/res_sip.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/lock.h"
+#include "asterisk/uuid.h"
+#include "asterisk/pbx.h"
+#include "asterisk/taskprocessor.h"
+#include "asterisk/causes.h"
+
+#define SDP_HANDLER_BUCKETS 11
+
+/* Some forward declarations */
+static void handle_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata);
+static void handle_incoming_response(struct ast_sip_session *session, pjsip_rx_data *rdata);
+static int handle_incoming(struct ast_sip_session *session, pjsip_rx_data *rdata);
+static void handle_outgoing_request(struct ast_sip_session *session, pjsip_tx_data *tdata);
+static void handle_outgoing_response(struct ast_sip_session *session, pjsip_tx_data *tdata);
+static void handle_outgoing(struct ast_sip_session *session, pjsip_tx_data *tdata);
+
+/*! \brief NAT hook for modifying outgoing messages with SDP */
+static struct ast_sip_nat_hook *nat_hook;
+
+/*!
+ * \brief Registered SDP stream handlers
+ *
+ * This container is keyed on stream types. Each
+ * object in the container is a linked list of
+ * handlers for the stream type.
+ */
+static struct ao2_container *sdp_handlers;
+
+/*!
+ * These are the objects in the sdp_handlers container
+ */
+struct sdp_handler_list {
+ /* The list of handlers to visit */
+ AST_LIST_HEAD_NOLOCK(, ast_sip_session_sdp_handler) list;
+ /* The handlers in this list handle streams of this type */
+ char stream_type[1];
+};
+
+static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, struct ast_sip_session *session, const pjmedia_sdp_session *offer);
+
+static int sdp_handler_list_hash(const void *obj, int flags)
+{
+ const struct sdp_handler_list *handler_list = obj;
+ const char *stream_type = flags & OBJ_KEY ? obj : handler_list->stream_type;
+
+ return ast_str_hash(stream_type);
+}
+
+static int sdp_handler_list_cmp(void *obj, void *arg, int flags)
+{
+ struct sdp_handler_list *handler_list1 = obj;
+ struct sdp_handler_list *handler_list2 = arg;
+ const char *stream_type2 = flags & OBJ_KEY ? arg : handler_list2->stream_type;
+
+ return strcmp(handler_list1->stream_type, stream_type2) ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+static int session_media_hash(const void *obj, int flags)
+{
+ const struct ast_sip_session_media *session_media = obj;
+ const char *stream_type = flags & OBJ_KEY ? obj : session_media->stream_type;
+
+ return ast_str_hash(stream_type);
+}
+
+static int session_media_cmp(void *obj, void *arg, int flags)
+{
+ struct ast_sip_session_media *session_media1 = obj;
+ struct ast_sip_session_media *session_media2 = arg;
+ const char *stream_type2 = flags & OBJ_KEY ? arg : session_media2->stream_type;
+
+ return strcmp(session_media1->stream_type, stream_type2) ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+int ast_sip_session_register_sdp_handler(struct ast_sip_session_sdp_handler *handler, const char *stream_type)
+{
+ RAII_VAR(struct sdp_handler_list *, handler_list,
+ ao2_find(sdp_handlers, stream_type, OBJ_KEY), ao2_cleanup);
+ SCOPED_AO2LOCK(lock, sdp_handlers);
+
+ if (handler_list) {
+ struct ast_sip_session_sdp_handler *iter;
+ /* Check if this handler is already registered for this stream type */
+ AST_LIST_TRAVERSE(&handler_list->list, iter, next) {
+ if (!strcmp(iter->id, handler->id)) {
+ ast_log(LOG_WARNING, "Handler '%s' already registered for stream type '%s'.\n", handler->id, stream_type);
+ return -1;
+ }
+ }
+ AST_LIST_INSERT_TAIL(&handler_list->list, handler, next);
+ ast_debug(1, "Registered SDP stream handler '%s' for stream type '%s'\n", handler->id, stream_type);
+ ast_module_ref(ast_module_info->self);
+ return 0;
+ }
+
+ /* No stream of this type has been registered yet, so we need to create a new list */
+ handler_list = ao2_alloc(sizeof(*handler_list) + strlen(stream_type), NULL);
+ if (!handler_list) {
+ return -1;
+ }
+ /* Safe use of strcpy */
+ strcpy(handler_list->stream_type, stream_type);
+ AST_LIST_HEAD_INIT_NOLOCK(&handler_list->list);
+ AST_LIST_INSERT_TAIL(&handler_list->list, handler, next);
+ if (!ao2_link(sdp_handlers, handler_list)) {
+ return -1;
+ }
+ ast_debug(1, "Registered SDP stream handler '%s' for stream type '%s'\n", handler->id, stream_type);
+ ast_module_ref(ast_module_info->self);
+ return 0;
+}
+
+static int remove_handler(void *obj, void *arg, void *data, int flags)
+{
+ struct sdp_handler_list *handler_list = obj;
+ struct ast_sip_session_sdp_handler *handler = data;
+ struct ast_sip_session_sdp_handler *iter;
+ const char *stream_type = arg;
+
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&handler_list->list, iter, next) {
+ if (!strcmp(iter->id, handler->id)) {
+ AST_LIST_REMOVE_CURRENT(next);
+ ast_debug(1, "Unregistered SDP stream handler '%s' for stream type '%s'\n", handler->id, stream_type);
+ ast_module_unref(ast_module_info->self);
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+
+ if (AST_LIST_EMPTY(&handler_list->list)) {
+ ast_debug(3, "No more handlers exist for stream type '%s'\n", stream_type);
+ return CMP_MATCH;
+ } else {
+ return CMP_STOP;
+ }
+}
+
+void ast_sip_session_unregister_sdp_handler(struct ast_sip_session_sdp_handler *handler, const char *stream_type)
+{
+ ao2_callback_data(sdp_handlers, OBJ_KEY | OBJ_UNLINK | OBJ_NODATA, remove_handler, (void *)stream_type, handler);
+}
+
+static int validate_port_hash(const void *obj, int flags)
+{
+ const int *port = obj;
+ return *port;
+}
+
+static int validate_port_cmp(void *obj, void *arg, int flags)
+{
+ int *port1 = obj;
+ int *port2 = arg;
+
+ return *port1 == *port2 ? CMP_MATCH | CMP_STOP : 0;
+}
+
+struct bundle_assoc {
+ int port;
+ char tag[1];
+};
+
+static int bundle_assoc_hash(const void *obj, int flags)
+{
+ const struct bundle_assoc *assoc = obj;
+ const char *tag = flags & OBJ_KEY ? obj : assoc->tag;
+
+ return ast_str_hash(tag);
+}
+
+static int bundle_assoc_cmp(void *obj, void *arg, int flags)
+{
+ struct bundle_assoc *assoc1 = obj;
+ struct bundle_assoc *assoc2 = arg;
+ const char *tag2 = flags & OBJ_KEY ? arg : assoc2->tag;
+
+ return strcmp(assoc1->tag, tag2) ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+/* return must be ast_freed */
+static pjmedia_sdp_attr *media_get_mid(pjmedia_sdp_media *media)
+{
+ pjmedia_sdp_attr *attr = pjmedia_sdp_media_find_attr2(media, "mid", NULL);
+ if (!attr) {
+ return NULL;
+ }
+
+ return attr;
+}
+
+static int get_bundle_port(const pjmedia_sdp_session *sdp, const char *mid)
+{
+ int i;
+ for (i = 0; i < sdp->media_count; ++i) {
+ pjmedia_sdp_attr *mid_attr = media_get_mid(sdp->media[i]);
+ if (mid_attr && !pj_strcmp2(&mid_attr->value, mid)) {
+ return sdp->media[i]->desc.port;
+ }
+ }
+
+ return -1;
+}
+
+static int validate_incoming_sdp(const pjmedia_sdp_session *sdp)
+{
+ int i;
+ RAII_VAR(struct ao2_container *, portlist, ao2_container_alloc(5, validate_port_hash, validate_port_cmp), ao2_cleanup);
+ RAII_VAR(struct ao2_container *, bundle_assoc_list, ao2_container_alloc(5, bundle_assoc_hash, bundle_assoc_cmp), ao2_cleanup);
+
+ /* check for bundles (for websocket RTP multiplexing, there can be more than one) */
+ for (i = 0; i < sdp->attr_count; ++i) {
+ char *bundle_list;
+ int bundle_port = 0;
+ if (pj_stricmp2(&sdp->attr[i]->name, "group")) {
+ continue;
+ }
+
+ /* check to see if this group is a bundle */
+ if (7 >= sdp->attr[i]->value.slen || pj_strnicmp2(&sdp->attr[i]->value, "bundle ", 7)) {
+ continue;
+ }
+
+ bundle_list = ast_alloca(sdp->attr[i]->value.slen - 6);
+ strncpy(bundle_list, sdp->attr[i]->value.ptr + 7, sdp->attr[i]->value.slen - 7);
+ bundle_list[sdp->attr[i]->value.slen - 7] = '\0';
+ while (bundle_list) {
+ char *item;
+ RAII_VAR(struct bundle_assoc *, assoc, NULL, ao2_cleanup);
+ item = strsep(&bundle_list, " ,");
+ if (!bundle_port) {
+ RAII_VAR(int *, port, ao2_alloc(sizeof(int), NULL), ao2_cleanup);
+ RAII_VAR(int *, port_match, NULL, ao2_cleanup);
+ bundle_port = get_bundle_port(sdp, item);
+ if (bundle_port < 0) {
+ return -1;
+ }
+ port_match = ao2_find(portlist, &bundle_port, OBJ_KEY);
+ if (port_match) {
+ /* bundle port aready consumed by a different bundle */
+ return -1;
+ }
+ *port = bundle_port;
+ ao2_link(portlist, port);
+ }
+ assoc = ao2_alloc(sizeof(*assoc) + strlen(item), NULL);
+ if (!assoc) {
+ return -1;
+ }
+
+ /* safe use of strcpy */
+ strcpy(assoc->tag, item);
+ assoc->port = bundle_port;
+ ao2_link(bundle_assoc_list, assoc);
+ }
+ }
+
+ /* validate all streams */
+ for (i = 0; i < sdp->media_count; ++i) {
+ RAII_VAR(int *, port, ao2_alloc(sizeof(int), NULL), ao2_cleanup);
+ RAII_VAR(int *, port_match, NULL, ao2_cleanup);
+ RAII_VAR(int *, bundle_match, NULL, ao2_cleanup);
+ *port = sdp->media[i]->desc.port;
+ port_match = ao2_find(portlist, port, OBJ_KEY);
+ if (port_match) {
+ RAII_VAR(struct bundle_assoc *, assoc, NULL, ao2_cleanup);
+ pjmedia_sdp_attr *mid = media_get_mid(sdp->media[i]);
+ char *mid_val;
+
+ if (!mid) {
+ /* not part of a bundle */
+ return -1;
+ }
+
+ mid_val = ast_alloca(mid->value.slen + 1);
+ strncpy(mid_val, mid->value.ptr, mid->value.slen);
+ mid_val[mid->value.slen] = '\0';
+
+ assoc = ao2_find(bundle_assoc_list, mid_val, OBJ_KEY);
+ if (!assoc || assoc->port != *port) {
+ /* This port already exists elsewhere in the SDP
+ * and is not an appropriate bundle port, fail
+ * catastrophically */
+ return -1;
+ }
+ }
+ ao2_link(portlist, port);
+ }
+ return 0;
+}
+
+static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *sdp)
+{
+ int i;
+ if (validate_incoming_sdp(sdp)) {
+ return -1;
+ }
+
+ for (i = 0; i < sdp->media_count; ++i) {
+ /* See if there are registered handlers for this media stream type */
+ char media[20];
+ struct ast_sip_session_sdp_handler *handler;
+ RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
+
+ /* We need a null-terminated version of the media string */
+ ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media));
+
+ handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);
+ if (!handler_list) {
+ ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);
+ continue;
+ }
+ AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
+ int res;
+ RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
+ session_media = ao2_find(session->media, handler_list->stream_type, OBJ_KEY);
+ if (!session_media || session_media->handler) {
+ /* There is only one slot for this stream type and it has already been claimed
+ * so it will go unhandled */
+ break;
+ }
+ res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, sdp->media[i]);
+ if (res < 0) {
+ /* Catastrophic failure. Abort! */
+ return -1;
+ }
+ if (res > 0) {
+ /* Handled by this handler. Move to the next stream */
+ session_media->handler = handler;
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+struct handle_negotiated_sdp_cb {
+ struct ast_sip_session *session;
+ const pjmedia_sdp_session *local;
+ const pjmedia_sdp_session *remote;
+};
+
+static int handle_negotiated_sdp_session_media(void *obj, void *arg, int flags)
+{
+ struct ast_sip_session_media *session_media = obj;
+ struct handle_negotiated_sdp_cb *callback_data = arg;
+ struct ast_sip_session *session = callback_data->session;
+ const pjmedia_sdp_session *local = callback_data->local;
+ const pjmedia_sdp_session *remote = callback_data->remote;
+ int i;
+
+ for (i = 0; i < local->media_count; ++i) {
+ /* See if there are registered handlers for this media stream type */
+ char media[20];
+ struct ast_sip_session_sdp_handler *handler;
+ RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
+
+ /* We need a null-terminated version of the media string */
+ ast_copy_pj_str(media, &local->media[i]->desc.media, sizeof(media));
+
+ /* stream type doesn't match the one we're looking to fill */
+ if (strcasecmp(session_media->stream_type, media)) {
+ continue;
+ }
+
+ handler = session_media->handler;
+ if (handler) {
+ int res = handler->apply_negotiated_sdp_stream(session, session_media, local, local->media[i], remote, remote->media[i]);
+ if (res >= 0) {
+ return CMP_MATCH;
+ }
+ return 0;
+ }
+
+ handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);
+ if (!handler_list) {
+ ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);
+ continue;
+ }
+ AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
+ int res = handler->apply_negotiated_sdp_stream(session, session_media, local, local->media[i], remote, remote->media[i]);
+ if (res < 0) {
+ /* Catastrophic failure. Abort! */
+ return 0;
+ }
+ if (res > 0) {
+ /* Handled by this handler. Move to the next stream */
+ session_media->handler = handler;
+ return CMP_MATCH;
+ }
+ }
+ }
+ return CMP_MATCH;
+}
+
+static int handle_negotiated_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *local, const pjmedia_sdp_session *remote)
+{
+ RAII_VAR(struct ao2_iterator *, successful, NULL, ao2_iterator_cleanup);
+ struct handle_negotiated_sdp_cb callback_data = {
+ .session = session,
+ .local = local,
+ .remote = remote,
+ };
+
+ successful = ao2_callback(session->media, OBJ_MULTIPLE, handle_negotiated_sdp_session_media, &callback_data);
+ if (successful && ao2_container_count(successful->c) == ao2_container_count(session->media)) {
+ /* Nothing experienced a catastrophic failure */
+ return 0;
+ }
+ return -1;
+}
+
+AST_RWLIST_HEAD_STATIC(session_supplements, ast_sip_session_supplement);
+
+int ast_sip_session_register_supplement(struct ast_sip_session_supplement *supplement)
+{
+ struct ast_sip_session_supplement *iter;
+ int inserted = 0;
+ SCOPED_LOCK(lock, &session_supplements, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&session_supplements, iter, next) {
+ if (iter->priority > supplement->priority) {
+ AST_RWLIST_INSERT_BEFORE_CURRENT(supplement, next);
+ inserted = 1;
+ break;
+ }
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+
+ if (!inserted) {
+ AST_RWLIST_INSERT_TAIL(&session_supplements, supplement, next);
+ }
+ ast_module_ref(ast_module_info->self);
+ return 0;
+}
+
+void ast_sip_session_unregister_supplement(struct ast_sip_session_supplement *supplement)
+{
+ struct ast_sip_session_supplement *iter;
+ SCOPED_LOCK(lock, &session_supplements, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&session_supplements, iter, next) {
+ if (supplement == iter) {
+ AST_RWLIST_REMOVE_CURRENT(next);
+ ast_module_unref(ast_module_info->self);
+ break;
+ }
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+}
+
+static struct ast_sip_session_supplement *supplement_dup(const struct ast_sip_session_supplement *src)
+{
+ struct ast_sip_session_supplement *dst = ast_calloc(1, sizeof(*dst));
+ if (!dst) {
+ return NULL;
+ }
+ /* Will need to revisit if shallow copy becomes an issue */
+ *dst = *src;
+ return dst;
+}
+
+#define DATASTORE_BUCKETS 53
+#define MEDIA_BUCKETS 7
+
+static void session_datastore_destroy(void *obj)
+{
+ struct ast_datastore *datastore = obj;
+
+ /* Using the destroy function (if present) destroy the data */
+ if (datastore->info->destroy != NULL && datastore->data != NULL) {
+ datastore->info->destroy(datastore->data);
+ datastore->data = NULL;
+ }
+
+ ast_free((void *) datastore->uid);
+ datastore->uid = NULL;
+}
+
+struct ast_datastore *ast_sip_session_alloc_datastore(const struct ast_datastore_info *info, const char *uid)
+{
+ RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
+ const char *uid_ptr = uid;
+
+ if (!info) {
+ return NULL;
+ }
+
+ datastore = ao2_alloc(sizeof(*datastore), session_datastore_destroy);
+ if (!datastore) {
+ return NULL;
+ }
+
+ datastore->info = info;
+ if (ast_strlen_zero(uid)) {
+ /* They didn't provide an ID so we'll provide one ourself */
+ struct ast_uuid *uuid = ast_uuid_generate();
+ char uuid_buf[AST_UUID_STR_LEN];
+ if (!uuid) {
+ return NULL;
+ }
+ uid_ptr = ast_uuid_to_str(uuid, uuid_buf, sizeof(uuid_buf));
+ ast_free(uuid);
+ }
+
+ datastore->uid = ast_strdup(uid_ptr);
+ if (!datastore->uid) {
+ return NULL;
+ }
+
+ ao2_ref(datastore, +1);
+ return datastore;
+}
+
+int ast_sip_session_add_datastore(struct ast_sip_session *session, struct ast_datastore *datastore)
+{
+ ast_assert(datastore != NULL);
+ ast_assert(datastore->info != NULL);
+ ast_assert(ast_strlen_zero(datastore->uid) == 0);
+
+ if (!ao2_link(session->datastores, datastore)) {
+ return -1;
+ }
+ return 0;
+}
+
+struct ast_datastore *ast_sip_session_get_datastore(struct ast_sip_session *session, const char *name)
+{
+ return ao2_find(session->datastores, name, OBJ_KEY);
+}
+
+void ast_sip_session_remove_datastore(struct ast_sip_session *session, const char *name)
+{
+ ao2_callback(session->datastores, OBJ_KEY | OBJ_UNLINK | OBJ_NODATA, NULL, (void *) name);
+}
+
+int ast_sip_session_get_identity(struct pjsip_rx_data *rdata, struct ast_party_id *id)
+{
+ /* XXX STUB
+ * This is low-priority as far as getting SIP working is concerned, so this
+ * will be addressed later.
+ *
+ * The idea here will be that the rdata will be examined for headers such as
+ * P-Asserted-Identity, Remote-Party-ID, and From in order to determine Identity
+ * information.
+ *
+ * For reference, Asterisk SCF code does something very similar to this, except in
+ * C++ and using its version of the ast_party_id struct, so using it as a basis
+ * would be a smart idea here.
+ */
+ return 0;
+}
+
+/*!
+ * \brief Structure used for sending delayed requests
+ *
+ * Requests are typically delayed because the current transaction
+ * state of an INVITE. Once the pending INVITE transaction terminates,
+ * the delayed request will be sent
+ */
+struct ast_sip_session_delayed_request {
+ /*! Method of the request */
+ char method[15];
+ /*! Callback to call when the delayed request is created. */
+ ast_sip_session_request_creation_cb on_request_creation;
+ /*! Callback to call when the delayed request receives a response */
+ ast_sip_session_response_cb on_response;
+ /*! Request to send */
+ pjsip_tx_data *tdata;
+ AST_LIST_ENTRY(ast_sip_session_delayed_request) next;
+};
+
+static struct ast_sip_session_delayed_request *delayed_request_alloc(const char *method,
+ ast_sip_session_request_creation_cb on_request_creation,
+ ast_sip_session_response_cb on_response,
+ pjsip_tx_data *tdata)
+{
+ struct ast_sip_session_delayed_request *delay = ast_calloc(1, sizeof(*delay));
+ if (!delay) {
+ return NULL;
+ }
+ ast_copy_string(delay->method, method, sizeof(delay->method));
+ delay->on_request_creation = on_request_creation;
+ delay->on_response = on_response;
+ delay->tdata = tdata;
+ return delay;
+}
+
+static int send_delayed_request(struct ast_sip_session *session, struct ast_sip_session_delayed_request *delay)
+{
+ ast_debug(3, "Sending delayed %s request to %s\n", delay->method, ast_sorcery_object_get_id(session->endpoint));
+
+ if (delay->tdata) {
+ ast_sip_session_send_request_with_cb(session, delay->tdata, delay->on_response);
+ return 0;
+ }
+
+ if (!strcmp(delay->method, "INVITE")) {
+ ast_sip_session_refresh(session, delay->on_request_creation,
+ delay->on_response, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);
+ } else if (!strcmp(delay->method, "UPDATE")) {
+ ast_sip_session_refresh(session, delay->on_request_creation,
+ delay->on_response, AST_SIP_SESSION_REFRESH_METHOD_UPDATE, 1);
+ } else {
+ ast_log(LOG_WARNING, "Unexpected delayed %s request with no existing request structure\n", delay->method);
+ return -1;
+ }
+ return 0;
+}
+
+static int queued_delayed_request_send(void *data)
+{
+ RAII_VAR(struct ast_sip_session *, session, data, ao2_cleanup);
+ RAII_VAR(struct ast_sip_session_delayed_request *, delay, NULL, ast_free_ptr);
+
+ delay = AST_LIST_REMOVE_HEAD(&session->delayed_requests, next);
+ if (!delay) {
+ return 0;
+ }
+
+ return send_delayed_request(session, delay);
+}
+
+static void queue_delayed_request(struct ast_sip_session *session)
+{
+ if (AST_LIST_EMPTY(&session->delayed_requests)) {
+ /* No delayed request to send, so just return */
+ return;
+ }
+
+ ast_debug(3, "Queuing delayed request to run for %s\n",
+ ast_sorcery_object_get_id(session->endpoint));
+
+ ao2_ref(session, +1);
+ ast_sip_push_task(session->serializer, queued_delayed_request_send, session);
+}
+
+static int delay_request(struct ast_sip_session *session, ast_sip_session_request_creation_cb on_request,
+ ast_sip_session_response_cb on_response, const char *method, pjsip_tx_data *tdata)
+{
+ struct ast_sip_session_delayed_request *delay = delayed_request_alloc(method,
+ on_request, on_response, tdata);
+
+ if (!delay) {
+ return -1;
+ }
+
+ AST_LIST_INSERT_TAIL(&session->delayed_requests, delay, next);
+ return 0;
+}
+
+static pjmedia_sdp_session *generate_session_refresh_sdp(struct ast_sip_session *session)
+{
+ pjsip_inv_session *inv_session = session->inv_session;
+ const pjmedia_sdp_session *previous_sdp;
+
+ if (pjmedia_sdp_neg_was_answer_remote(inv_session->neg)) {
+ pjmedia_sdp_neg_get_active_remote(inv_session->neg, &previous_sdp);
+ } else {
+ pjmedia_sdp_neg_get_active_local(inv_session->neg, &previous_sdp);
+ }
+ return create_local_sdp(inv_session, session, previous_sdp);
+}
+
+int ast_sip_session_refresh(struct ast_sip_session *session,
+ ast_sip_session_request_creation_cb on_request_creation, ast_sip_session_response_cb on_response,
+ enum ast_sip_session_refresh_method method, int generate_new_sdp)
+{
+ pjsip_inv_session *inv_session = session->inv_session;
+ pjmedia_sdp_session *new_sdp = NULL;
+ pjsip_tx_data *tdata;
+
+ if (inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
+ /* Don't try to do anything with a hung-up call */
+ ast_debug(3, "Not sending reinvite to %s because of disconnected state...\n",
+ ast_sorcery_object_get_id(session->endpoint));
+ return 0;
+ }
+
+ if (inv_session->invite_tsx) {
+ /* We can't send a reinvite yet, so delay it */
+ ast_debug(3, "Delaying sending reinvite to %s because of outstanding transaction...\n",
+ ast_sorcery_object_get_id(session->endpoint));
+ return delay_request(session, on_request_creation, on_response, "INVITE", NULL);
+ }
+
+ if (generate_new_sdp) {
+ new_sdp = generate_session_refresh_sdp(session);
+ if (!new_sdp) {
+ ast_log(LOG_ERROR, "Failed to generate session refresh SDP. Not sending session refresh\n");
+ return -1;
+ }
+ }
+
+ if (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) {
+ if (pjsip_inv_reinvite(inv_session, NULL, new_sdp, &tdata)) {
+ ast_log(LOG_WARNING, "Failed to create reinvite properly.\n");
+ return -1;
+ }
+ } else if (pjsip_inv_update(inv_session, NULL, new_sdp, &tdata)) {
+ ast_log(LOG_WARNING, "Failed to create UPDATE properly.\n");
+ return -1;
+ }
+ if (on_request_creation) {
+ if (on_request_creation(session, tdata)) {
+ return -1;
+ }
+ }
+ ast_sip_session_send_request_with_cb(session, tdata, on_response);
+ return 0;
+}
+
+void ast_sip_session_send_response(struct ast_sip_session *session, pjsip_tx_data *tdata)
+{
+ handle_outgoing_response(session, tdata);
+ pjsip_inv_send_msg(session->inv_session, tdata);
+ return;
+}
+
+static pj_status_t session_load(pjsip_endpoint *endpt);
+static pj_status_t session_start(void);
+static pj_status_t session_stop(void);
+static pj_status_t session_unload(void);
+static pj_bool_t session_on_rx_request(pjsip_rx_data *rdata);
+
+static pjsip_module session_module = {
+ .name = {"Session Module", 14},
+ .priority = PJSIP_MOD_PRIORITY_APPLICATION,
+ .load = session_load,
+ .unload = session_unload,
+ .start = session_start,
+ .stop = session_stop,
+ .on_rx_request = session_on_rx_request,
+};
+
+void ast_sip_session_send_request_with_cb(struct ast_sip_session *session, pjsip_tx_data *tdata,
+ ast_sip_session_response_cb on_response)
+{
+ pjsip_inv_session *inv_session = session->inv_session;
+
+ if (inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
+ /* Don't try to do anything with a hung-up call */
+ return;
+ }
+
+ tdata->mod_data[session_module.id] = on_response;
+ handle_outgoing_request(session, tdata);
+ pjsip_inv_send_msg(session->inv_session, tdata);
+ return;
+}
+
+void ast_sip_session_send_request(struct ast_sip_session *session, pjsip_tx_data *tdata)
+{
+ ast_sip_session_send_request_with_cb(session, tdata, NULL);
+}
+
+/*!
+ * \brief Called when the PJSIP core loads us
+ *
+ * Since we already have Asterisk's fine module load/unload framework
+ * in use, we don't need to do anything special here.
+ */
+static pj_status_t session_load(pjsip_endpoint *endpt)
+{
+ return PJ_SUCCESS;
+}
+
+/*!
+ * \brief Called when the PJSIP core starts us
+ *
+ * Since we already have Asterisk's fine module load/unload framework
+ * in use, we don't need to do anything special here.
+ */
+static pj_status_t session_start(void)
+{
+ return PJ_SUCCESS;
+}
+
+/*!
+ * \brief Called when the PJSIP core stops us
+ *
+ * Since we already have Asterisk's fine module load/unload framework
+ * in use, we don't need to do anything special here.
+ */
+static pj_status_t session_stop(void)
+{
+ return PJ_SUCCESS;
+}
+
+/*!
+ * \brief Called when the PJSIP core unloads us
+ *
+ * Since we already have Asterisk's fine module load/unload framework
+ * in use, we don't need to do anything special here.
+ */
+static pj_status_t session_unload(void)
+{
+ return PJ_SUCCESS;
+}
+
+static int datastore_hash(const void *obj, int flags)
+{
+ const struct ast_datastore *datastore = obj;
+ const char *uid = flags & OBJ_KEY ? obj : datastore->uid;
+
+ ast_assert(uid != NULL);
+
+ return ast_str_hash(uid);
+}
+
+static int datastore_cmp(void *obj, void *arg, int flags)
+{
+ const struct ast_datastore *datastore1 = obj;
+ const struct ast_datastore *datastore2 = arg;
+ const char *uid2 = flags & OBJ_KEY ? arg : datastore2->uid;
+
+ ast_assert(datastore1->uid != NULL);
+ ast_assert(uid2 != NULL);
+
+ return strcmp(datastore1->uid, uid2) ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+static void session_media_dtor(void *obj)
+{
+ struct ast_sip_session_media *session_media = obj;
+ if (session_media->handler) {
+ session_media->handler->stream_destroy(session_media);
+ }
+}
+
+static void session_destructor(void *obj)
+{
+ struct ast_sip_session *session = obj;
+ struct ast_sip_session_supplement *supplement;
+ struct ast_sip_session_delayed_request *delay;
+
+ ast_debug(3, "Destroying SIP session with endpoint %s\n",
+ ast_sorcery_object_get_id(session->endpoint));
+
+ while ((supplement = AST_LIST_REMOVE_HEAD(&session->supplements, next))) {
+ if (supplement->session_destroy) {
+ supplement->session_destroy(session);
+ }
+ ast_free(supplement);
+ }
+
+ ast_taskprocessor_unreference(session->serializer);
+ ao2_cleanup(session->datastores);
+ ao2_cleanup(session->media);
+ AST_LIST_HEAD_DESTROY(&session->supplements);
+ while ((delay = AST_LIST_REMOVE_HEAD(&session->delayed_requests, next))) {
+ ast_free(delay);
+ }
+ ast_party_id_free(&session->id);
+ ao2_cleanup(session->endpoint);
+ ast_format_cap_destroy(session->req_caps);
+}
+
+static int add_supplements(struct ast_sip_session *session)
+{
+ struct ast_sip_session_supplement *iter;
+ SCOPED_LOCK(lock, &session_supplements, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
+
+ AST_RWLIST_TRAVERSE(&session_supplements, iter, next) {
+ struct ast_sip_session_supplement *copy = supplement_dup(iter);
+ if (!copy) {
+ return -1;
+ }
+ AST_LIST_INSERT_TAIL(&session->supplements, copy, next);
+ }
+ return 0;
+}
+
+static int add_session_media(void *obj, void *arg, int flags)
+{
+ struct sdp_handler_list *handler_list = obj;
+ struct ast_sip_session * session = arg;
+ RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
+ session_media = ao2_alloc(sizeof(*session_media) + strlen(handler_list->stream_type), session_media_dtor);
+ if (!session_media) {
+ return CMP_STOP;
+ }
+ /* Safe use of strcpy */
+ strcpy(session_media->stream_type, handler_list->stream_type);
+ ao2_link(session->media, session_media);
+ return 0;
+}
+
+struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint, pjsip_inv_session *inv_session)
+{
+ RAII_VAR(struct ast_sip_session *, session, ao2_alloc(sizeof(*session), session_destructor), ao2_cleanup);
+ struct ast_sip_session_supplement *iter;
+ if (!session) {
+ return NULL;
+ }
+ AST_LIST_HEAD_INIT(&session->supplements);
+ session->datastores = ao2_container_alloc(DATASTORE_BUCKETS, datastore_hash, datastore_cmp);
+ if (!session->datastores) {
+ return NULL;
+ }
+
+ session->media = ao2_container_alloc(MEDIA_BUCKETS, session_media_hash, session_media_cmp);
+ if (!session->media) {
+ return NULL;
+ }
+ /* fill session->media with available types */
+ ao2_callback(sdp_handlers, OBJ_NODATA, add_session_media, session);
+
+ session->serializer = ast_sip_create_serializer();
+ if (!session->serializer) {
+ return NULL;
+ }
+ ast_sip_dialog_set_serializer(inv_session->dlg, session->serializer);
+ ast_sip_dialog_set_endpoint(inv_session->dlg, endpoint);
+ ao2_ref(endpoint, +1);
+ inv_session->mod_data[session_module.id] = session;
+ session->endpoint = endpoint;
+ session->inv_session = inv_session;
+ session->req_caps = ast_format_cap_alloc_nolock();
+
+ if (add_supplements(session)) {
+ return NULL;
+ }
+ AST_LIST_TRAVERSE(&session->supplements, iter, next) {
+ if (iter->session_begin) {
+ iter->session_begin(session);
+ }
+ }
+ session->direct_media_cap = ast_format_cap_alloc_nolock();
+ AST_LIST_HEAD_INIT_NOLOCK(&session->delayed_requests);
+ ast_party_id_init(&session->id);
+ ao2_ref(session, +1);
+ return session;
+}
+
+static int session_outbound_auth(pjsip_dialog *dlg, pjsip_tx_data *tdata, void *user_data)
+{
+ pjsip_inv_session *inv = pjsip_dlg_get_inv_session(dlg);
+ struct ast_sip_session *session = inv->mod_data[session_module.id];
+
+ if (inv->state < PJSIP_INV_STATE_CONFIRMED && tdata->msg->line.req.method.id == PJSIP_INVITE_METHOD) {
+ pjsip_inv_uac_restart(inv, PJ_TRUE);
+ }
+ ast_sip_session_send_request(session, tdata);
+ return 0;
+}
+
+struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint *endpoint, const char *location, const char *request_user, struct ast_format_cap *req_caps)
+{
+ const char *uri = NULL;
+ RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
+ pjsip_timer_setting timer;
+ pjsip_dialog *dlg;
+ struct pjsip_inv_session *inv_session;
+ RAII_VAR(struct ast_sip_session *, session, NULL, ao2_cleanup);
+ pjmedia_sdp_session *offer;
+
+ /* If no location has been provided use the AOR list from the endpoint itself */
+ location = S_OR(location, endpoint->aors);
+
+ contact = ast_sip_location_retrieve_contact_from_aor_list(location);
+ if (!contact || ast_strlen_zero(contact->uri)) {
+ uri = location;
+ } else {
+ uri = contact->uri;
+ }
+
+ /* If we still have no URI to dial fail to create the session */
+ if (ast_strlen_zero(uri)) {
+ return NULL;
+ }
+
+ if (!(dlg = ast_sip_create_dialog(endpoint, uri, request_user))) {
+ return NULL;
+ }
+
+ if (ast_sip_dialog_setup_outbound_authentication(dlg, endpoint, session_outbound_auth, NULL)) {
+ pjsip_dlg_terminate(dlg);
+ return NULL;
+ }
+
+ if (pjsip_inv_create_uac(dlg, NULL, endpoint->extensions, &inv_session) != PJ_SUCCESS) {
+ pjsip_dlg_terminate(dlg);
+ return NULL;
+ }
+
+ pjsip_timer_setting_default(&timer);
+ timer.min_se = endpoint->min_se;
+ timer.sess_expires = endpoint->sess_expires;
+ pjsip_timer_init_session(inv_session, &timer);
+
+ if (!(session = ast_sip_session_alloc(endpoint, inv_session))) {
+ pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
+ return NULL;
+ }
+
+ ast_format_cap_copy(session->req_caps, req_caps);
+ if ((pjsip_dlg_add_usage(dlg, &session_module, NULL) != PJ_SUCCESS) ||
+ !(offer = create_local_sdp(inv_session, session, NULL))) {
+ pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
+ return NULL;
+ }
+
+ pjsip_inv_set_local_sdp(inv_session, offer);
+
+ ao2_ref(session, +1);
+ return session;
+}
+
+enum sip_get_destination_result {
+ /*! The extension was successfully found */
+ SIP_GET_DEST_EXTEN_FOUND,
+ /*! The extension specified in the RURI was not found */
+ SIP_GET_DEST_EXTEN_NOT_FOUND,
+ /*! The extension specified in the RURI was a partial match */
+ SIP_GET_DEST_EXTEN_PARTIAL,
+ /*! The RURI is of an unsupported scheme */
+ SIP_GET_DEST_UNSUPPORTED_URI,
+};
+
+/*!
+ * \brief Determine where in the dialplan a call should go
+ *
+ * This uses the username in the request URI to try to match
+ * an extension in the endpoint's configured context in order
+ * to route the call.
+ *
+ * \param session The inbound SIP session
+ * \param rdata The SIP INVITE
+ */
+static enum sip_get_destination_result get_destination(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+ pjsip_uri *ruri = rdata->msg_info.msg->line.req.uri;
+ pjsip_sip_uri *sip_ruri;
+ if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) {
+ return SIP_GET_DEST_UNSUPPORTED_URI;
+ }
+ sip_ruri = pjsip_uri_get_uri(ruri);
+ ast_copy_pj_str(session->exten, &sip_ruri->user, sizeof(session->exten));
+ if (ast_exists_extension(NULL, session->endpoint->context, session->exten, 1, NULL)) {
+ return SIP_GET_DEST_EXTEN_FOUND;
+ }
+ /* XXX In reality, we'll likely have further options so that partial matches
+ * can be indicated here, but for getting something up and running, we're going
+ * to return a "not exists" error here.
+ */
+ return SIP_GET_DEST_EXTEN_NOT_FOUND;
+}
+
+static pjsip_inv_session *pre_session_setup(pjsip_rx_data *rdata, const struct ast_sip_endpoint *endpoint)
+{
+ pjsip_tx_data *tdata;
+ pjsip_dialog *dlg;
+ pjsip_inv_session *inv_session;
+ unsigned int options = endpoint->extensions;
+
+ if (pjsip_inv_verify_request(rdata, &options, NULL, NULL, ast_sip_get_pjsip_endpoint(), &tdata) != PJ_SUCCESS) {
+ if (tdata) {
+ pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL);
+ } else {
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
+ }
+ return NULL;
+ }
+ if (pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, NULL, &dlg) != PJ_SUCCESS) {
+ pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL);
+ return NULL;
+ }
+ if (pjsip_inv_create_uas(dlg, rdata, NULL, 0, &inv_session) != PJ_SUCCESS) {
+ pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL);
+ pjsip_dlg_terminate(dlg);
+ return NULL;
+ }
+ if (pjsip_dlg_add_usage(dlg, &session_module, NULL) != PJ_SUCCESS) {
+ if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) != PJ_SUCCESS) {
+ pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
+ }
+ pjsip_inv_send_msg(inv_session, tdata);
+ return NULL;
+ }
+ return inv_session;
+}
+
+static void handle_new_invite_request(pjsip_rx_data *rdata)
+{
+ RAII_VAR(struct ast_sip_endpoint *, endpoint,
+ ast_pjsip_rdata_get_endpoint(rdata), ao2_cleanup);
+ pjsip_tx_data *tdata = NULL;
+ pjsip_inv_session *inv_session = NULL;
+ struct ast_sip_session *session = NULL;
+ pjsip_timer_setting timer;
+ pjsip_rdata_sdp_info *sdp_info;
+ pjmedia_sdp_session *local = NULL;
+
+ ast_assert(endpoint != NULL);
+
+ inv_session = pre_session_setup(rdata, endpoint);
+ if (!inv_session) {
+ /* pre_session_setup() returns a response on failure */
+ return;
+ }
+
+ session = ast_sip_session_alloc(endpoint, inv_session);
+ if (!session) {
+ if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) {
+ pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
+ } else {
+ pjsip_inv_send_msg(inv_session, tdata);
+ }
+ return;
+ }
+
+ /* From this point on, any calls to pjsip_inv_terminate have the last argument as PJ_TRUE
+ * so that we will be notified so we can destroy the session properly
+ */
+
+ switch (get_destination(session, rdata)) {
+ case SIP_GET_DEST_EXTEN_FOUND:
+ /* Things worked. Keep going */
+ break;
+ case SIP_GET_DEST_UNSUPPORTED_URI:
+ if (pjsip_inv_initial_answer(inv_session, rdata, 416, NULL, NULL, &tdata) == PJ_SUCCESS) {
+ ast_sip_session_send_response(session, tdata);
+ } else {
+ pjsip_inv_terminate(inv_session, 416, PJ_TRUE);
+ }
+ return;
+ case SIP_GET_DEST_EXTEN_NOT_FOUND:
+ case SIP_GET_DEST_EXTEN_PARTIAL:
+ default:
+ if (pjsip_inv_initial_answer(inv_session, rdata, 404, NULL, NULL, &tdata) == PJ_SUCCESS) {
+ ast_sip_session_send_response(session, tdata);
+ } else {
+ pjsip_inv_terminate(inv_session, 404, PJ_TRUE);
+ }
+ return;
+ };
+
+ if ((sdp_info = pjsip_rdata_get_sdp_info(rdata)) && (sdp_info->sdp_err == PJ_SUCCESS) && sdp_info->sdp) {
+ if (handle_incoming_sdp(session, sdp_info->sdp)) {
+ if (pjsip_inv_initial_answer(inv_session, rdata, 488, NULL, NULL, &tdata) == PJ_SUCCESS) {
+ ast_sip_session_send_response(session, tdata);
+ } else {
+ pjsip_inv_terminate(inv_session, 488, PJ_TRUE);
+ }
+ return;
+ }
+ /* We are creating a local SDP which is an answer to their offer */
+ local = create_local_sdp(inv_session, session, sdp_info->sdp);
+ } else {
+ /* We are creating a local SDP which is an offer */
+ local = create_local_sdp(inv_session, session, NULL);
+ }
+
+ /* If we were unable to create a local SDP terminate the session early, it won't go anywhere */
+ if (!local) {
+ if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) {
+ ast_sip_session_send_response(session, tdata);
+ } else {
+ pjsip_inv_terminate(inv_session, 500, PJ_TRUE);
+ }
+ return;
+ } else {
+ pjsip_inv_set_local_sdp(inv_session, local);
+ }
+
+ pjsip_timer_setting_default(&timer);
+ timer.min_se = endpoint->min_se;
+ timer.sess_expires = endpoint->sess_expires;
+ pjsip_timer_init_session(inv_session, &timer);
+
+ /* At this point, we've verified what we can, so let's go ahead and send a 100 Trying out */
+ if (pjsip_inv_initial_answer(inv_session, rdata, 100, NULL, NULL, &tdata) != PJ_SUCCESS) {
+ pjsip_inv_terminate(inv_session, 500, PJ_TRUE);
+ return;
+ }
+ ast_sip_session_send_response(session, tdata);
+
+ handle_incoming_request(session, rdata);
+}
+
+static int has_supplement(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+ struct ast_sip_session_supplement *supplement;
+ struct pjsip_method *method = &rdata->msg_info.msg->line.req.method;
+
+ if (!session) {
+ return PJ_FALSE;
+ }
+
+ AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
+ if (!supplement->method || !pj_strcmp2(&method->name, supplement->method)) {
+ return PJ_TRUE;
+ }
+ }
+ return PJ_FALSE;
+}
+/*!
+ * \brief Called when a new SIP request comes into PJSIP
+ *
+ * This function is called under two circumstances
+ * 1) An out-of-dialog request is received by PJSIP
+ * 2) An in-dialog request that the inv_session layer does not
+ * handle is received (such as an in-dialog INFO)
+ *
+ * In all cases, there is very little we actually do in this function
+ * 1) For requests we don't handle, we return PJ_FALSE
+ * 2) For new INVITEs, throw the work into the SIP threadpool to be done
+ * there to free up the thread(s) handling incoming requests
+ * 3) For in-dialog requests we handle, we defer handling them until the
+ * on_inv_state_change() callback instead (where we will end up putting
+ * them into the threadpool).
+ */
+static pj_bool_t session_on_rx_request(pjsip_rx_data *rdata)
+{
+ pj_status_t handled = PJ_FALSE;
+ pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
+ pjsip_inv_session *inv_session;
+
+ switch (rdata->msg_info.msg->line.req.method.id) {
+ case PJSIP_INVITE_METHOD:
+ if (dlg) {
+ ast_log(LOG_WARNING, "on_rx_request called for INVITE in mid-dialog?\n");
+ break;
+ }
+ handled = PJ_TRUE;
+ handle_new_invite_request(rdata);
+ break;
+ default:
+ /* Handle other in-dialog methods if their supplements have been registered */
+ handled = dlg && (inv_session = pjsip_dlg_get_inv_session(dlg)) &&
+ has_supplement(inv_session->mod_data[session_module.id], rdata);
+ break;
+ }
+
+ return handled;
+}
+
+struct reschedule_reinvite_data {
+ struct ast_sip_session *session;
+ struct ast_sip_session_delayed_request *delay;
+};
+
+static struct reschedule_reinvite_data *reschedule_reinvite_data_alloc(
+ struct ast_sip_session *session, struct ast_sip_session_delayed_request *delay)
+{
+ struct reschedule_reinvite_data *rrd = ast_malloc(sizeof(*rrd));
+ if (!rrd) {
+ return NULL;
+ }
+ ao2_ref(session, +1);
+ rrd->session = session;
+ rrd->delay = delay;
+ return rrd;
+}
+
+static void reschedule_reinvite_data_destroy(struct reschedule_reinvite_data *rrd)
+{
+ ao2_cleanup(rrd->session);
+ ast_free(rrd->delay);
+ ast_free(rrd);
+}
+
+static int really_resend_reinvite(void *data)
+{
+ RAII_VAR(struct reschedule_reinvite_data *, rrd, data, reschedule_reinvite_data_destroy);
+
+ return send_delayed_request(rrd->session, rrd->delay);
+}
+
+static void resend_reinvite(pj_timer_heap_t *timer, pj_timer_entry *entry)
+{
+ struct reschedule_reinvite_data *rrd = entry->user_data;
+
+ ast_sip_push_task(rrd->session->serializer, really_resend_reinvite, entry->user_data);
+}
+
+static void reschedule_reinvite(struct ast_sip_session *session, ast_sip_session_response_cb on_response, pjsip_tx_data *tdata)
+{
+ struct ast_sip_session_delayed_request *delay = delayed_request_alloc("INVITE",
+ NULL, on_response, tdata);
+ pjsip_inv_session *inv = session->inv_session;
+ struct reschedule_reinvite_data *rrd = reschedule_reinvite_data_alloc(session, delay);
+ pj_time_val tv;
+
+ if (!rrd || !delay) {
+ return;
+ }
+
+ tv.sec = 0;
+ if (inv->role == PJSIP_ROLE_UAC) {
+ tv.msec = 2100 + ast_random() % 2000;
+ } else {
+ tv.msec = ast_random() % 2000;
+ }
+
+ pj_timer_entry_init(&session->rescheduled_reinvite, 0, rrd, resend_reinvite);
+
+ pjsip_endpt_schedule_timer(ast_sip_get_pjsip_endpoint(), &session->rescheduled_reinvite, &tv);
+}
+
+static void __print_debug_details(const char *function, pjsip_inv_session *inv, pjsip_transaction *tsx, pjsip_event *e)
+{
+ struct ast_sip_session *session;
+ ast_debug(5, "Function %s called on event %s\n", function, pjsip_event_str(e->type));
+ if (!inv) {
+ ast_debug(5, "Transaction %p does not belong to an inv_session?\n", tsx);
+ ast_debug(5, "The transaction state is %s\n", pjsip_tsx_state_str(tsx->state));
+ return;
+ }
+ session = inv->mod_data[session_module.id];
+ if (!session) {
+ ast_debug(5, "inv_session %p has no ast session\n", inv);
+ } else {
+ ast_debug(5, "The state change pertains to the session with %s\n",
+ ast_sorcery_object_get_id(session->endpoint));
+ }
+ if (inv->invite_tsx) {
+ ast_debug(5, "The inv session still has an invite_tsx (%p)\n", inv->invite_tsx);
+ } else {
+ ast_debug(5, "The inv session does NOT have an invite_tsx\n");
+ }
+ if (tsx) {
+ ast_debug(5, "The transaction involved in this state change is %p\n", tsx);
+ ast_debug(5, "The current transaction state is %s\n", pjsip_tsx_state_str(tsx->state));
+ ast_debug(5, "The transaction state change event is %s\n", pjsip_event_str(e->body.tsx_state.type));
+ } else {
+ ast_debug(5, "There is no transaction involved in this state change\n");
+ }
+ ast_debug(5, "The current inv state is %s\n", pjsip_inv_state_name(inv->state));
+}
+
+#define print_debug_details(inv, tsx, e) __print_debug_details(__PRETTY_FUNCTION__, (inv), (tsx), (e))
+
+static void handle_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+ struct ast_sip_session_supplement *supplement;
+ struct pjsip_request_line req = rdata->msg_info.msg->line.req;
+
+ ast_debug(3, "Method is %.*s\n", (int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name));
+ AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
+ if (supplement->incoming_request && (
+ !supplement->method || !pj_strcmp2(&req.method.name, supplement->method))) {
+ supplement->incoming_request(session, rdata);
+ }
+ }
+}
+
+static void handle_incoming_response(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+ struct ast_sip_session_supplement *supplement;
+ struct pjsip_status_line status = rdata->msg_info.msg->line.status;
+
+ ast_debug(3, "Response is %d %.*s\n", status.code, (int) pj_strlen(&status.reason),
+ pj_strbuf(&status.reason));
+
+ AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
+ if (supplement->incoming_response && (
+ !supplement->method || !pj_strcmp2(&rdata->msg_info.cseq->method.name, supplement->method))) {
+ supplement->incoming_response(session, rdata);
+ }
+ }
+}
+
+static int handle_incoming(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+ ast_debug(3, "Received %s\n", rdata->msg_info.msg->type == PJSIP_REQUEST_MSG ?
+ "request" : "response");
+
+ if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) {
+ handle_incoming_request(session, rdata);
+ } else {
+ handle_incoming_response(session, rdata);
+ }
+
+ return 0;
+}
+
+static void handle_outgoing_request(struct ast_sip_session *session, pjsip_tx_data *tdata)
+{
+ struct ast_sip_session_supplement *supplement;
+ struct pjsip_request_line req = tdata->msg->line.req;
+
+ ast_debug(3, "Method is %.*s\n", (int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name));
+ AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
+ if (supplement->outgoing_request &&
+ (!supplement->method || !pj_strcmp2(&req.method.name, supplement->method))) {
+ supplement->outgoing_request(session, tdata);
+ }
+ }
+}
+
+static void handle_outgoing_response(struct ast_sip_session *session, pjsip_tx_data *tdata)
+{
+ struct ast_sip_session_supplement *supplement;
+ struct pjsip_status_line status = tdata->msg->line.status;
+ ast_debug(3, "Response is %d %.*s\n", status.code, (int) pj_strlen(&status.reason),
+ pj_strbuf(&status.reason));
+
+ AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
+ /* XXX Not sure how to get the method from a response.
+ * For now, just call supplements on all responses, no
+ * matter the method. This is less than ideal
+ */
+ if (supplement->outgoing_response) {
+ supplement->outgoing_response(session, tdata);
+ }
+ }
+}
+
+static void handle_outgoing(struct ast_sip_session *session, pjsip_tx_data *tdata)
+{
+ ast_debug(3, "Sending %s\n", tdata->msg->type == PJSIP_REQUEST_MSG ?
+ "request" : "response");
+ if (tdata->msg->type == PJSIP_REQUEST_MSG) {
+ handle_outgoing_request(session, tdata);
+ } else {
+ handle_outgoing_response(session, tdata);
+ }
+}
+
+static int session_end(struct ast_sip_session *session)
+{
+ struct ast_sip_session_supplement *iter;
+
+ /* Session is dead. Let's get rid of the reference to the session */
+ AST_LIST_TRAVERSE(&session->supplements, iter, next) {
+ if (iter->session_end) {
+ iter->session_end(session);
+ }
+ }
+
+ session->inv_session->mod_data[session_module.id] = NULL;
+ ast_sip_dialog_set_serializer(session->inv_session->dlg, NULL);
+ ast_sip_dialog_set_endpoint(session->inv_session->dlg, NULL);
+ ao2_cleanup(session);
+ return 0;
+}
+
+static void session_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
+{
+ struct ast_sip_session *session = inv->mod_data[session_module.id];
+
+ print_debug_details(inv, NULL, e);
+
+ switch(e->type) {
+ case PJSIP_EVENT_TX_MSG:
+ handle_outgoing(session, e->body.tx_msg.tdata);
+ break;
+ case PJSIP_EVENT_RX_MSG:
+ handle_incoming(session, e->body.rx_msg.rdata);
+ break;
+ case PJSIP_EVENT_TSX_STATE:
+ ast_debug(3, "Source of transaction state change is %s\n", pjsip_event_str(e->body.tsx_state.type));
+ /* Transaction state changes are prompted by some other underlying event. */
+ switch(e->body.tsx_state.type) {
+ case PJSIP_EVENT_TX_MSG:
+ handle_outgoing(session, e->body.tsx_state.src.tdata);
+ break;
+ case PJSIP_EVENT_RX_MSG:
+ handle_incoming(session, e->body.tsx_state.src.rdata);
+ break;
+ case PJSIP_EVENT_TRANSPORT_ERROR:
+ case PJSIP_EVENT_TIMER:
+ case PJSIP_EVENT_USER:
+ case PJSIP_EVENT_UNKNOWN:
+ case PJSIP_EVENT_TSX_STATE:
+ /* Inception? */
+ break;
+ }
+ break;
+ case PJSIP_EVENT_TRANSPORT_ERROR:
+ case PJSIP_EVENT_TIMER:
+ case PJSIP_EVENT_UNKNOWN:
+ case PJSIP_EVENT_USER:
+ default:
+ break;
+ }
+
+ if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+ session_end(session);
+ }
+}
+
+static void session_inv_on_new_session(pjsip_inv_session *inv, pjsip_event *e)
+{
+ /* XXX STUB */
+}
+
+static void session_inv_on_tsx_state_changed(pjsip_inv_session *inv, pjsip_transaction *tsx, pjsip_event *e)
+{
+ struct ast_sip_session *session = inv->mod_data[session_module.id];
+ print_debug_details(inv, tsx, e);
+ if (!session) {
+ /* Transaction likely timed out after the call was hung up. Just
+ * ignore such transaction changes
+ */
+ return;
+ }
+ switch (e->body.tsx_state.type) {
+ case PJSIP_EVENT_TX_MSG:
+ /* When we create an outgoing request, we do not have access to the transaction that
+ * is created. Instead, We have to place transaction-specific data in the tdata. Here,
+ * we transfer the data into the transaction. This way, when we receive a response, we
+ * can dig this data out again
+ */
+ tsx->mod_data[session_module.id] = e->body.tsx_state.src.tdata->mod_data[session_module.id];
+ break;
+ case PJSIP_EVENT_RX_MSG:
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ if (tsx->role == PJSIP_ROLE_UAC && tsx->state == PJSIP_TSX_STATE_COMPLETED) {
+ /* This means we got a non 2XX final response to our outgoing INVITE */
+ if (tsx->status_code == PJSIP_SC_REQUEST_PENDING) {
+ reschedule_reinvite(session, tsx->mod_data[session_module.id], tsx->last_tx);
+ return;
+ } else {
+ /* Other failures result in destroying the session. */
+ pjsip_tx_data *tdata;
+ pjsip_inv_end_session(inv, 500, NULL, &tdata);
+ ast_sip_session_send_request(session, tdata);
+ }
+ }
+ } else {
+ if (tsx->role == PJSIP_ROLE_UAS && tsx->state == PJSIP_TSX_STATE_TRYING) {
+ handle_incoming_request(session, e->body.tsx_state.src.rdata);
+ }
+ }
+ if (tsx->mod_data[session_module.id]) {
+ ast_sip_session_response_cb cb = tsx->mod_data[session_module.id];
+ cb(session, e->body.tsx_state.src.rdata);
+ }
+ case PJSIP_EVENT_TRANSPORT_ERROR:
+ case PJSIP_EVENT_TIMER:
+ case PJSIP_EVENT_USER:
+ case PJSIP_EVENT_UNKNOWN:
+ case PJSIP_EVENT_TSX_STATE:
+ /* Inception? */
+ break;
+ }
+
+ /* Terminated INVITE transactions always should result in queuing delayed requests,
+ * no matter what event caused the transaction to terminate
+ */
+ if (tsx->method.id == PJSIP_INVITE_METHOD && tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+ queue_delayed_request(session);
+ }
+}
+
+static int add_sdp_streams(void *obj, void *arg, void *data, int flags)
+{
+ struct ast_sip_session_media *session_media = obj;
+ pjmedia_sdp_session *answer = arg;
+ struct ast_sip_session *session = data;
+ struct ast_sip_session_sdp_handler *handler = session_media->handler;
+ RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
+
+ if (handler) {
+ /* if an already assigned handler does not handle the session_media or reports a catastrophic error, fail */
+ if (handler->create_outgoing_sdp_stream(session, session_media, answer) <= 0) {
+ return 0;
+ }
+ return CMP_MATCH;
+ }
+
+ handler_list = ao2_find(sdp_handlers, session_media->stream_type, OBJ_KEY);
+ if (!handler_list) {
+ return CMP_MATCH;
+ }
+
+ /* no handler for this stream type and we have a list to search */
+ AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
+ int res = handler->create_outgoing_sdp_stream(session, session_media, answer);
+ if (res < 0) {
+ /* catastrophic error */
+ return 0;
+ }
+ if (res > 0) {
+ /* handled */
+ return CMP_MATCH;
+ }
+ }
+
+ /* streams that weren't handled won't be included in generated outbound SDP */
+ return CMP_MATCH;
+}
+
+static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, struct ast_sip_session *session, const pjmedia_sdp_session *offer)
+{
+ RAII_VAR(struct ao2_iterator *, successful, NULL, ao2_iterator_cleanup);
+ static const pj_str_t STR_ASTERISK = { "Asterisk", 8 };
+ static const pj_str_t STR_IN = { "IN", 2 };
+ static const pj_str_t STR_IP4 = { "IP4", 3 };
+ static const pj_str_t STR_IP6 = { "IP6", 3 };
+ pjmedia_sdp_session *local;
+
+ if (!(local = PJ_POOL_ZALLOC_T(inv->pool_prov, pjmedia_sdp_session))) {
+ return NULL;
+ }
+
+ if (!offer) {
+ local->origin.version = local->origin.id = (pj_uint32_t)(ast_random());
+ } else {
+ local->origin.version = offer->origin.version + 1;
+ local->origin.id = offer->origin.id;
+ }
+
+ local->origin.user = STR_ASTERISK;
+ local->origin.net_type = STR_IN;
+ local->origin.addr_type = session->endpoint->rtp_ipv6 ? STR_IP6 : STR_IP4;
+ local->origin.addr = *pj_gethostname();
+ local->name = local->origin.user;
+
+ /* Now let the handlers add streams of various types, pjmedia will automatically reorder the media streams for us */
+ successful = ao2_callback_data(session->media, OBJ_MULTIPLE, add_sdp_streams, local, session);
+ if (!successful || ao2_container_count(successful->c) != ao2_container_count(session->media)) {
+ /* Something experienced a catastrophic failure */
+ return NULL;
+ }
+
+ /* Use the connection details of the first media stream if possible for SDP level */
+ if (local->media_count) {
+ local->conn = local->media[0]->conn;
+ }
+
+ return local;
+}
+
+static void session_inv_on_rx_offer(pjsip_inv_session *inv, const pjmedia_sdp_session *offer)
+{
+ struct ast_sip_session *session = inv->mod_data[session_module.id];
+ pjmedia_sdp_session *answer;
+
+ if (handle_incoming_sdp(session, offer)) {
+ return;
+ }
+
+ if ((answer = create_local_sdp(inv, session, offer))) {
+ pjsip_inv_set_sdp_answer(inv, answer);
+ }
+}
+
+#if 0
+static void session_inv_on_create_offer(pjsip_inv_session *inv, pjmedia_sdp_session **p_offer)
+{
+ /* XXX STUB */
+}
+#endif
+
+static void session_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status)
+{
+ struct ast_sip_session *session = inv->mod_data[session_module.id];
+ const pjmedia_sdp_session *local, *remote;
+
+ if (!session->channel) {
+ /* If we don't have a channel. We really don't care about media updates.
+ * Just ignore
+ */
+ return;
+ }
+
+ if ((status != PJ_SUCCESS) || (pjmedia_sdp_neg_get_active_local(inv->neg, &local) != PJ_SUCCESS) ||
+ (pjmedia_sdp_neg_get_active_remote(inv->neg, &remote) != PJ_SUCCESS)) {
+ ast_channel_hangupcause_set(session->channel, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL);
+ ast_queue_hangup(session->channel);
+ return;
+ }
+
+ handle_negotiated_sdp(session, local, remote);
+}
+
+static pjsip_redirect_op session_inv_on_redirected(pjsip_inv_session *inv, const pjsip_uri *target, const pjsip_event *e)
+{
+ /* XXX STUB */
+ return PJSIP_REDIRECT_REJECT;
+}
+
+static pjsip_inv_callback inv_callback = {
+ .on_state_changed = session_inv_on_state_changed,
+ .on_new_session = session_inv_on_new_session,
+ .on_tsx_state_changed = session_inv_on_tsx_state_changed,
+ .on_rx_offer = session_inv_on_rx_offer,
+ .on_media_update = session_inv_on_media_update,
+ .on_redirected = session_inv_on_redirected,
+};
+
+/*! \brief Hook for modifying outgoing messages with SDP to contain the proper address information */
+static void session_outgoing_nat_hook(pjsip_tx_data *tdata, struct ast_sip_transport *transport)
+{
+ struct ast_sip_nat_hook *hook = tdata->mod_data[session_module.id];
+ struct pjmedia_sdp_session *sdp;
+ int stream;
+
+ /* SDP produced by us directly will never be multipart */
+ if (hook || !tdata->msg->body || pj_stricmp2(&tdata->msg->body->content_type.type, "application") ||
+ pj_stricmp2(&tdata->msg->body->content_type.subtype, "sdp") || ast_strlen_zero(transport->external_media_address)) {
+ return;
+ }
+
+ sdp = tdata->msg->body->data;
+
+ for (stream = 0; stream < sdp->media_count; ++stream) {
+ /* See if there are registered handlers for this media stream type */
+ char media[20];
+ struct ast_sip_session_sdp_handler *handler;
+ RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
+
+ /* We need a null-terminated version of the media string */
+ ast_copy_pj_str(media, &sdp->media[stream]->desc.media, sizeof(media));
+
+ handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);
+ if (!handler_list) {
+ ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);
+ continue;
+ }
+ AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
+ if (handler->change_outgoing_sdp_stream_media_address) {
+ handler->change_outgoing_sdp_stream_media_address(tdata, sdp->media[stream], transport);
+ }
+ }
+ }
+
+ /* We purposely do this so that the hook will not be invoked multiple times, ie: if a retransmit occurs */
+ tdata->mod_data[session_module.id] = nat_hook;
+}
+
+static int load_module(void)
+{
+ pjsip_endpoint *endpt;
+ if (!ast_sip_get_sorcery() || !ast_sip_get_pjsip_endpoint()) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ if (!(nat_hook = ast_sorcery_alloc(ast_sip_get_sorcery(), "nat_hook", NULL))) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ nat_hook->outgoing_external_message = session_outgoing_nat_hook;
+ ast_sorcery_create(ast_sip_get_sorcery(), nat_hook);
+ sdp_handlers = ao2_container_alloc(SDP_HANDLER_BUCKETS,
+ sdp_handler_list_hash, sdp_handler_list_cmp);
+ if (!sdp_handlers) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ endpt = ast_sip_get_pjsip_endpoint();
+ pjsip_inv_usage_init(endpt, &inv_callback);
+ pjsip_100rel_init_module(endpt);
+ pjsip_timer_init_module(endpt);
+ if (ast_sip_register_service(&session_module)) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ ast_sip_unregister_service(&session_module);
+ if (nat_hook) {
+ ast_sorcery_delete(ast_sip_get_sorcery(), nat_hook);
+ nat_hook = NULL;
+ }
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "SIP Session resource",
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_APP_DEPEND,
+ );
diff --git a/res/res_sip_session.exports.in b/res/res_sip_session.exports.in
new file mode 100644
index 000000000..08c6f3937
--- /dev/null
+++ b/res/res_sip_session.exports.in
@@ -0,0 +1,18 @@
+{
+ global:
+ LINKER_SYMBOL_PREFIXast_sip_session_register_sdp_handler;
+ LINKER_SYMBOL_PREFIXast_sip_session_unregister_sdp_handler;
+ LINKER_SYMBOL_PREFIXast_sip_session_register_supplement;
+ LINKER_SYMBOL_PREFIXast_sip_session_unregister_supplement;
+ LINKER_SYMBOL_PREFIXast_sip_session_alloc_datastore;
+ LINKER_SYMBOL_PREFIXast_sip_session_add_datastore;
+ LINKER_SYMBOL_PREFIXast_sip_session_get_datastore;
+ LINKER_SYMBOL_PREFIXast_sip_session_remove_datastore;
+ LINKER_SYMBOL_PREFIXast_sip_session_get_identity;
+ LINKER_SYMBOL_PREFIXast_sip_session_refresh;
+ LINKER_SYMBOL_PREFIXast_sip_session_send_response;
+ LINKER_SYMBOL_PREFIXast_sip_session_send_request;
+ LINKER_SYMBOL_PREFIXast_sip_session_create_outgoing;
+ local:
+ *;
+};
diff --git a/res/res_sorcery_config.c b/res/res_sorcery_config.c
index 509538f5a..c6ec62ed6 100644
--- a/res/res_sorcery_config.c
+++ b/res/res_sorcery_config.c
@@ -199,7 +199,6 @@ static void sorcery_config_retrieve_multiple(const struct ast_sorcery *sorcery,
if (!config_objects) {
return;
}
-
ao2_callback(config_objects, 0, sorcery_config_fields_cmp, &params);
}