diff '--color=auto' -durN asterisk-22.6.0.orig/channels/Makefile asterisk-22.6.0/channels/Makefile --- asterisk-22.6.0.orig/channels/Makefile 2025-10-17 13:01:59.849455714 +1300 +++ asterisk-22.6.0/channels/Makefile 2025-10-21 18:12:24.432605577 +1300 @@ -29,4 +29,7 @@ chan_unistim.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION) chan_phone.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION) +$(call MOD_ADD_C,chan_sip,$(wildcard sip/*.c)) +chan_sip.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION) + $(call MOD_ADD_C,console_video.c vgrabbers.c console_board.c) diff '--color=auto' -durN asterisk-22.6.0.orig/channels/chan_sip.c asterisk-22.6.0/channels/chan_sip.c --- asterisk-22.6.0.orig/channels/chan_sip.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/chan_sip.c 2025-10-21 18:12:24.434605523 +1300 @@ -0,0 +1,105 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2012, Digium, Inc. + * + * Mark Spencer + * + * 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. + */ + +/* + * Implementation of Session Initiation Protocol + * + * Mark Spencer + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + res_crypto + yes + extended + ***/ + +#include "asterisk.h" +#include "asterisk/module.h" +#include "asterisk/netsock2.h" +#include "asterisk/channel.h" +#include "asterisk/indications.h" + +#include "sip/include/sip.h" +#include "sip/include/proxy.h" +#include "sip/include/config.h" + +static int sip_module_load(void); +static int sip_module_unload(void); +static int sip_module_reload(void); + +void sip_module_ref(void) +{ + ast_module_ref(ast_module_info->self); +} + +void sip_module_unref(void) +{ + ast_module_unref(ast_module_info->self); +} + +void sip_module_notice(void) +{ + ast_log(LOG_NOTICE, "This version of chan_sip has been modified to support Cisco Enterprise IP phones\n"); + ast_log(LOG_NOTICE, "along with additional changes after the driver was removed from the official\n"); + ast_log(LOG_NOTICE, "Asterisk codebase in version 22. See https://usecallmanager.nz for more information\n"); +} + +static int sip_module_load(void) +{ + if (sip_config_load()) { + sip_config_unload(); + return AST_MODULE_LOAD_DECLINE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int sip_module_unload(void) +{ + if (sip_config_unload()) { + return AST_MODULE_LOAD_FAILURE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int sip_module_reload(void) +{ + if (sip_config_reload(CHANNEL_CLI_RELOAD)) { + return AST_MODULE_RELOAD_IN_PROGRESS; + } + + return AST_MODULE_RELOAD_SUCCESS; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Session Initiation Protocol (SIP)", + .support_level = AST_MODULE_SUPPORT_EXTENDED, + .load = sip_module_load, + .unload = sip_module_unload, + .reload = sip_module_reload, + .load_pri = AST_MODPRI_CHANNEL_DRIVER, + .requires = "dnsmgr,udptl", + .optional_modules = "res_crypto", +); diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/authentication_realms.c asterisk-22.6.0/channels/sip/authentication_realms.c --- asterisk-22.6.0.orig/channels/sip/authentication_realms.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/authentication_realms.c 2025-10-21 18:38:17.432218625 +1300 @@ -0,0 +1,137 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" + +#include "include/sip.h" +#include "include/authentication_realms.h" + +static void sip_authentication_realm_destroy(void *data); + +/* Authentication container for realm authentication */ +struct ao2_container *sip_authentication_realms = NULL; + +int sip_authentication_realm_cmp(void *data, void *arg, int flags) +{ + struct sip_authentication_realm *authentication_realm; + const char *realm; + + authentication_realm = (struct sip_authentication_realm *) data; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + struct sip_authentication_realm *authentication_realm = (struct sip_authentication_realm *) arg; + + realm = authentication_realm->realm; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + realm = (const char *) arg; + } else { + return 0; + } + + if (!strcmp(authentication_realm->realm, realm)) { + return CMP_MATCH | CMP_STOP; + } + + return 0; +} + +/* Add realm authentication to credentials */ +int sip_authentication_realm_build(struct ao2_container *authentication_realms, const char *config, int lineno) +{ + char *user, *realm, *secret, *md5_secret; + struct sip_authentication_realm *authentication_realm; + + user = ast_strdupa(config); + + if ((realm = strrchr(user, '@'))) { + *realm++ = '\0'; + } + + if (strchr(user, '#')) { + ast_log(LOG_WARNING, + "Specifying MD5 secret using '#' is no longer supported, use 'user::md5secret@realm' instead at line %d\n", + lineno); + return -1; + } + + if ((secret = strchr(user, ':'))) { + *secret++ = '\0'; + + if ((md5_secret = strchr(secret, ':'))) { + *md5_secret++ = '\0'; + } + } else { + md5_secret = NULL; + } + + if (ast_strlen_zero(user) || ast_strlen_zero(realm) || + (ast_strlen_zero(secret) && ast_strlen_zero(md5_secret))) { + ast_log(LOG_WARNING, + "Format for 'authentication' is user[:secret[:md5secret]]@realm at line %d\n", lineno); + return -1; + } + + if ((authentication_realm = ao2_find(authentication_realms, realm, OBJ_SEARCH_KEY))) { + ao2_ref(authentication_realm, -1); + return 0; + } + + /* Create the authentication credential entry */ + if (!(authentication_realm = ao2_alloc(sizeof(*authentication_realm), sip_authentication_realm_destroy))) { + return -1; + } + + if (ast_string_field_init(authentication_realm, 128)) { + ao2_ref(authentication_realm, -1); + return -1; + } + + ast_string_field_set(authentication_realm, realm, realm); + ast_string_field_set(authentication_realm, user, user); + ast_string_field_set(authentication_realm, secret, secret); + ast_string_field_set(authentication_realm, md5_secret, md5_secret); + + /* Add credential to container list */ + ao2_link(authentication_realms, authentication_realm); + ao2_ref(authentication_realm, -1); + + ast_debug(1, "Added authentication for realm '%s'\n", realm); + return 0; +} + +/* Realm authentication container destructor */ +static void sip_authentication_realm_destroy(void *data) +{ + struct sip_authentication_realm *authentication_realm = (struct sip_authentication_realm *) data; + + ast_debug(1, "Destroying authentication realm '%s'\n", authentication_realm->realm); + ast_string_field_free_memory(authentication_realm); +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/callback.c asterisk-22.6.0/channels/sip/callback.c --- asterisk-22.6.0.orig/channels/sip/callback.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/callback.c 2025-10-21 18:38:17.433218599 +1300 @@ -0,0 +1,440 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/localtime.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/channel.h" +#include "asterisk/causes.h" +#include "asterisk/indications.h" +#include "asterisk/pbx.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/response.h" +#include "include/session_timer.h" +#include "include/proxy.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/remotecc.h" +#include "include/callback.h" + +static int sip_callback_event(const char *context, const char *exten, struct ast_state_cb_info *info, void *data); + +int sip_callback_build(struct sip_dialog *dialog, struct sip_message *request, + const char *call_id, const char *local_tag, const char *remote_tag, const char *user_call_data) +{ + struct sip_dialog *target_dialog, *callback_dialog; + struct ast_channel *channel; + char escaped_exten[128]; + int exten_state, presence_state; + struct ast_str *content; + int is_79xx; + + sip_response_send(dialog, "202 Accepted", request); + + content = ast_str_alloca(4096); + is_79xx = strcasestr(sip_message_find_header(request, "User-Agent"), "CP79") != NULL; + + if (!ast_strlen_zero(call_id)) { + RAII_VAR(char *, subtype, NULL, ast_free_ptr); + RAII_VAR(char *, message, NULL, ast_free_ptr); + struct sip_callback *callback; + char *exten; + int res; + + res = -1; + exten = NULL; + + /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ + if (!(target_dialog = sip_dialog_find(call_id, remote_tag, local_tag))) { + ast_debug(1, "No dialog with Call-ID: '%s' From: tag='%s' To: tag='%s'\n", + call_id, remote_tag, local_tag); + goto cleanup; + } + + ao2_lock(target_dialog); + + if (!(channel = target_dialog->channel)) { + ast_debug(1, "No owner channel\n"); + + ao2_unlock(target_dialog); + ao2_ref(target_dialog, -1); + goto cleanup; + } + + ast_channel_ref(channel); + ao2_unlock(target_dialog); + ao2_ref(target_dialog, -1); + + ast_channel_lock(channel); + + exten = ast_strdupa(S_COR(ast_channel_connected(channel)->id.number.valid, + ast_channel_connected(channel)->id.number.str, target_dialog->to_user)); + + ast_channel_hangupcause_set(channel, AST_CAUSE_FAILURE); + ast_channel_unlock(channel); + + ast_softhangup(channel, AST_SOFTHANGUP_EXPLICIT); + ast_channel_unref(channel); + + if (dialog->peer->callback) { + sip_callback_destroy(dialog->peer->callback); + dialog->peer->callback = NULL; + } + + if (ast_strlen_zero(exten)) { + goto cleanup; + } + + if (!(callback = ast_calloc(1, sizeof(*callback)))) { + goto cleanup; + } + + if (!(callback->exten = ast_strdup(exten))) { + ast_free(callback); + goto cleanup; + } + + if (!(callback->state_id = ast_extension_state_add(dialog->peer->context, callback->exten, + sip_callback_event, dialog->peer))) { + ast_free(callback->exten); + ast_free(callback); + goto cleanup; + } + + dialog->peer->callback = callback; + + exten_state = ast_extension_state(NULL, dialog->peer->context, callback->exten); + presence_state = ast_hint_presence_state(NULL, dialog->peer->context, callback->exten, + &subtype, &message); + + if (exten_state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY) || presence_state == AST_PRESENCE_DND) { + callback->busy = TRUE; + } + + res = 0; + + cleanup: + if (res) { + if (!(callback_dialog = sip_dialog_alloc(NULL, &dialog->socket, SIP_METHOD_REFER, NULL, 0))) { + return 0; + } + + sip_dialog_copy(callback_dialog, dialog); + ast_xml_escape(exten, escaped_exten, sizeof(escaped_exten)); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n" + " 0\n" + " 0\n" + " StationSequenceLast\n" + " 2\n" + " 0\n" + " 0\n" + " 0\n" + " \n" + "\n" + "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-cm+xml\r\n" + "\r\n" + "\n" + "\n" + " CallBack\n"); + ast_str_append(&content, 0, " Unable to activate callback on %s\n", escaped_exten); + ast_str_append(&content, 0, " Please select\n" + " \n" + " Exit\n"); + ast_str_append(&content, 0, " %d\n", is_79xx ? 3 : 1); + ast_str_append(&content, 0, " SoftKey:Exit\n" + " \n" + "\n" + "\r\n" + "--uniqueBoundary--\r\n"); + + sip_request_send_refer_with_content(callback_dialog, "multipart/mixed; boundary=uniqueBoundary", + ast_str_buffer(content)); + ao2_ref(callback_dialog, -1); + return 0; + } + } else if (!ast_strlen_zero(user_call_data) && dialog->peer->callback) { + if (!(callback_dialog = sip_dialog_alloc(NULL, &dialog->socket, SIP_METHOD_REFER, NULL, 0))) { + return 0; + } + + sip_dialog_copy(callback_dialog, dialog); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n" + " 0\n" + " 0\n" + " StationSequenceLast\n" + " 2\n" + " 0\n" + " 0\n" + " 0\n" + " \n" + "\n" + "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-cm+xml\r\n" + "\r\n" + "\n" + "\n" + " \n" + "\n" + "\r\n" + "--uniqueBoundary--\r\n"); + + sip_request_send_refer_with_content(callback_dialog, "multipart/mixed; boundary=uniqueBoundary", + ast_str_buffer(content)); + ao2_ref(callback_dialog, -1); + + if (!strcmp(user_call_data, "Dial")) { + if (!(callback_dialog = sip_dialog_alloc(NULL, &dialog->socket, SIP_METHOD_REFER, NULL, 0))) { + return 0; + } + + sip_dialog_copy(callback_dialog, dialog); + ast_xml_escape(dialog->peer->callback->exten, escaped_exten, sizeof(escaped_exten)); + + ast_str_reset(content); + ast_str_append(&content, 0, "\n" + "\n" + " \n"); + ast_str_append(&content, 0, " %s\n", escaped_exten); + ast_str_append(&content, 0, " %d\n", dialog->peer->line_index); + ast_str_append(&content, 0, " \n" + "\n"); + + sip_request_send_refer_with_content(callback_dialog, "application/x-cisco-remotecc-request+xml", + ast_str_buffer(content)); + ao2_ref(callback_dialog, -1); + + sip_callback_destroy(dialog->peer->callback); + dialog->peer->callback = NULL; + } else if (!strcmp(user_call_data, "Cancel")) { + sip_callback_destroy(dialog->peer->callback); + dialog->peer->callback = NULL; + } + + return 0; + } + + if (!(callback_dialog = sip_dialog_alloc(NULL, &dialog->socket, SIP_METHOD_REFER, NULL, 0))) { + return 0; + } + + sip_dialog_copy(callback_dialog, dialog); + ast_xml_escape(dialog->peer->callback->exten, escaped_exten, sizeof(escaped_exten)); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n"); + ast_str_append(&content, 0, " %d\n", SIP_REMOTECC_CALLBACK); + ast_str_append(&content, 0, " 0\n" + " StationSequenceLast\n" + " 2\n" + " 0\n" + " 0\n" + " 0\n" + " \n" + "\n" + "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-cm+xml\r\n" + "\r\n" + "\n" + "\n" + " CallBack\n" + " "); + + if (dialog->peer->callback) { + ast_str_append(&content, 0, + "CallBack is activated on %s.\n\nPress Cancel to deactivate.\nPress Exit to quit this screen.", + escaped_exten); + } else { + ast_str_append(&content, 0, "CallBack is not activated."); + } + + ast_str_append(&content, 0, "" + " Please select\n" + " \n" + " Exit\n"); + ast_str_append(&content, 0, " %d\n", is_79xx ? 3 : 1); + ast_str_append(&content, 0, " SoftKey:Exit\n" + " \n"); + + if (dialog->peer->callback) { + ast_str_append(&content, 0, " \n" + " Cancel\n"); + ast_str_append(&content, 0, " %d\n", is_79xx ? 2 : 2); + ast_str_append(&content, 0, " UserCallData:%d:0:0:0:Cancel\n", SIP_REMOTECC_CALLBACK); + ast_str_append(&content, 0, " \n"); + } + + ast_str_append(&content, 0, "\n" + "\r\n" + "--uniqueBoundary--\r\n"); + + sip_request_send_refer_with_content(callback_dialog, "multipart/mixed; boundary=uniqueBoundary", + ast_str_buffer(content)); + ao2_ref(callback_dialog, -1); + return 0; +} + +void sip_callback_destroy(struct sip_callback *callback) +{ + ast_extension_state_del(callback->state_id, sip_callback_event); + ast_free(callback->exten); + ast_free(callback); +} + +static int sip_callback_event(const char *context, const char *exten, struct ast_state_cb_info *info, void *data) +{ + struct sip_peer *peer; + struct sip_dialog *dialog; + struct ast_str *content; + struct timeval now; + struct ast_tm tm; + char escaped_exten[128], date[32]; + int is_79xx; + + peer = (struct sip_peer *) data; + + /* Only send an notify after the phone has gone from busy/inuse to not inuse */ + if (info->exten_state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY) || info->presence_state == AST_PRESENCE_DND) { + peer->callback->busy = TRUE; + return 0; + } else if (info->exten_state != AST_EXTENSION_NOT_INUSE || + info->presence_state != AST_PRESENCE_AVAILABLE || !peer->callback->busy) { + return 0; + } + + if (!((dialog = sip_dialog_alloc(NULL, NULL, SIP_METHOD_REFER, NULL, 0)))) { + return 0; + } + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + ao2_ref(dialog, -1); + return 0; + } + + is_79xx = strstr(peer->useragent, "CP79") != NULL; + content = ast_str_alloca(4096); + + now = ast_tvnow(); + ast_strftime(date, sizeof(date), "%X %x", ast_localtime(&now, &tm, NULL)); + ast_xml_escape(peer->callback->exten, escaped_exten, sizeof(escaped_exten)); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\n" + "\n" + " \n" + " DtBeepBonk\n" + " all\n" + " \n" + "\n" + "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n"); + ast_str_append(&content, 0, " %d\n", SIP_REMOTECC_CALLBACK); + ast_str_append(&content, 0, " 0\n" + " StationSequenceLast\n" + " 2\n" + " 0\n" + " 0\n" + " 0\n" + " \n" + "\n" + "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-cm+xml\r\n" + "\r\n" + "\n" + "\n" + " CallBack\n"); + ast_str_append(&content, 0, + "%s is now available at %s.\n\nPress Dial to call.\nPress Cancel to deactivate.\n" + "Press Exit to quit this screen.\n", escaped_exten, date); + ast_str_append(&content, 0, " Please select\n" + " \n" + " Exit\n"); + ast_str_append(&content, 0, " %d\n", is_79xx ? 3 : 1); + ast_str_append(&content, 0, " SoftKey:Exit\n" + " \n" + " \n" + " Cancel\n" + " %d\n", is_79xx ? 2 : 2); + ast_str_append(&content, 0, " UserCallData:%d:0:0:0:Cancel\n", SIP_REMOTECC_CALLBACK); + ast_str_append(&content, 0, " \n" + " \n" + " Dial\n"); + ast_str_append(&content, 0, " %d\n", is_79xx ? 1 : 3); + ast_str_append(&content, 0, " UserCallData:%d:0:0:0:Dial\n", SIP_REMOTECC_CALLBACK); + ast_str_append(&content, 0, " \n" + "\n" + "\r\n" + "--uniqueBoundary--\r\n"); + + sip_request_send_refer_with_content(dialog, "multipart/mixed; boundary=uniqueBoundary", + ast_str_buffer(content)); + ao2_ref(dialog, -1); + return 0; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/channel_tech.c asterisk-22.6.0/channels/sip/channel_tech.c --- asterisk-22.6.0.orig/channels/sip/channel_tech.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/channel_tech.c 2025-10-21 18:38:17.434218572 +1300 @@ -0,0 +1,1815 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/channel.h" +#include "asterisk/chanvars.h" +#include "asterisk/causes.h" +#include "asterisk/callerid.h" +#include "asterisk/indications.h" +#include "asterisk/musiconhold.h" +#include "asterisk/format.h" +#include "asterisk/format_cache.h" +#include "asterisk/rtp_engine.h" +#include "asterisk/sdp_srtp.h" +#include "asterisk/dsp.h" +#include "asterisk/pbx.h" +#include "asterisk/app.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_channels.h" +#include "asterisk/sip_api.h" +#include "asterisk/message.h" +#include "asterisk/udptl.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/channel_tech.h" +#include "include/rtp_glue.h" +#include "include/dialplan_functions.h" +#include "include/conference.h" +#include "include/fax.h" + +/*** DOCUMENTATION + + Specifying a prefix of sip: will send the + message as a SIP MESSAGE request. + + + The from parameter can be a configured peer name + or in the form of "display-name" <URI>. + + + Ignored + + ***/ + +static struct ast_frame *sip_channel_read_rtp(struct ast_channel *channel, struct sip_dialog *dialog, int *fax_detect); + +/* Definition of this channel for PBX channel registration */ +struct ast_channel_tech sip_channel_tech = { + .type = "SIP", + .description = "Session Initiation Protocol (SIP)", + .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER, + .requester = sip_channel_requester, /* Called with channel unlocked */ + .call = sip_channel_call, /* Called with channel locked */ + .hangup = sip_channel_hangup, /* Called with channel locked */ + .answer = sip_channel_answer, /* Called with channel locked */ + .read = sip_channel_read, /* Called with channel locked */ + .write = sip_channel_write, /* Called with channel locked */ + .write_video = sip_channel_write, /* Called with channel locked */ + .write_text = sip_channel_write, + .indicate = sip_channel_indicate, /* Called with channel locked */ + .transfer = sip_channel_transfer, /* Called with channel locked */ + .fixup = sip_channel_fixup, /* Called with channel locked */ + .send_digit_begin = sip_channel_send_digit_begin, /* Called with channel unlocked */ + .send_digit_end = sip_channel_send_digit_end, + .setoption = sip_channel_setoption, + .queryoption = sip_channel_queryoption, + .devicestate = sip_channel_devicestate, /* Called with channel unlocked (not channel-specific) */ + .presencestate = sip_channel_presencestate, /* Called with channel unlocked (not channel-specific) */ + .get_pvt_uniqueid = sip_channel_get_pvt_uniqueid, + .early_bridge = ast_rtp_instance_early_bridge, + .send_text = sip_channel_send_text, /* Called with channel locked */ + .send_html = sip_channel_send_html, + .func_channel_read = sip_function_channel_read, +}; + +struct ast_sip_api_tech sip_api_tech = { + .version = AST_SIP_API_VERSION, + .name = "chan_sip", + .sipinfo_send = sip_api_sipinfo_send, +}; + +const struct ast_msg_tech sip_msg_tech = { + .name = "sip", + .msg_send = sip_msg_send, +}; + +/* SIP calls initiated by the PBX arrive here */ +struct ast_channel *sip_channel_requester(const char *type, struct ast_format_cap *format_cap, + const struct ast_assigned_ids *assigned_ids, const struct ast_channel *requestor_channel, + const char *destination, int *cause) +{ + struct sip_dialog *dialog; + char *parse, *peer_name, *exten, *user, *host, *secret, *md5_secret, *authorization_user, *proxy, *protocol; + struct ast_str *format_names; + enum ast_transport transport; + + if (ast_format_cap_empty(format_cap)) { + ast_log(LOG_NOTICE, "Asked to get a channel without offering any format\n"); + *cause = AST_CAUSE_BEARERCAPABILITY_NOTAVAIL; /* Can't find codec to connect to host */ + return NULL; + } + + format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + ast_debug(1, "Asked to create a SIP channel with formats '%s'\n", + ast_format_cap_get_names(format_cap, &format_names)); + + if (ast_strlen_zero(destination)) { + ast_log(LOG_ERROR, "Unable to create channel with empty destination\n"); + *cause = AST_CAUSE_CHANNEL_UNACCEPTABLE; + return NULL; + } + + peer_name = NULL; + user = NULL; + secret = NULL; + md5_secret = NULL; + authorization_user = NULL; + exten = NULL; + proxy = NULL; + transport = AST_TRANSPORT_UDP; + + /* Save the destination, the SIP dial string */ + parse = ast_strdupa(destination); + + if ((host = strchr(parse, '@'))) { + *host++ = '\0'; + + peer_name = NULL; + user = strsep(&parse, ":"); + secret = strsep(&parse, ":"); + md5_secret = strsep(&parse, ":"); + authorization_user = strsep(&parse, ":"); + protocol = strsep(&parse, ":"); + + if (!ast_strlen_zero(protocol)) { + if (!strcmp(protocol, "udp")) { + transport = AST_TRANSPORT_UDP; + } else if (!strcmp(protocol, "tcp")) { + transport = AST_TRANSPORT_TCP; + } else if (!strcmp(protocol, "tls")) { + transport = AST_TRANSPORT_TLS; + } else { + ast_log(LOG_WARNING, "'%s' is not a valid transport option to Dial() for SIP calls\n", + protocol); + *cause = AST_CAUSE_CHANNEL_UNACCEPTABLE; + return NULL; + } + } + } else { + peer_name = strsep(&parse, "/"); + exten = strsep(&parse, "/"); + proxy = strsep(&parse, "/"); + } + + if (!(dialog = sip_dialog_alloc(NULL, NULL, SIP_METHOD_INVITE, NULL, ast_read_threadstorage_callid()))) { + *cause = AST_CAUSE_SWITCH_CONGESTION; + return NULL; + } + + if (sip_dialog_build(dialog, S_OR(peer_name, host), NULL, TRUE)) { + sip_dialog_unlink(dialog); + ao2_ref(dialog, -1); + *cause = AST_CAUSE_UNREGISTERED; + return NULL; + } + + ast_string_field_build(dialog, device_name, "%s/%s", type, destination); + + if (peer_name) { + /* We have an extension to call, don't use the full contact here. This to enable dialing registered + * peers with extension dialing, like SIP/peername/exten SIP/peername will still use the full contact */ + if (!ast_strlen_zero(exten)) { + ast_string_field_set(dialog, to_user, exten); + ast_string_field_set(dialog, contact, NULL); + } + + if (!ast_strlen_zero(proxy)) { + ao2_cleanup(dialog->proxy); + dialog->proxy = sip_proxy_build(proxy, 0, NULL); + } + } else { + sip_socket_set_transport(&dialog->socket, transport); + + /* This is a temporary peer so it is okay to overwrite the settings */ + ast_string_field_set(dialog, from_user, user); + ast_string_field_set(dialog->peer, secret, secret); + ast_string_field_set(dialog->peer, md5_secret, md5_secret); + ast_string_field_set(dialog->peer, authorization_user, S_OR(authorization_user, user)); + } + + sip_dialog_set_our_address(dialog); + dialog->originated_call = TRUE; + + /* When chan_sip is first loaded or reloaded, we need to check for NAT and set the appropiate flags now that we + * have the auto_* settings */ + sip_dialog_check_nat(dialog, &dialog->address); + + /* If there is a peer related to this outgoing call and it hasn't re-registered after a reload, we need to set + * the peer's NAT flags accordingly */ + if (!ast_strlen_zero(dialog->peer->contact) && !dialog->nat_detected && + ((dialog->nat_auto_rport && !dialog->nat_force_rport) || + (dialog->nat_auto_rtp && !dialog->nat_rtp))) { + /* We need to make an attempt to determine if a peer is behind NAT + * if the peer has the flags auto_force_rport or auto_comedia set */ + struct ast_sockaddr address; + + sip_get_uri_address(dialog->peer->contact, &address); + sip_dialog_check_nat(dialog, &address); + } + + sip_peer_set_auto_nat(dialog->peer, dialog->nat_detected); + sip_dialog_set_rtp_nat(dialog); + + ast_format_cap_append_from_cap(dialog->outgoing_format_cap, format_cap, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_get_compatible(format_cap, dialog->format_cap, dialog->joint_format_cap); + + /* Place the call */ + ao2_lock(dialog); + sip_dialog_alloc_channel(dialog, AST_STATE_DOWN, dialog->peer->name, assigned_ids, requestor_channel, + dialog->logger_callid); + ao2_unlock(dialog); + + if (!dialog->channel) { + sip_dialog_unlink(dialog); + } else { + ast_channel_unlock(dialog->channel); + } + + ao2_ref(dialog, -1); /* Channel pvt holds the ref now */ + + ast_update_use_count(); + sip_monitor_restart(); + return dialog->channel; +} + +/* Initiate SIP call from PBX */ +int sip_channel_call(struct ast_channel *channel, const char *destination, int timeout) +{ + struct sip_dialog *dialog; + struct ast_var_t *variable; + + /* Channel is locked, so the reference cannot go away */ + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Channel '%s' has no tech_pvt\n", ast_channel_name(channel)); + return -1; + } + + if (ast_channel_state(channel) != AST_STATE_DOWN && ast_channel_state(channel) != AST_STATE_RESERVED) { + ast_log(LOG_WARNING, "Call on channel '%s' is neither down nor reserved\n", ast_channel_name(channel)); + return -1; + } + + if (dialog->peer->do_not_disturb && dialog->peer->busy_when_dnd) { + ast_queue_control(dialog->channel, AST_CONTROL_BUSY); + return 0; + } else if (!ast_strlen_zero(dialog->peer->call_forward)) { + ast_channel_call_forward_set(channel, dialog->peer->call_forward); + ast_queue_control(dialog->channel, AST_CONTROL_BUSY); + return 0; + } + + AST_LIST_TRAVERSE(ast_channel_varshead(channel), variable, entries) { + if (!strcmp(ast_var_name(variable), "SIP_URI_OPTIONS")) { + ast_string_field_set(dialog, uri_options, ast_var_value(variable)); + } else if (!dialog->add_headers && !strncmp(ast_var_name(variable), "SIP_ADD_HEADER", 14)) { + dialog->add_headers = TRUE; + } else if (!strcmp(ast_var_name(variable), "SIP_FROM_DOMAIN")) { + ast_string_field_set(dialog, from_domain, ast_var_value(variable)); + } else if (!strcmp(ast_var_name(variable), "SIP_TRANSFER_REPLACES")) { + /* We're replacing a call */ + ast_string_field_set(dialog, replaces, ast_var_value(variable)); + } else if (!strcmp(ast_var_name(variable), "SIP_MAX_FORWARDS")) { + if (sscanf(ast_var_value(variable), "%30d", &dialog->max_forwards) != 1) { + ast_log(LOG_WARNING, "The SIP_MAX_FORWARDS channel variable is not a valid integer\n"); + } + } + } + + /* Check to see if we should try to force encryption */ + if (dialog->secure_signaling && dialog->socket.transport != AST_TRANSPORT_TLS) { + ast_log(LOG_WARNING, "Encrypted signaling is required on %s\n", ast_channel_name(channel)); + ast_channel_hangupcause_set(channel, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); + return -1; + } + + if (dialog->secure_media) { + if (!dialog->secure_signaling) { + ast_log(LOG_WARNING, "Encrypted signaling is required to use secure media on %s\n", + ast_channel_name(channel)); + ast_channel_hangupcause_set(channel, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); + return -1; + } + + if (dialog->direct_media) { + ast_debug(1, "Direct media not possible when using SRTP, disabling 'directmedia'\n"); + dialog->direct_media = 0; + } + + if (dialog->audio_rtp && !dialog->secure_audio_rtp && !(dialog->secure_audio_rtp = ast_sdp_srtp_alloc())) { + ast_log(LOG_WARNING, "SRTP audio setup failed\n"); + return -1; + } + + if (dialog->video_rtp && !dialog->secure_video_rtp && !(dialog->secure_video_rtp = ast_sdp_srtp_alloc())) { + ast_log(LOG_WARNING, "SRTP video setup failed\n"); + return -1; + } + + if (dialog->text_rtp && !dialog->secure_text_rtp && !(dialog->secure_text_rtp = ast_sdp_srtp_alloc())) { + ast_log(LOG_WARNING, "SRTP text setup failed\n"); + return -1; + } + } + + if (sip_dialog_change_inuse(dialog, SIP_RINGING_ADD) == -1) { + ast_channel_hangupcause_set(channel, AST_CAUSE_USER_BUSY); + return -1; + } + + ast_debug(1, "Outgoing call for %s\n", dialog->peer->name); + ast_rtp_instance_available_formats(dialog->audio_rtp, + dialog->format_cap, dialog->outgoing_format_cap, dialog->joint_format_cap); + + /* If there are no formats left to offer, punt */ + if (ast_format_cap_empty(dialog->joint_format_cap)) { + ast_log(LOG_WARNING, "No format found to offer. Cancelling call to %s\n", dialog->peer->name); + return -1; + } + + dialog->joint_non_format_cap = dialog->non_format_cap; + + /* If audio was requested (prefcaps) and the [peer] section contains audio (caps) the user expects audio. In + * that case, if jointcaps contain no audio, punt. Furthermore, this check allows the [peer] section to have no + * audio. In that case, the user expects no audio and we can pass. Finally, this check allows the requester not + * to offer any audio. In that case, the call is expected to have no audio and we can pass, as well */ + if ((ast_format_cap_empty(dialog->format_cap) || + ast_format_cap_has_type(dialog->format_cap, AST_MEDIA_TYPE_AUDIO)) && + (ast_format_cap_empty(dialog->outgoing_format_cap) || + ast_format_cap_has_type(dialog->outgoing_format_cap, AST_MEDIA_TYPE_AUDIO)) && + !ast_format_cap_has_type(dialog->joint_format_cap, AST_MEDIA_TYPE_AUDIO)) { + ast_log(LOG_WARNING, "No audio format found to offer. Cancelling call to %s\n", dialog->peer->name); + return -1; + } + + dialog->caller_presentation = ast_party_id_presentation(&ast_channel_caller(channel)->id); + + if (!ast_strlen_zero(dialog->caller_number) || !ast_strlen_zero(dialog->caller_name) || + (dialog->caller_presentation & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + sip_dialog_queue_connected_line(dialog, AST_CONNECTED_LINE_UPDATE_SOURCE_UNKNOWN); + } + + dialog->outgoing = TRUE; + dialog->invite_state = SIP_INVITE_CALLING; + + if (sip_request_send_invite(dialog, TRUE, SIP_INIT_REQUEST, NULL)) { + ao2_unlock(dialog); + return -1; + } + + /* Initialize auto-congest time */ + AST_SCHED_REPLACE_UNREF(dialog->invite_sched_id, sip_sched_context, dialog->timer_b, sip_dialog_auto_congest, + dialog, ao2_cleanup(_data), ao2_cleanup(dialog), ao2_bump(dialog)); + ao2_unlock(dialog); + return 0; +} + +/* Hangup SIP call */ +int sip_channel_hangup(struct ast_channel *channel) +{ + struct sip_dialog *dialog; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to hangup channel '%s' with no tech_pvt\n", ast_channel_name(channel)); + return 0; + } + + if (ast_channel_hangupcause(channel) == AST_CAUSE_ANSWERED_ELSEWHERE) { + ast_debug(1, "This call was answered elsewhere\n"); + dialog->answered_elsewhere = TRUE; + } + + /* Store hangupcause locally in dialog so we still have it before disconnect */ + if (dialog->channel) { + dialog->hangupcause = ast_channel_hangupcause(dialog->channel); + } + + if (dialog->defer_bye_on_transfer) { + if (dialog->inuse || dialog->onhold) { + ast_debug(1, "Decrement '%s' inuse count on hangup\n", dialog->peer->name); + sip_dialog_change_inuse(dialog, SIP_INUSE_REMOVE); + } + + ast_debug(1, "Not hanging up '%s' right now due to transfer, rescheduling hangup\n", dialog->call_id); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + + dialog->defer_bye_on_transfer = FALSE; + + if (dialog->channel) { + ao2_lock(dialog); + sip_dialog_set_channel(dialog, NULL); /* Owner will be gone after we return, so take it away */ + ao2_unlock(dialog); + + ao2_cleanup(ast_channel_tech_pvt(channel)); + ast_channel_tech_pvt_set(channel, NULL); + } + + sip_module_unref(); + return 0; + } + + ast_debug(1, "Hanging up '%s' with channel '%s'\n", dialog->call_id, ast_channel_name(channel)); + ao2_lock(dialog); + + if (dialog->inuse || dialog->onhold) { + ast_debug(1, "Decrement '%s' inuse counter on hangup\n", dialog->peer->name); + sip_dialog_change_inuse(dialog, SIP_INUSE_REMOVE); + + /* Clear the peer selected list if they hangup before completing the conference */ + if (!dialog->peer->inuse) { + struct sip_selected *selected; + + ao2_lock(dialog->peer); + + while ((selected = AST_LIST_REMOVE_HEAD(&dialog->peer->selected, next))) { + sip_selected_destroy(selected); + } + + ao2_unlock(dialog->peer); + } + } + + /* Determine how to disconnect */ + if (dialog->channel != channel) { + ast_log(LOG_WARNING, "We aren't the owner of '%s', can't hangup call\n", ast_channel_name(channel)); + ao2_unlock(dialog); + return 0; + } + + sip_dialog_stop_rtp(dialog); /* Immediately stop RTP, VRTP and UDPTL as applicable */ + sip_dialog_set_dsp_detect(dialog, FALSE); + + sip_dialog_set_channel(dialog, NULL); + ast_channel_tech_pvt_set(channel, NULL); + sip_module_unref(); + + /* Do not destroy this dialog until we have timeout or get an answer to the BYE or INVITE/CANCEL. If we get no + * answer during retransmit period, drop the call anyway. (Sorry, mother-in-law, you can't deny a hangup by + * sending 603 declined to BYE...) */ + if (dialog->already_gone) { + sip_dialog_set_need_destroy(dialog, "hangup"); + } else if (dialog->invite_state != SIP_INVITE_CALLING) { + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } + + /* Start the process if it's not already started */ + if (!dialog->already_gone && !ast_strlen_zero(dialog->initial_request.uri)) { + /* If the call is not UP, we need to send CANCEL instead of BYE. In case of re-invites, the call might + * be UP even though we have an incomplete invite transaction */ + if (ast_channel_state(channel) != AST_STATE_UP) { + /* Outgoing call, not up */ + if (dialog->outgoing) { + /* If we can't send right now, mark it pending */ + if (dialog->invite_state == SIP_INVITE_CALLING) { + /* We can't send anything in CALLING state */ + dialog->pending_bye = TRUE; + } else { + struct sip_packet *packet; + + AST_LIST_TRAVERSE(&dialog->packet_queue, packet, next) { + sip_packet_semi_ack(dialog, packet->method, packet->cseq, packet->response); + } + + /* Actually don't destroy us yet, wait for the 487 on our original INVITE, but + * we do set an autodestruct just in case we never get it so send a new CANCEL */ + sip_request_send_cancel(dialog); + } + } else { /* Incoming call, not up */ + const char *status_line; + + dialog->invite_state = SIP_INVITE_TERMINATED; + sip_dialog_cancel_provisional_keepalive(dialog); + + if (dialog->transfer_response_error) { + sip_response_send_reliable(dialog, "500 Internal Server Error", + &dialog->initial_request); + } else if (dialog->hangupcause && (status_line = sip_cause2hangup(dialog->hangupcause))) { + sip_response_send_reliable(dialog, status_line, &dialog->initial_request); + } else { + sip_response_send_reliable(dialog, "603 Decline", &dialog->initial_request); + } + } + } else { /* Call is in UP state, send BYE */ + sip_session_timer_stop(dialog); + + if (!dialog->pending_invite_cseq) { + char quality[AST_MAX_USER_FIELD]; + + if (dialog->audio_rtp) { + struct ast_rtp_instance *rtp = dialog->audio_rtp; + + ao2_ref(rtp, +1); + ast_channel_unlock(channel); + + ao2_unlock(dialog); + ast_rtp_instance_set_stats_vars(channel, rtp); + + ao2_ref(rtp, -1); + ast_channel_lock(channel); + + ao2_lock(dialog); + } + + /* The channel variables are set below just to get the AMI VarSet event because the + * channel is being hungup */ + if (dialog->audio_rtp || dialog->video_rtp || dialog->text_rtp) { + ast_channel_stage_snapshot(channel); + } + + if (dialog->audio_rtp && ast_rtp_instance_get_quality(dialog->audio_rtp, + AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality, sizeof(quality))) { + pbx_builtin_setvar_helper(channel, "RTPAUDIOQOS", quality); + } + + if (dialog->video_rtp && ast_rtp_instance_get_quality(dialog->video_rtp, + AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality, sizeof(quality))) { + pbx_builtin_setvar_helper(channel, "RTPVIDEOQOS", quality); + } + + if (dialog->text_rtp && ast_rtp_instance_get_quality(dialog->text_rtp, + AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality, sizeof(quality))) { + pbx_builtin_setvar_helper(channel, "RTPTEXTQOS", quality); + } + + if (dialog->audio_rtp || dialog->video_rtp || dialog->text_rtp) { + ast_channel_stage_snapshot_done(channel); + } + + /* Send a hangup */ + if (ast_channel_state(channel) == AST_STATE_UP) { + sip_request_send_bye(dialog); + } + } else { + /* Note we will need a BYE when this all settles out but we can't send one while we have + * "INVITE" outstanding */ + dialog->pending_bye = TRUE; + dialog->need_reinvite = FALSE; + + sip_dialog_stop_need_reinvite(dialog); + sip_dialog_cancel_destroy(dialog); + + /* If we have an ongoing reinvite, there is a chance that we have gotten a provisional + * response, but something weird has happened and we will never receive a final + * response. So, just in case, check for pending actions after a bit of time to trigger + * the pending bye that we are setting above */ + if (dialog->ongoing_reinvite && dialog->reinvite_sched_id == -1) { + sip_dialog_start_reinvite(dialog); + } + } + } + } + + ao2_unlock(dialog); + ao2_ref(dialog, -1); /* Channel ref */ + return 0; +} + +/* Answer SIP call, send 200 OK on Invite */ +int sip_channel_answer(struct ast_channel *channel) +{ + struct sip_dialog *dialog; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to answer channel '%s' with no tech_pvt\n", ast_channel_name(channel)); + return 0; + } + + if (ast_channel_state(channel) == AST_STATE_UP) { + ast_debug(1, "Already answered channel '%s'\n", ast_channel_name(channel)); + return 0; + } + + ast_debug(1, "Answering channel: %s\n", ast_channel_name(channel)); + + ao2_lock(dialog); + sip_try_suggested_codec(dialog); + + ast_setstate(channel, AST_STATE_UP); + ast_rtp_instance_update_source(dialog->audio_rtp); + + /* If we have sent progress then use the original SDP version number */ + sip_response_send_with_sdp(dialog, "200 OK", &dialog->initial_request, SIP_SEND_CRITICAL, + dialog->sent_progress, TRUE); + dialog->established = TRUE; + + /* RFC says the session timer starts counting on 200, not on INVITE */ + sip_session_timer_restart(dialog); + ao2_unlock(dialog); + return 0; +} + +/* Read SIP RTP from channel */ +struct ast_frame *sip_channel_read(struct ast_channel *channel) +{ + struct ast_frame *frame; + struct sip_dialog *dialog; + int fax_detect; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to read from channel '%s' with no tech_pvt\n", ast_channel_name(channel)); + return &ast_null_frame; + } + + ao2_lock(dialog); + + fax_detect = FALSE; + frame = sip_channel_read_rtp(channel, dialog, &fax_detect); + + dialog->last_rtp_received = ast_tvnow(); + + /* If we detect a CNG tone and fax detection is enabled then send us off to the fax extension */ + if (fax_detect && dialog->peer->fax_detect & SIP_FAX_DETECT_CNG) { + if (strcmp(ast_channel_exten(channel), "fax")) { + const char *context = ast_channel_context(channel); + + /* We need to unlock 'chan' here because ast_exists_extension has the potential to start and + * stop an autoservice on the channel. Such action is prone to deadlock if the channel is + * locked. ast_async_goto has its own restriction on not holding the channel lock */ + ao2_unlock(dialog); + ast_channel_unlock(channel); + + ast_frfree(frame); + frame = &ast_null_frame; + + if (ast_exists_extension(channel, context, "fax", 1, + S_COR(ast_channel_caller(channel)->id.number.valid, ast_channel_caller(channel)->id.number.str, NULL))) { + ast_verb(2, "Redirecting '%s' to fax extension due to CNG detection\n", + ast_channel_name(channel)); + pbx_builtin_setvar_helper(channel, "FAXEXTEN", ast_channel_exten(channel)); + + if (ast_async_goto(channel, context, "fax", 1)) { + ast_log(LOG_WARNING, + "Failed to async goto '%s' into fax of '%s'\n", + ast_channel_name(channel), context); + } + } else { + ast_log(LOG_NOTICE, "FAX CNG detected but no fax extension\n"); + } + + ast_channel_lock(channel); + ao2_lock(dialog); + } + } + + /* Only allow audio through if they sent progress with SDP, or if the channel is actually answered */ + if (frame && frame->frametype == AST_FRAME_VOICE && + dialog->invite_state != SIP_INVITE_EARLY_MEDIA && ast_channel_state(channel) != AST_STATE_UP) { + ast_frfree(frame); + frame = &ast_null_frame; + } + + ao2_unlock(dialog); + return frame; +} + +/* Read RTP from network */ +static struct ast_frame *sip_channel_read_rtp(struct ast_channel *channel, struct sip_dialog *dialog, int *fax_detect) +{ + /* Retrieve audio/etc from channel. Assumes dialog lock is already held */ + struct ast_frame *frame; + + if (!dialog->audio_rtp) { + /* We have no RTP allocated for this channel */ + return &ast_null_frame; + } + + switch (ast_channel_fdno(channel)) { + case SIP_AUDIO_RTP_FD: + frame = ast_rtp_instance_read(dialog->audio_rtp, 0); /* RTP Audio */ + break; + case SIP_AUDIO_RTCP_FD: + frame = ast_rtp_instance_read(dialog->audio_rtp, 1); /* RTCP Control Channel */ + break; + case SIP_VIDEO_RTP_FD: + frame = ast_rtp_instance_read(dialog->video_rtp, 0); /* RTP Video */ + break; + case SIP_VIDEO_RTCP_FD: + frame = ast_rtp_instance_read(dialog->video_rtp, 1); /* RTCP Control Channel for video */ + break; + case SIP_TEXT_RTP_FD: + frame = ast_rtp_instance_read(dialog->text_rtp, 0); /* RTP Text */ + break; + case SIP_UDPTL_FD: + frame = ast_udptl_read(dialog->udptl); /* UDPTL for T.38 */ + break; + default: + frame = &ast_null_frame; + break; + } + + /* Don't forward RFC2833 if we're not supposed to */ + if (frame && (frame->frametype == AST_FRAME_DTMF_BEGIN || frame->frametype == AST_FRAME_DTMF_END) && + dialog->peer->dtmf_mode != SIP_DTMF_MODE_RFC2833) { + ast_debug(1, "Ignoring DTMF '%c' RTP frame because peer DTMF mode is not RFC2833\n", frame->subclass.integer); + ast_frfree(frame); + return &ast_null_frame; + } + + /* We already hold the channel lock */ + if (!dialog->channel || (frame && frame->frametype != AST_FRAME_VOICE)) { + return frame; + } + + if (frame && + ast_format_cap_iscompatible_format(ast_channel_nativeformats(dialog->channel), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { + RAII_VAR(struct ast_format_cap *, format_cap, NULL, ao2_cleanup); + + if (ast_format_cap_iscompatible_format(dialog->joint_format_cap, frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { + ast_debug(1, "Invalid frame of format '%s' received from '%s'\n", + ast_format_get_name(frame->subclass.format), ast_channel_name(dialog->channel)); + ast_frfree(frame); + return &ast_null_frame; + } + + ast_debug(1, "Format changed to '%s'\n", ast_format_get_name(frame->subclass.format)); + + if ((format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + ast_format_cap_append_from_cap(format_cap, + ast_channel_nativeformats(dialog->channel), AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_remove_by_type(format_cap, AST_MEDIA_TYPE_AUDIO); + + ast_format_cap_append(format_cap, frame->subclass.format, 0); + ast_channel_nativeformats_set(dialog->channel, format_cap); + } + + ast_set_read_format(dialog->channel, ast_channel_readformat(dialog->channel)); + ast_set_write_format(dialog->channel, ast_channel_writeformat(dialog->channel)); + } + + if (frame && dialog->dsp) { + frame = ast_dsp_process(dialog->channel, dialog->dsp, frame); + + if (frame && frame->frametype == AST_FRAME_DTMF) { + if (frame->subclass.integer == 'f') { + ast_debug(1, "Fax CNG detected on '%s'\n", ast_channel_name(channel)); + + if (!dialog->originated_call) { + *fax_detect = TRUE; /* Only allow fax detecting in incoming calls */ + } + + /* If we only needed this DSP for fax detection purposes we can just drop it now */ + if (dialog->peer->dtmf_mode == SIP_DTMF_MODE_INBAND) { + ast_dsp_set_features(dialog->dsp, DSP_FEATURE_DIGIT_DETECT); + } else { + ast_dsp_free(dialog->dsp); + dialog->dsp = NULL; + } + } else { + ast_debug(1, "Detected inband DTMF '%c'\n", frame->subclass.integer); + } + } + } + + return frame; +} + +/* Send frame to media channel (rtp) */ +int sip_channel_write(struct ast_channel *channel, struct ast_frame *frame) +{ + struct sip_dialog *dialog; + char type[64]; + int res; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to write to channel '%s' with no tech_pvt\n", ast_channel_name(channel)); + return 0; + } + + ao2_lock(dialog); + + switch (frame->frametype) { + case AST_FRAME_VOICE: + if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(channel), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { + struct ast_str *format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + + ast_log(LOG_WARNING, + "Asked to transmit audio frame type '%s' while native formats are '%s' and read/write formats are '%s/%s'\n", + ast_format_get_name(frame->subclass.format), + ast_format_cap_get_names(ast_channel_nativeformats(channel), &format_names), + ast_format_get_name(ast_channel_readformat(channel)), + ast_format_get_name(ast_channel_writeformat(channel))); + + res = -1; + } else if (dialog->fax_state == SIP_FAX_ENABLED) { + /* Drop frame, can't sent VOICE frames while in T.38 mode */ + res = 0; + } else if (dialog->audio_rtp) { + /* If channel is not up, activate early media session */ + if (ast_channel_state(channel) != AST_STATE_UP && !dialog->sent_progress && !dialog->outgoing) { + ast_rtp_instance_update_source(dialog->audio_rtp); + + if (!sip_config.allow_early_media) { + dialog->invite_state = SIP_INVITE_EARLY_MEDIA; + dialog->sent_progress = TRUE; + + sip_response_send_provisional(dialog, "183 Session Progress", + &dialog->initial_request, TRUE); + } + } + + if (dialog->invite_state > SIP_INVITE_EARLY_MEDIA || + (dialog->invite_state == SIP_INVITE_EARLY_MEDIA && dialog->sent_progress)) { + dialog->last_rtp_sent = ast_tvnow(); + res = ast_rtp_instance_write(dialog->audio_rtp, frame); + } else { + res = 0; + } + } else { + res = -1; + } + + break; + case AST_FRAME_VIDEO: + if (dialog->video_rtp) { + /* Activate video early media */ + if ((ast_channel_state(channel) != AST_STATE_UP) && !dialog->sent_progress && !dialog->outgoing) { + dialog->invite_state = SIP_INVITE_EARLY_MEDIA; + dialog->sent_progress = TRUE; + + sip_response_send_provisional(dialog, "183 Session Progress", + &dialog->initial_request, TRUE); + } + + if (dialog->invite_state > SIP_INVITE_EARLY_MEDIA || + (dialog->invite_state == SIP_INVITE_EARLY_MEDIA && dialog->sent_progress)) { + dialog->last_rtp_sent = ast_tvnow(); + res = ast_rtp_instance_write(dialog->video_rtp, frame); + } else { + res = 0; + } + } else { + res = -1; + } + + break; + case AST_FRAME_TEXT: + if (dialog->fax_red) { + ast_rtp_red_buffer(dialog->text_rtp, frame); + res = 0; + } else if (dialog->text_rtp) { + /* Activate text early media */ + if ((ast_channel_state(channel) != AST_STATE_UP) && !dialog->sent_progress && !dialog->outgoing) { + dialog->invite_state = SIP_INVITE_EARLY_MEDIA; + dialog->sent_progress = TRUE; + + sip_response_send_provisional(dialog, "183 Session Progress", + &dialog->initial_request, TRUE); + } + + if (dialog->invite_state > SIP_INVITE_EARLY_MEDIA || + (dialog->invite_state == SIP_INVITE_EARLY_MEDIA && dialog->sent_progress)) { + dialog->last_rtp_sent = ast_tvnow(); + res = ast_rtp_instance_write(dialog->text_rtp, frame); + } else { + res = 0; + } + } else { + res = -1; + } + + break; + case AST_FRAME_IMAGE: + res = 0; + break; + case AST_FRAME_MODEM: + /* UDPTL requires two-way communication, so early media is not needed here. we simply forget the frames + * if we get modem frames before the bridge is up. Fax will re-transmit */ + if (ast_channel_state(channel) == AST_STATE_UP && dialog->udptl && dialog->fax_state == SIP_FAX_ENABLED) { + res = ast_udptl_write(dialog->udptl, frame); + } else { + res = 0; + } + + break; + default: + ast_log(LOG_WARNING, "Unable to write '%s' frame to '%s'\n", + ast_frame_type2str(frame->frametype, type, sizeof(type)), ast_channel_name(channel)); + res = -1; + break; + } + + ao2_unlock(dialog); + return res; +} + +/* Fix up a channel: If a channel is consumed, this is called. Basically update any ->owner links */ +int sip_channel_fixup(struct ast_channel *old_channel, struct ast_channel *new_channel) +{ + struct sip_dialog *dialog; + + if (new_channel && ast_test_flag(ast_channel_flags(new_channel), AST_FLAG_ZOMBIE)) { + ast_debug(1, "New channel is zombie\n"); + } + + if (old_channel && ast_test_flag(ast_channel_flags(old_channel), AST_FLAG_ZOMBIE)) { + ast_debug(1, "Old channel is zombie\n"); + } + + if (!new_channel) { + ast_log(LOG_WARNING, "No new channel so fixup of '%s' failed\n", ast_channel_name(old_channel)); + return -1; + } + + if (!(dialog = ast_channel_tech_pvt(new_channel))) { + ast_log(LOG_WARNING, "No SIP tech_pvt so fixup of '%s' failed\n", ast_channel_name(old_channel)); + return -1; + } + + ao2_lock(dialog); + + if (dialog->channel != old_channel) { + ast_log(LOG_WARNING, "Old channel was not '%p' but instead was '%p'\n", old_channel, dialog->channel); + ao2_unlock(dialog); + return -1; + } + + sip_dialog_set_channel(dialog, new_channel); + /* Re-invite RTP back to Asterisk. Needed if channel is masqueraded out of a native RTP bridge (i.e., RTP not + * going through Asterisk): RTP bridge code might not be able to do this if the masquerade happens before the + * bridge breaks (e.g., AMI redirect of both channels). Note that a channel can not be masqueraded *into* native + * bridge. So there is no danger that this breaks a native bridge that should stay up */ + sip_rtp_update_peer(new_channel, NULL, NULL, NULL, NULL, 0); + + ast_debug(3, "New channel for '%s' is '%s' old channel was '%s'\n", + dialog->call_id, ast_channel_name(dialog->channel), ast_channel_name(old_channel)); + ao2_unlock(dialog); + return 0; +} + +int sip_channel_send_digit_begin(struct ast_channel *channel, char digit) +{ + struct sip_dialog *dialog; + int res; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to begin DTMF digit on channel '%s' with no tech_pvt\n", + ast_channel_name(channel)); + return -1; + } + + ao2_lock(dialog); + + switch (dialog->peer->dtmf_mode) { + case SIP_DTMF_MODE_RFC2833: + if (dialog->audio_rtp) { + ast_rtp_instance_dtmf_begin(dialog->audio_rtp, digit); + } + + res = 0; + break; + case SIP_DTMF_MODE_INBAND: + default: + res = -1; /* Tell Asterisk to generate inband indications */ + break; + } + + ao2_unlock(dialog); + return res; +} + +/* Send DTMF character on SIP channel within one call, we're able to transmit in many methods simultaneously */ +int sip_channel_send_digit_end(struct ast_channel *channel, char digit, unsigned int duration) +{ + struct sip_dialog *dialog; + int res; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to end DTMF digit on channel '%s' with no tech_pvt\n", + ast_channel_name(channel)); + return -1; + } + + ao2_lock(dialog); + + switch (dialog->peer->dtmf_mode) { + case SIP_DTMF_MODE_RFC2833: + if (dialog->audio_rtp) { + ast_rtp_instance_dtmf_end_with_duration(dialog->audio_rtp, digit, duration); + } + + res = 0; + break; + case SIP_DTMF_MODE_INBAND: + default: + res = -1; /* Tell Asterisk to stop inband indications */ + break; + } + + ao2_unlock(dialog); + return res; +} + +/* Set an option on a SIP dialog */ +int sip_channel_setoption(struct ast_channel *channel, int option, void *data, int data_len) +{ + struct sip_dialog *dialog; + int res; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to set option on channel '%s' with no tech_pvt\n", + ast_channel_name(channel)); + return -1; + } + + ao2_lock(dialog); + + switch (option) { + case AST_OPTION_FORMAT_READ: + if (dialog->audio_rtp) { + res = ast_rtp_instance_set_read_format(dialog->audio_rtp, *(struct ast_format **) data); + } else { + res = -1; + } + + break; + case AST_OPTION_FORMAT_WRITE: + if (dialog->audio_rtp) { + res = ast_rtp_instance_set_write_format(dialog->audio_rtp, *(struct ast_format **) data); + } else { + res = -1; + } + + break; + case AST_OPTION_DIGIT_DETECT: + if (dialog->peer->dtmf_mode == SIP_DTMF_MODE_INBAND) { + sip_dialog_set_dsp_detect(dialog, *(char *) data ? TRUE : FALSE); + } + + res = 0; + break; + case AST_OPTION_SECURE_SIGNALING: + dialog->secure_signaling = *(unsigned int *) data; + res = 0; + + break; + case AST_OPTION_SECURE_MEDIA: + dialog->secure_media = *(unsigned int *) data; + res = 0; + + break; + default: + ast_log(LOG_WARNING, "Unable to set option '%d' on channel '%s'\n", option, ast_channel_name(channel)); + res = -1; + break; + } + + ao2_unlock(dialog); + return res; +} + +/* Query an option on a SIP dialog */ +int sip_channel_queryoption(struct ast_channel *channel, int option, void *data, int *data_len) +{ + struct sip_dialog *dialog; + int res; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to query option on channel '%s' with no tech_pvt\n", + ast_channel_name(channel)); + return -1; + } + + ao2_lock(dialog); + + switch (option) { + case AST_OPTION_DEVICE_NAME: + if (dialog->originated_call) { + ast_copy_string((char *) data, dialog->device_name, *data_len); + } else { + snprintf((char *) data, *data_len, "SIP/%s", dialog->peer->name); + } + + res = 0; + break; + case AST_OPTION_DIGIT_DETECT: + *((char *) data) = dialog->dsp ? TRUE : FALSE; + res = 0; + + break; + case AST_OPTION_SECURE_SIGNALING: + *((unsigned int *) data) = dialog->secure_signaling; + res = 0; + + break; + case AST_OPTION_SECURE_MEDIA: + *((unsigned int *) data) = dialog->secure_media; + res = 0; + + break; + case AST_OPTION_T38_STATE: + /* Now if T38 support is enabled we need to look and see what the current state is to get what we want + * to report back */ + if (dialog->peer->fax_support) { + switch (dialog->fax_state) { + case SIP_FAX_LOCAL_REINVITE: + case SIP_FAX_REMOTE_REINVITE: + *((enum ast_t38_state *) data) = T38_STATE_NEGOTIATING; + break; + case SIP_FAX_ENABLED: + *((enum ast_t38_state *) data) = T38_STATE_NEGOTIATED; + break; + case SIP_FAX_REJECTED: + *((enum ast_t38_state *) data) = T38_STATE_REJECTED; + break; + default: + *((enum ast_t38_state *) data) = T38_STATE_UNKNOWN; + break; + } + } else { + *((enum ast_t38_state *) data) = T38_STATE_UNAVAILABLE; + } + + res = 0; + break; + default: + ast_log(LOG_WARNING, "Unknown query option '%d' on channel '%s'\n", option, ast_channel_name(channel)); + res = -1; + break; + } + + ao2_unlock(dialog); + return res; +} + +/* Transfer SIP call */ +int sip_channel_transfer(struct ast_channel *channel, const char *destination) +{ + struct sip_dialog *dialog; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to transfer channel '%s' with no tech_pvt\n", ast_channel_name(channel)); + return -1; + } + + if (ast_strlen_zero(destination)) { + ast_log(LOG_WARNING, "Unable to transfer channel to empty destination\n"); + return -1; + } + + ao2_lock(dialog); + + /* Transfer call before connect with a 302 redirect. Called by the Transfer() dialplan application through the + * sip_channel_transfer function if the call is in ringing state */ + if (ast_channel_state(channel) == AST_STATE_RING) { + char *user, *domain; + + user = ast_strdupa(destination); + + if ((domain = strchr(user, '@'))) { + *domain++ = '\0'; + } + + if (ast_strlen_zero(user)) { + ast_log(LOG_ERROR, "Missing mandatory argument: extension\n"); + ao2_unlock(dialog); + return -1; + } + + if (ast_strlen_zero(domain)) { + char *to = ast_strdupa(sip_message_find_header(&dialog->initial_request, "To")); + + if (ast_strlen_zero(to)) { + ast_log(LOG_ERROR, "Cannot retrieve the To: header from the original SIP request!\n"); + ao2_unlock(dialog); + return -1; + } + + if (sip_parse_contact(to, NULL, NULL, &domain, NULL, NULL)) { + ast_log(LOG_ERROR, "Unable to find the domain\n"); + ao2_unlock(dialog); + return -1; + } + + domain = strsep(&domain, ":"); + } + + ast_string_field_build(dialog, our_contact, "", user, domain); + sip_response_send_reliable(dialog, "302 Moved Temporarily", &dialog->initial_request); + + sip_dialog_sched_destroy(dialog, dialog->timer_b); /* Make sure we stop send this reply */ + sip_dialog_set_already_gone(dialog); + + if (dialog->channel) { + enum ast_control_transfer transfer = AST_TRANSFER_SUCCESS; + + ast_queue_control_data(dialog->channel, AST_CONTROL_TRANSFER, &transfer, sizeof(transfer)); + } + } else { + char *uri; + int use_tls; + + ast_debug(1, "Transfer on '%s' to '%s'\n", dialog->call_id, destination); + + /* Are we transfering an inbound or outbound call ? */ + uri = ast_strdupa(sip_message_find_header(&dialog->initial_request, dialog->outgoing ? "To" : "From")); + uri = sip_get_uri(uri); + + if (!strncasecmp(uri, "sips:", 5)) { + use_tls = TRUE; + } else { + use_tls = FALSE; + } + + ast_string_field_build(dialog, refer_to, "", use_tls ? "s" : "", destination); + ast_string_field_set(dialog, referred_by, dialog->our_contact); + + sip_request_send_refer(dialog, SIP_INIT_NONE); + } + + ao2_unlock(dialog); + return 0; +} + +/* Play indication to user. With SIP a lot of indications is sent as messages, letting the device play the indication + * - busy signal, congestion etc */ +int sip_channel_indicate(struct ast_channel *channel, int condition, const void *data, size_t data_len) +{ + struct sip_dialog *dialog; + int res; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to indicate condition on channel '%s' with no tech_pvt\n", + ast_channel_name(channel)); + return -1; + } + + ao2_lock(dialog); + + switch (condition) { + case AST_CONTROL_INCOMPLETE: + if (ast_channel_state(channel) != AST_STATE_UP) { + switch (dialog->peer->allow_overlap) { + case SIP_ALLOW_OVERLAP_INVITE: + dialog->invite_state = SIP_INVITE_COMPLETED; + + sip_response_send_reliable(dialog, "484 Address Incomplete", &dialog->initial_request); + sip_dialog_set_already_gone(dialog); + + ast_softhangup_nolock(channel, AST_SOFTHANGUP_DEV); + break; + case SIP_ALLOW_OVERLAP_DTMF: + /* Just wait for inband DTMF digits */ + break; + default: + dialog->invite_state = SIP_INVITE_COMPLETED; + + /* It actually means no support for overlap */ + sip_response_send_reliable(dialog, "404 Not Found", &dialog->initial_request); + sip_dialog_set_already_gone(dialog); + + ast_softhangup_nolock(channel, AST_SOFTHANGUP_DEV); + break; + } + } + + res = 0; + break; + case AST_CONTROL_PROCEEDING: + if (ast_channel_state(channel) != AST_STATE_UP && !dialog->sent_progress && !dialog->outgoing) { + dialog->invite_state = SIP_INVITE_PROCEEDING; + + sip_response_send(dialog, "100 Trying", &dialog->initial_request); + res = 0; + } else { + res = -1; + } + + break; + case AST_CONTROL_RINGING: + if (ast_channel_state(channel) == AST_STATE_RING && !dialog->sent_progress && !dialog->outgoing) { + dialog->invite_state = SIP_INVITE_EARLY_MEDIA; + dialog->sent_ringing = TRUE; + + /* Send 180 ringing if out-of-band seems reasonable */ + sip_response_send_provisional(dialog, "180 Ringing", &dialog->initial_request, FALSE); + res = 0; + } else { + res = -1; + } + + break; + case AST_CONTROL_PROGRESS: + if (ast_channel_state(channel) != AST_STATE_UP && !dialog->sent_progress && !dialog->outgoing) { + dialog->invite_state = SIP_INVITE_EARLY_MEDIA; + dialog->sent_progress = TRUE; + + sip_response_send_provisional(dialog, "183 Session Progress", &dialog->initial_request, TRUE); + res = 0; + } else { + res = -1; + } + + break; + case AST_CONTROL_BUSY: + if (ast_channel_state(channel) != AST_STATE_UP) { + dialog->invite_state = SIP_INVITE_COMPLETED; + + sip_response_send_reliable(dialog, "486 Busy Here", &dialog->initial_request); + sip_dialog_set_already_gone(dialog); + + ast_softhangup_nolock(channel, AST_SOFTHANGUP_DEV); + res = 0; + } else { + res = -1; + } + + break; + case AST_CONTROL_CONGESTION: + if (ast_channel_state(channel) != AST_STATE_UP) { + dialog->invite_state = SIP_INVITE_COMPLETED; + + sip_response_send_reliable(dialog, "503 Service Unavailable", &dialog->initial_request); + sip_dialog_set_already_gone(dialog); + + ast_softhangup_nolock(channel, AST_SOFTHANGUP_DEV); + res = 0; + } else { + res = -1; + } + + break; + case AST_CONTROL_HOLD: + ast_rtp_instance_update_source(dialog->audio_rtp); + ast_moh_start(channel, data, dialog->peer->moh_interpret); + + res = 0; + break; + case AST_CONTROL_UNHOLD: + ast_rtp_instance_update_source(dialog->audio_rtp); + ast_moh_stop(channel); + + res = 0; + break; + case AST_CONTROL_VIDUPDATE: /* Request a video frame update */ + if (dialog->video_rtp && !dialog->no_video_support) { + /* Only use this for VP8. Additional work would have to be done to fully support other + * video codecs */ + if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(channel), ast_format_vp8) != AST_FORMAT_CMP_NOT_EQUAL) { + /* Fake RTP write, this will be sent as an RTCP packet. Ideally the RTP engine + * would provide a way to externally write/schedule RTCP packets */ + struct ast_frame frame = { + .frametype = AST_FRAME_CONTROL, + .subclass.integer = AST_CONTROL_VIDUPDATE + }; + + res = ast_rtp_instance_write(dialog->video_rtp, &frame); + } else { + res = sip_request_send_info_with_media_control(dialog); + } + } else { + res = -1; + } + + break; + case AST_CONTROL_SRCUPDATE: + ast_rtp_instance_update_source(dialog->audio_rtp); + res = 0; + break; + case AST_CONTROL_SRCCHANGE: + ast_rtp_instance_change_source(dialog->audio_rtp); + res = 0; + break; + case AST_CONTROL_CONNECTED_LINE: + sip_dialog_update_connected_line(dialog); + res = 0; + break; + case AST_CONTROL_REDIRECTING: + sip_dialog_update_redirecting(dialog); + res = 0; + break; + case AST_CONTROL_T38_PARAMETERS: + if (data_len == sizeof(struct ast_control_t38_parameters)) { + res = sip_fax_update(dialog, (struct ast_control_t38_parameters *) data); + } else { + res = -1; + } + + break; + case AST_CONTROL_UPDATE_RTP_PEER: /* Absorb this since it is handled by the bridge */ + res = 0; + break; + case AST_CONTROL_FLASH: /* We don't currently handle AST_CONTROL_FLASH here, but it is expected, so we don't + * need to warn either */ + case AST_CONTROL_AOC: + case AST_CONTROL_MCID: + case AST_CONTROL_PVT_CAUSE_CODE: /* These should be handled by the code in channel.c */ + case AST_CONTROL_MASQUERADE_NOTIFY: + case AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE: /* No support for topology change */ + case AST_CONTROL_STREAM_TOPOLOGY_CHANGED: + case AST_CONTROL_STREAM_TOPOLOGY_SOURCE_CHANGED: + res = -1; + break; + default: + if (condition != -1) { + ast_log(LOG_WARNING, "Don't know how to indicate condition %d on '%s'\n", condition, + ast_channel_name(channel)); + } + + res = -1; + break; + } + + ao2_unlock(dialog); + return res; +} + +/* Part of PBX channel interface. If we have qualify on and the device is not reachable, regardless of registration + * state we return AST_DEVICE_UNAVAILABLE + * + * For peers with max calls: + * - not registered AST_DEVICE_UNAVAILABLE + * - registered, no call AST_DEVICE_NOT_INUSE + * - registered, active calls AST_DEVICE_INUSE + * - registered, max calls reached AST_DEVICE_BUSY + * - registered, onhold AST_DEVICE_ONHOLD + * - registered, ringing AST_DEVICE_RINGING + * + * For peers without max calls: + * - not registered AST_DEVICE_UNAVAILABLE + * - registered AST_DEVICE_NOT_INUSE + * - fixed IP (not dynamic) AST_DEVICE_NOT_INUSE + * + * Peers that do not have a call and can't be reached by OPTIONS + * - unreachable AST_DEVICE_UNAVAILABLE + * + * If we return AST_DEVICE_UNKNOWN, the device state engine will try to find out a state by walking the channel list. + * + * The queue system treats a member as "active" if device state is not AST_DEVICE_UNAVAILBALE or AST_DEVICE_INVALID + * When placing a call to the queue member, queue system sets a member to busy not AST_DEVICE_NOT_INUSE and noti + * AST_DEVICE_UNKNOWN */ +int sip_channel_devicestate(const char *name) +{ + RAII_VAR(struct sip_peer *, peer, NULL, ao2_cleanup); + int res; + + ast_debug(3, "Checking device state for peer %s\n", name); + + /* If sip_peer_find asks for a realtime peer, then this breaks rtautoclear. This is because when a peer tries + * to autoexpire, the last thing it does is to queue up an event telling the system that the devicestate has + * changed (presumably to unavailable). If we ask for a realtime peer here, this would load it BACK into memory, + * thus defeating the point of trying to clear dead hosts out of memory */ + if (!(peer = sip_peer_find(name, FALSE, TRUE))) { + return AST_DEVICE_INVALID; + } + + /* We have an address for the peer */ + if (ast_sockaddr_isnull(&peer->address)) { + /* There is no address, it's unavailable */ + return AST_DEVICE_UNAVAILABLE; + } + + /* Check status in this order: + * - Hold + * - Ringing + * - Busy (enforced only by call limit) + * - Inuse (we have a call) + * - Unreachable (qualify) + * If we don't find any of these state, report AST_DEVICE_NOT_INUSE for registered devices */ + if (peer->onhold) { + /* First check for hold or ring states */ + res = AST_DEVICE_ONHOLD; + } else if (peer->ringing) { + if (peer->ringing == peer->inuse) { + res = AST_DEVICE_RINGING; + } else { + res = AST_DEVICE_RINGINUSE; + } + } else if (peer->max_calls && (peer->inuse == peer->max_calls)) { + /* Check the max calls */ + res = AST_DEVICE_BUSY; + } else if (peer->max_calls && peer->busy_level && peer->inuse >= peer->busy_level) { + /* We're forcing busy before we've reached the call limit */ + res = AST_DEVICE_BUSY; + } else if (peer->max_calls && (peer->inuse || peer->offhook)) { + /* Not busy, but we do have a call */ + res = AST_DEVICE_INUSE; + } else if (peer->qualify_max && (peer->qualify > peer->qualify_max || peer->qualify == -1)) { + /* We don't have a call. Are we reachable at all? Requires qualify= */ + res = AST_DEVICE_UNAVAILABLE; + } else { /* Default reply if we're registered and have no other data */ + res = AST_DEVICE_NOT_INUSE; + } + + return res; +} + +int sip_channel_presencestate(const char *name, char **subtype, char **message) +{ + RAII_VAR(struct sip_peer *, peer, NULL, ao2_cleanup); + int res; + + ast_debug(3, "Checking presence state for peer %s\n", name); + + if (!(peer = sip_peer_find(name, FALSE, TRUE))) { + return AST_PRESENCE_INVALID; + } + + if (ast_sockaddr_isnull(&peer->address)) { + return AST_PRESENCE_UNAVAILABLE; + } + + if (peer->do_not_disturb) { + res = AST_PRESENCE_DND; + } else { + res = AST_PRESENCE_AVAILABLE; + } + + return res; +} + +/* Deliver SIP call ID for the call */ +const char *sip_channel_get_pvt_uniqueid(struct ast_channel *channel) +{ + struct sip_dialog *dialog = ast_channel_tech_pvt(channel); + + return dialog ? dialog->call_id : ""; +} + +/* Send SIP MESSAGE text within a call. Called from PBX core sendtext() application */ +int sip_channel_send_text(struct ast_channel *channel, const char *text) +{ + struct sip_dialog *dialog; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to send text on channel '%s' with no tech_pvt\n", ast_channel_name(channel)); + return -1; + } + + /* NOT ast_strlen_zero, because a zero-length message is specifically allowed by RFC 3428, section 10 */ + if (!text) { + return -1; + } + + if (!(dialog->allow_methods & SIP_METHOD_MESSAGE)) { + ast_debug(2, "Trying to send MESSAGE to device that does not support that method\n"); + return -1; + } + + /* Setup to send text message */ + ao2_lock(dialog); + + ast_variables_destroy(dialog->message_headers); + dialog->message_headers = NULL; + ast_string_field_set(dialog, message_content, text); + + sip_request_send_message(dialog, SIP_INIT_NONE); + ao2_unlock(dialog); + + return 0; +} + +/* Send message with Access-URL header, if this is an HTML URL only! */ +int sip_channel_send_html(struct ast_channel *channel, int subclass, const char *data, int data_len) +{ + struct sip_dialog *dialog; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to send html on channel '%s' with no tech_pvt\n", ast_channel_name(channel)); + return -1; + } + + if (subclass != AST_HTML_URL) { + return -1; + } + + ao2_lock(dialog); + ast_string_field_build(dialog, html_url, "<%s>;mode=active", data); + + switch (ast_channel_state(channel)) { + case AST_STATE_RING: + sip_response_send(dialog, "100 Trying", &dialog->initial_request); + break; + case AST_STATE_RINGING: + sip_response_send(dialog, "180 Ringing", &dialog->initial_request); + break; + case AST_STATE_UP: + if (!dialog->pending_invite_cseq) { /* We are up, and have no outstanding invite */ + sip_request_send_reinvite_with_sdp(dialog, FALSE, FALSE); + } else if (!dialog->pending_bye) { + dialog->need_reinvite = TRUE; + } + + break; + default: + ast_log(LOG_WARNING, "Don't know how to send URI when channel is %s\n", + ast_state2str(ast_channel_state(channel))); + break; + } + + ao2_unlock(dialog); + return 0; +} + +int sip_api_sipinfo_send(struct ast_channel *channel, struct ast_variable *headers, const char *content_type, + const char *content, const char *useragent_filter) +{ + struct sip_dialog *dialog; + struct ast_variable *header; + struct sip_message request; + int res; + + ast_channel_lock(channel); + + if (ast_channel_tech(channel) != &sip_channel_tech) { + ast_log(LOG_WARNING, "Attempted to send a custom INFO on a non-SIP channel %s\n", + ast_channel_name(channel)); + ast_channel_unlock(channel); + return -1; + } + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to send SIP INFO on channel '%s' with no tech_pvt\n", + ast_channel_name(channel)); + return -1; + } + + ao2_lock(dialog); + + if (!ast_strlen_zero(useragent_filter) && strcasestr(dialog->useragent, useragent_filter)) { + res = -1; + } else { + sip_message_build_request(&request, dialog, SIP_METHOD_INFO, 0, TRUE); + + for (header = headers; header; header = header->next) { + sip_message_add_header(&request, header->name, header->value); + } + + if (!ast_strlen_zero(content) && !ast_strlen_zero(content_type)) { + sip_message_add_header(&request, "Content-Type", content_type); + sip_message_add_content(&request, content); + } + + res = sip_message_send(dialog, &request, SIP_SEND_RELIABLE, dialog->outgoing_cseq); + } + + ao2_unlock(dialog); + ast_channel_unlock(channel); + + return res; +} + +int sip_msg_send(const struct ast_msg *msg, const char *to, const char *from) +{ + RAII_VAR(struct sip_dialog *, dialog, NULL, ao2_cleanup); + char *uri, *domain, *user; + const char *name, *value; + struct ast_msg_var_iterator *iter; + struct sip_peer *peer; + + uri = ast_strdupa(to); + + if (sip_parse_uri(uri, NULL, &user, &domain, NULL, NULL)) { + ast_log(LOG_WARNING, "MESSAGE(to) does not begin with sip: '%s'\n", to); + return -1; + } + + domain = strsep(&domain, ":"); + + if (ast_strlen_zero(domain)) { + ast_log(LOG_WARNING, "MESSAGE(to) is invalid for SIP '%s'\n", to); + return -1; + } + + if (!(dialog = sip_dialog_alloc(NULL, NULL, SIP_METHOD_MESSAGE, NULL, 0))) { + return -1; + } + + if (!ast_strlen_zero(from)) { + if ((peer = sip_peer_find(from, FALSE, FALSE))) { + ast_string_field_set(dialog, from_name, S_OR(peer->caller_name, peer->name)); + ast_string_field_set(dialog, from_user, S_OR(peer->caller_number, peer->name)); + + ao2_ref(peer, -1); + } else if (strchr(from, '<')) { /* From is callerid-style */ + char *callerid, *caller_name, *caller_number, *user, *domain; + + callerid = ast_strdupa(from); + ast_callerid_parse(callerid, &caller_name, &caller_number); + + if (ast_strlen_zero(caller_number)) { + /* This can occur if either + * 1) A name-addr style From header does not close the angle brackets properly. + * 2) The From header is not in name-addr style and the content of the From contains + * characters other than 0-9, *, #, or +. + * In both cases, ast_callerid_parse() should have parsed the From header as a name + * rather than a number. So we just need to set the location to what was parsed as a + * name, and set the name NULL since there was no name present */ + caller_number = caller_name; + caller_name = NULL; + } + + ast_string_field_set(dialog, from_name, caller_name); + + if ((user = strchr(caller_number, ':'))) { /* Must be a URI */ + *user++ = '\0'; + + if ((domain = strchr(user, '@'))) { + *domain++ = '\0'; + } + + ast_string_field_set(dialog, from_user, user); + ast_string_field_set(dialog, from_domain, domain); + } else { /* Treat it as an exten/user */ + ast_string_field_set(dialog, from_user, caller_number); + } + } else { + /* Assume we just have the name, use defaults for the rest */ + ast_string_field_set(dialog, from_name, from); + } + } + + /* Look up the host to contact */ + if (sip_dialog_build(dialog, domain, NULL, TRUE)) { + sip_dialog_unlink(dialog); + return -1; + } + + if (!ast_strlen_zero(user)) { + ast_string_field_set(dialog->peer, authorization_user, user); + } + + sip_dialog_set_our_address(dialog); + dialog->outgoing = TRUE; + + /* Save additional MESSAGE headers in case of authentication request */ + iter = ast_msg_var_iterator_init(msg); + + while (ast_msg_var_iterator_next(msg, iter, &name, &value)) { + if (!strcasecmp(name, "Request-URI")) { + ast_string_field_set(dialog, contact, value); + } else if (!strcasecmp(name, "Max-Forwards")) { + /* Decrement Max-Forwards for SIP loop prevention */ + if (sscanf(value, "%30d", &dialog->max_forwards) != 1 || dialog->max_forwards < 1) { + ast_msg_var_iterator_destroy(iter); + + ao2_unlock(dialog); + sip_dialog_unlink(dialog); + ao2_ref(dialog, -1); + + ast_log(LOG_NOTICE, "MESSAGE(Max-Forwards) reached zero. MESSAGE not sent\n"); + return -1; + } + + dialog->max_forwards--; + } else if (!strcasecmp(name, "To") || !strcasecmp(name, "From") || !strcasecmp(name, "Via") || + !strcasecmp(name, "Contact") || !strcasecmp(name, "Call-ID") || !strcasecmp(name, "CSeq") || + !strcasecmp(name, "Allow") || !strcasecmp(name, "Content-Length")) { + ast_debug(3, "Ignoring msg var '%s'\n", name); + } else { + ast_variable_list_append(&dialog->message_headers, ast_variable_new(name, value, "")); + } + + ast_msg_var_unref_current(iter); + } + + ast_msg_var_iterator_destroy(iter); + ast_string_field_set(dialog, message_content, ast_msg_get_body(msg)); + + sip_request_send_message(dialog, SIP_INIT_REQUEST); + + sip_dialog_sched_destroy(dialog, dialog->timer_b); + ao2_unlock(dialog); + return 0; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/cli_commands.c asterisk-22.6.0/channels/sip/cli_commands.c --- asterisk-22.6.0.orig/channels/sip/cli_commands.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/cli_commands.c 2025-10-21 18:38:17.436218519 +1300 @@ -0,0 +1,2210 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include + +#include "asterisk.h" +#include "asterisk/astobj2.h" +#include "asterisk/localtime.h" +#include "asterisk/linkedlists.h" +#include "asterisk/tcptls.h" +#include "asterisk/channel.h" +#include "asterisk/callerid.h" +#include "asterisk/indications.h" +#include "asterisk/rtp_engine.h" +#include "asterisk/pbx.h" +#include "asterisk/astdb.h" +#include "asterisk/acl.h" +#include "asterisk/cli.h" +#include "asterisk/udptl.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/session_timer.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/authentication_realms.h" +#include "include/domains.h" +#include "include/peers.h" +#include "include/registrations.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/cli_commands.h" +#include "include/manager.h" +#include "include/mwi_subscriptions.h" +#include "include/notifications.h" +#include "include/fax.h" + +/* CLI commands */ +struct ast_cli_entry sip_cli_commands[] = { + AST_CLI_DEFINE(sip_cli_show_calls, "List active SIP calls"), + AST_CLI_DEFINE(sip_cli_show_subscriptions, "List active SIP subscriptions"), + AST_CLI_DEFINE(sip_cli_show_dialog, "Show detailed SIP dialog information"), + AST_CLI_DEFINE(sip_cli_show_stats, "List statistics for active SIP calls"), + AST_CLI_DEFINE(sip_cli_show_domains, "List our local SIP domains"), + AST_CLI_DEFINE(sip_cli_show_inuse, "List SIP peer call in-use/busy limits"), + AST_CLI_DEFINE(sip_cli_show_peers, "List SIP peers"), + AST_CLI_DEFINE(sip_cli_show_registrations, "List SIP registrations"), + AST_CLI_DEFINE(sip_cli_show_mwi, "List MWI subscriptions"), + AST_CLI_DEFINE(sip_cli_show_settings, "Show SIP settings"), + AST_CLI_DEFINE(sip_cli_show_peer, "Show details on specific SIP peer"), + AST_CLI_DEFINE(sip_cli_show_tcp, "List TCP connections"), + AST_CLI_DEFINE(sip_cli_show_sched, "Present a report on the status of the scheduler queue"), + AST_CLI_DEFINE(sip_cli_show_objects, "List SIP object ref-counts"), + AST_CLI_DEFINE(sip_cli_unregister, "Unregister (force expiration) a SIP peer"), + AST_CLI_DEFINE(sip_cli_notify, "Send a NOTIFY request to a SIP peer"), + AST_CLI_DEFINE(sip_cli_do_not_disturb, "Enables/Disables do not disturb on a SIP peer"), + AST_CLI_DEFINE(sip_cli_call_forward, "Sets/Removes the call forwarding extension for a SIP peer"), + AST_CLI_DEFINE(sip_cli_hunt_group, "Login to/Logout from Hunt Group for a SIP peer"), + AST_CLI_DEFINE(sip_cli_qualify_peer, "Send an OPTIONS request to a peer"), + AST_CLI_DEFINE(sip_cli_prune_realtime, "Prune cached realtime peers"), + AST_CLI_DEFINE(sip_cli_set_debug, "Enable/Disable SIP debugging"), + AST_CLI_DEFINE(sip_cli_reload, "Reload SIP configuration"), +}; + +const int sip_cli_commands_count = ARRAY_LEN(sip_cli_commands); + +static char *sip_cli_complete_peer(struct ast_cli_args *args, int realtime); +static char *sip_cli_complete_call_id(struct ast_cli_args *args); +static char *sip_cli_complete_notify(struct ast_cli_args *args); +static int sip_cli_peer_cmp(const void *arg1, const void *arg2); + +/* Completion for peer names */ +static char *sip_cli_complete_peer(struct ast_cli_args *args, int realtime) +{ + int word_len, arg_number; + struct ao2_iterator iter; + struct sip_peer *peer; + char *name; + + word_len = strlen(args->word); + arg_number = 0; + name = NULL; + + iter = ao2_iterator_init(sip_peers, 0); + + while (!name && (peer = ao2_iterator_next(&iter))) { + ao2_lock(peer); + + if (!strncasecmp(args->word, peer->name, word_len) && + (!realtime || peer->realtime_cache_peer) && ++arg_number > args->n) { + name = ast_strdup(peer->name); + } + + ao2_unlock(peer); + ao2_ref(peer, -1); + } + + ao2_iterator_destroy(&iter); + return name; +} + +/* Completetion for dialog call-id's */ +static char *sip_cli_complete_call_id(struct ast_cli_args *args) +{ + int word_len, arg_number; + struct sip_dialog *dialog; + char *call_id; + struct ao2_iterator iter; + + if (args->pos != 3) { + return NULL; + } + + word_len = strlen(args->word); + arg_number = 0; + call_id = NULL; + + iter = ao2_iterator_init(sip_dialogs, 0); + + while (!call_id && (dialog = ao2_iterator_next(&iter))) { + ao2_lock(dialog); + + if (!strncasecmp(args->word, dialog->call_id, word_len) && ++arg_number > args->n) { + call_id = ast_strdup(dialog->call_id); + } + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + } + + ao2_iterator_destroy(&iter); + return call_id; +} + +/* Completion for 'sip notify' types */ +static char *sip_cli_complete_notify(struct ast_cli_args *args) +{ + if (args->pos == 2) { + struct ao2_iterator iter; + struct sip_notification *notification; + int word_len, arg_number; + char *type; + + word_len = strlen(args->word); + arg_number = 0; + type = NULL; + + iter = ao2_iterator_init(sip_notifications, 0); + + while (!type && (notification = ao2_iterator_next(&iter))) { + if (!strncasecmp(args->word, notification->type, word_len) && ++arg_number > args->n) { + type = ast_strdup(notification->type); + } + + ao2_ref(notification, -1); + } + + ao2_iterator_destroy(&iter); + return type; + } + + if (args->pos > 2) { + return sip_cli_complete_peer(args, FALSE); + } + + return NULL; +} + +/* Compare peer names for sorting */ +static int sip_cli_peer_cmp(const void *arg1, const void *arg2) +{ + struct sip_peer **peer1 = (struct sip_peer **) arg1; + struct sip_peer **peer2 = (struct sip_peer **) arg2; + + return strcmp((*peer1)->name, (*peer2)->name); +} + +/* Force reload of module */ +char *sip_cli_reload(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + if (command == CLI_INIT) { + entry->command = "sip reload"; + entry->usage = "Usage: sip reload\n" + " Reloads SIP configuration from sip.conf\n"; + return NULL; + } else if (command == CLI_GENERATE) { + return NULL; + } + + sip_config_reload(CHANNEL_MODULE_RELOAD); + return CLI_SUCCESS; +} + +/* Turn on SIP debugging */ +char *sip_cli_set_debug(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + int old_sip_debug; + + if (command == CLI_INIT) { + entry->command = "sip set debug {on|off|ip|peer}"; + entry->usage = "Usage: sip set debug {off|on|ip addr[:port]|peer peername}\n" + " Globally disables dumping of SIP packets,\n" + " or enables it either globally or for a (single)\n" + " IP address or registered peer.\n"; + return NULL; + } else if (command == CLI_GENERATE) { + if (args->pos == 4 && !strcasecmp(args->argv[3], "peer")) { + return sip_cli_complete_peer(args, FALSE); + } + + return NULL; + } + + old_sip_debug = sip_debug & SIP_DEBUG_CONSOLE; + + if (args->argc == entry->args) { /* Arg is on/off */ + if (!strcasecmp(args->argv[3], "on")) { + sip_debug |= SIP_DEBUG_CONSOLE; + ast_sockaddr_setnull(&sip_debug_address); + + ast_cli(args->fd, "SIP debugging %senabled\n", old_sip_debug ? "re-" : ""); + return CLI_SUCCESS; + } else if (!strcasecmp(args->argv[3], "off")) { + sip_debug &= ~SIP_DEBUG_CONSOLE; + + if (!sip_debug) { + ast_cli(args->fd, "SIP debugging disabled\n"); + } else { + ast_cli(args->fd, "SIP debugging still enabled due to configuration\n"); + ast_cli(args->fd, "Set debug=no in sip.conf [general] and reload to actually disable\n"); + } + + return CLI_SUCCESS; + } + } else if (args->argc == entry->args + 1) { /* IP or peer */ + if (!strcasecmp(args->argv[3], "ip")) { + if (ast_sockaddr_resolve_first_af(&sip_debug_address, args->argv[4], 0, AST_AF_INET)) { + return CLI_SHOWUSAGE; + } + + sip_debug |= SIP_DEBUG_CONSOLE; + ast_cli(args->fd, "SIP debugging enabled for IP: %s\n", + ast_sockaddr_stringify_addr(&sip_debug_address)); + } else if (!strcasecmp(args->argv[3], "peer")) { + RAII_VAR(struct sip_peer *, peer, NULL, ao2_cleanup); + + peer = sip_peer_find(args->argv[4], TRUE, FALSE); + + if (!peer) { + ast_cli(args->fd, "Peer '%s' not found\n", args->argv[4]); + } else if (ast_sockaddr_isnull(&peer->address)) { + ast_cli(args->fd, "Unable to get IP address of peer '%s'\n", args->argv[4]); + } else { + ast_sockaddr_copy(&sip_debug_address, &peer->address); + ast_sockaddr_set_port(&sip_debug_address, 0); + + sip_debug |= SIP_DEBUG_CONSOLE; + ast_cli(args->fd, "SIP debugging enabled for '%s'\n", + ast_sockaddr_stringify_addr(&sip_debug_address)); + } + } + + return CLI_SUCCESS; + } + + return CLI_SHOWUSAGE; +} + +/* Unregister (force expiration) a peer. This function does not tell the SIP device what's going */ +char *sip_cli_unregister(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct sip_peer *peer; + + if (command == CLI_INIT) { + entry->command = "sip unregister"; + entry->usage = "Usage: sip unregister \n" + " Unregister (force expiration) a SIP peer\n"; + return NULL; + } else if (command == CLI_GENERATE) { + return sip_cli_complete_peer(args, FALSE); + } + + if (args->argc != 3) { + return CLI_SHOWUSAGE; + } + + if ((peer = sip_peer_find(args->argv[2], FALSE, TRUE))) { + if (peer->register_expires_sched_id != -1) { + ao2_ref(peer, +1); /* 'sip_peer_unregister' decreases ref-count */ + sip_peer_unregister(peer); + ast_cli(args->fd, "Unregistered SIP peer \'%s\'\n\n", args->argv[2]); + } else { + ast_cli(args->fd, "Peer '%s' not registered\n", args->argv[2]); + } + + ao2_ref(peer, -1); + } else { + ast_cli(args->fd, "Peer '%s' not found\n", args->argv[2]); + } + + return CLI_SUCCESS; +} + +/* Remove temporary realtime objects from memory */ +char *sip_cli_prune_realtime(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct sip_peer *peer; + int check_all; + const char *peer_name; + regex_t pattern; + + if (command == CLI_INIT) { + entry->command = "sip prune realtime [peer|like|all]"; + entry->usage = "Usage: sip prune realtime [peer [|like ]|all]\n" + " Prunes object(s) from the cache.\n" + " Optional regular expression pattern is used to filter the objects.\n"; + return NULL; + } else if (command == CLI_GENERATE) { + if (args->pos == 4 && !strcasecmp(args->argv[3], "peer")) { + return sip_cli_complete_peer(args, TRUE); + } + + return NULL; + } + + check_all = FALSE; + peer_name = NULL; + + if (args->argc == 4) { + if (!strcasecmp(args->argv[3], "all")) { + check_all = TRUE; + } else { + return CLI_SHOWUSAGE; + } + } else if (args->argc == 5) { + if (!strcasecmp(args->argv[3], "peer")) { + peer_name = args->argv[4]; + } else if (!strcasecmp(args->argv[3], "like")) { + check_all = TRUE; + peer_name = args->argv[4]; + } else { + return CLI_SHOWUSAGE; + } + } else { + return CLI_SHOWUSAGE; + } + + if (check_all && peer_name) { + if (regcomp(&pattern, peer_name, REG_EXTENDED | REG_NOSUB)) { + return CLI_SHOWUSAGE; + } + } + + if (check_all) { + struct ao2_iterator iter; + int count; + + iter = ao2_iterator_init(sip_peers, 0); + count = 0; + + while ((peer = ao2_iterator_next(&iter))) { + ao2_lock(peer); + + if (peer_name && regexec(&pattern, peer->name, 0, NULL, 0)) { + ao2_unlock(peer); + ao2_ref(peer, -1); + continue; + }; + + if (peer->realtime_cache_peer) { + peer->removed = TRUE; + count++; + } + + ao2_unlock(peer); + ao2_ref(peer, -1); + } + + ao2_iterator_destroy(&iter); + + if (count) { + ao2_callback(sip_peers, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, sip_peer_unlink, NULL); + + ast_cli(args->fd, "%d peers pruned\n", count); + } else { + ast_cli(args->fd, "No peers found to prune\n"); + } + } else if ((peer = ao2_find(sip_peers, peer_name, OBJ_SEARCH_KEY | OBJ_UNLINK))) { + if (!ast_sockaddr_isnull(&peer->address)) { + ao2_unlink(sip_peer_addresses, peer); + } + + if (!peer->realtime_cache_peer) { + ast_cli(args->fd, "Peer '%s' is not a realtime peer, cannot be pruned\n", peer_name); + /* Put it back! */ + ao2_link(sip_peers, peer); + + if (!ast_sockaddr_isnull(&peer->address)) { + ao2_link(sip_peer_addresses, peer); + } + } else { + ast_cli(args->fd, "Peer '%s' pruned\n", peer_name); + } + + ao2_ref(peer, -1); + } else { + ast_cli(args->fd, "Peer '%s' not found\n", peer_name); + } + + if (check_all && peer_name) { + regfree(&pattern); + } + + return CLI_SUCCESS; +} + +/* Send custom NOTIFY to a peer */ +char *sip_cli_notify(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct sip_notification *notification; + int i; + + if (command == CLI_INIT) { + entry->command = "sip notify"; + entry->usage = "Usage: sip notify [...]\n" + " Send a NOTIFY message to a SIP peer or peers\n" + " Message types are defined in sip_notify.conf\n"; + return NULL; + } else if (command == CLI_GENERATE) { + return sip_cli_complete_notify(args); + } + + if (args->argc < 4) { + return CLI_SHOWUSAGE; + } + + if (!(notification = ao2_find(sip_notifications, args->argv[2], OBJ_SEARCH_KEY))) { + ast_cli(args->fd, "Notify type '%s' not found\n", args->argv[2]); + return CLI_FAILURE; + } + + for (i = 3; i < args->argc; i++) { + struct sip_peer *peer; + + if (!(peer = sip_peer_find(args->argv[i], TRUE, FALSE))) { + ast_cli(args->fd, "Peer '%s' not found\n", args->argv[i]); + continue; + } + + if (ast_sockaddr_isnull(&peer->address)) { + ast_cli(args->fd, "No IP address for peer '%s'\n", args->argv[i]); + } else { + ast_cli(args->fd, "Sending notify '%s' to '%s'\n", args->argv[2], args->argv[i]); + sip_notification_send(notification, peer); + } + + ao2_ref(peer, -1); + } + + ao2_ref(notification, -1); + return CLI_SUCCESS; +} + +/* Enable/Disable DoNotDisturb on a peer */ +char *sip_cli_do_not_disturb(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct sip_peer *peer; + struct sip_alias *alias; + int do_not_disturb; + + if (command == CLI_INIT) { + entry->command = "sip donotdisturb {on|off}"; + entry->usage = "Usage: sip donotdisturb {on|off} \n" + " Enables/Disables do not disturb on a SIP peer\n"; + return NULL; + } else if (command == CLI_GENERATE) { + if (args->pos == 3) { + return sip_cli_complete_peer(args, FALSE); + } + + return NULL; + } + + if (args->argc < 4) { + return CLI_SHOWUSAGE; + } + + if (!strcasecmp(args->argv[2], "on")) { + do_not_disturb = TRUE; + } else if (!strcasecmp(args->argv[2], "off")) { + do_not_disturb = FALSE; + } else { + return CLI_SHOWUSAGE; + } + + if (!(peer = sip_peer_find(args->argv[3], TRUE, FALSE))) { + ast_cli(args->fd, "Peer '%s' not found\n", args->argv[3]); + return CLI_FAILURE; + } + + ast_cli(args->fd, "Do Not Disturb on '%s' %s\n", peer->name, do_not_disturb ? "enabled" : "disabled"); + + peer->do_not_disturb = do_not_disturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", peer->name); + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (alias->peer) { + alias->peer->do_not_disturb = peer->do_not_disturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", alias->peer->name); + } + } + + if (!peer->realtime) { + ast_db_put("SIP/DoNotDisturb", peer->name, do_not_disturb ? "yes" : "no"); + } else if (sip_config.realtime_update_peer && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name, + "donotdisturb", do_not_disturb ? "yes" : "no", SENTINEL); + } + + sip_peer_send_do_not_disturb(peer); + ao2_ref(peer, -1); + return CLI_SUCCESS; +} + +/* Login to/Logout from huntgroup for a peer */ +char *sip_cli_hunt_group(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct sip_peer *peer; + struct sip_alias *alias; + int hunt_group; + + if (command == CLI_INIT) { + entry->command = "sip huntgroup {on|off}"; + entry->usage = "Usage: sip huntgroup {on|off} \n" + " Login to/Logout from huntgroup for a SIP peer\n"; + return NULL; + } else if (command == CLI_GENERATE) { + if (args->pos == 3) { + return sip_cli_complete_peer(args, FALSE); + } + + return NULL; + } + + if (args->argc < 4) { + return CLI_SHOWUSAGE; + } + + if (!strcasecmp(args->argv[2], "on")) { + hunt_group = TRUE; + } else if (!strcasecmp(args->argv[2], "off")) { + hunt_group = FALSE; + } else { + return CLI_SHOWUSAGE; + } + + if (!(peer = sip_peer_find(args->argv[3], TRUE, FALSE))) { + ast_cli(args->fd, "Peer '%s' not found\n", args->argv[3]); + return CLI_FAILURE; + } + + ast_cli(args->fd, "Hunt Group %s for '%s'\n", hunt_group ? "login" : "logout", peer->name); + peer->hunt_group = hunt_group; + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (alias->peer) { + alias->peer->hunt_group = peer->hunt_group; + } + } + + if (!peer->realtime) { + ast_db_put("SIP/HuntGroup", peer->name, hunt_group ? "yes" : "no"); + } else if (sip_config.realtime_update_peer && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name,"huntgroup", hunt_group ? "yes" : "no", SENTINEL); + } + + sip_peer_send_hunt_group(peer); + ao2_ref(peer, -1); + return CLI_SUCCESS; +} + +/* Sets/Removes the call fowarding extension for a peer */ +char *sip_cli_call_forward(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct sip_peer *peer; + const char *call_forward; + + if (command == CLI_INIT) { + entry->command = "sip callforward {on|off}"; + entry->usage = "Usage: sip callforward {on |off }\n" + " Sets/Clears the call forwarding extension for a SIP peer\n"; + return NULL; + } else if (command == CLI_GENERATE) { + if (args->pos == 3) { + return sip_cli_complete_peer(args, FALSE); + } + + return NULL; + } + + if (args->argc < 4) { + return CLI_SHOWUSAGE; + } + + if (!strcasecmp(args->argv[2], "on")) { + if (args->argc < 5) { + return CLI_SHOWUSAGE; + } + + call_forward = args->argv[4]; + } else if (!strcasecmp(args->argv[2], "off")) { + call_forward = ""; + } else { + return CLI_SHOWUSAGE; + } + + if (!(peer = sip_peer_find(args->argv[3], TRUE, FALSE))) { + ast_cli(args->fd, "Peer '%s' not found\n", args->argv[3]); + return CLI_FAILURE; + } + + if (ast_strlen_zero(call_forward)) { + ast_cli(args->fd, "Call forwarding on '%s' removed\n", peer->name); + } else { + ast_cli(args->fd, "Call forwarding on '%s' set to %s\n", peer->name, call_forward); + } + + ast_string_field_set(peer, call_forward, call_forward); + + if (!peer->realtime) { + if (ast_strlen_zero(peer->call_forward)) { + ast_db_del("SIP/CallForward", peer->name); + } else { + ast_db_put("SIP/CallForward", peer->name, call_forward); + } + } else if (sip_config.realtime_update_peer && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name, "callforward", peer->call_forward, SENTINEL); + } + + sip_peer_send_call_forward(peer); + ao2_ref(peer, -1); + return CLI_SUCCESS; +} + +/* Re-qualify a peer */ +char *sip_cli_qualify_peer(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + int load_realtime; + struct sip_peer *peer; + + if (command == CLI_INIT) { + entry->command = "sip qualify"; + entry->usage = "Usage: sip qualify peer [load]\n" + " Requests a response from one SIP peer and the current status.\n" + " Option \"load\" forces lookup of peer in realtime storage.\n"; + return NULL; + } else if (command == CLI_GENERATE) { + if (args->pos == 4) { + static const char *choices[] = {"load", NULL}; + + return ast_cli_complete(args->word, choices, args->n); + } else if (args->pos == 3) { + return sip_cli_complete_peer(args, FALSE); + } else { + return NULL; + } + } + + if (args->argc < 4) { + return CLI_SHOWUSAGE; + } + + load_realtime = (args->argc == 5 && !strcmp(args->argv[4], "load")); + + if ((peer = sip_peer_find(args->argv[3], load_realtime, FALSE))) { + sip_peer_send_qualify(peer, TRUE); + sip_publish_qualify_peer(args->argv[3], NULL); + + ao2_ref(peer, -1); + } else { + ast_cli(args->fd, "Peer '%s' not found\n", args->argv[3]); + } + + return CLI_SUCCESS; +} + +/* List global settings */ +char *sip_cli_show_settings(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + int realtime; + struct ao2_container *authentication_realms; + struct ast_str *format_names; + struct ast_ha *internal_network; + + if (command == CLI_INIT) { + entry->command = "sip show settings"; + entry->usage = "Usage: sip show settings\n" + " Provides detailed list of the configuration of the SIP channel.\n"; + return NULL; + } else if (command == CLI_GENERATE) { + return NULL; + } + + if (args->argc != 3) { + return CLI_SHOWUSAGE; + } + + realtime = ast_check_realtime("sippeers"); + + ast_cli(args->fd, "\n[ Listen Ports ]\n"); + ast_cli(args->fd, " UDP Bind Address: %s\n", ast_sockaddr_stringify(&sip_config.udp_bind_address)); + + if (ast_sockaddr_is_ipv6(&sip_config.udp_bind_address) && ast_sockaddr_is_any(&sip_config.udp_bind_address)) { + ast_cli(args->fd, " Additional Info: [::] may include IPv4 in addition to IPv6, if such a feature is enabled in the OS\n"); + } + + ast_cli(args->fd, " TCP Bind Address: %s\n", + sip_config.tcp_enabled ? ast_sockaddr_stringify(&sip_tcp_session.local_address) : "Disabled"); + ast_cli(args->fd, " TLS Bind Address: %s\n", + sip_tls_config.enabled ? ast_sockaddr_stringify(&sip_tls_session.local_address) : "Disabled"); + ast_cli(args->fd, " RTP Bind Address: %s\n", + !ast_sockaddr_isnull(&sip_config.rtp_bind_address) ? ast_sockaddr_stringify_addr(&sip_config.rtp_bind_address) : "Disabled"); + + ast_cli(args->fd, "\n[ Authentication ]\n"); + ast_cli(args->fd, " Domain Support: %s\n", AST_CLI_YESNO(ao2_container_count(sip_domains))); + ast_cli(args->fd, " Allow External Domains: %s\n", AST_CLI_YESNO(sip_config.allow_external_domains)); + ast_cli(args->fd, " Use Domains as Realms: %s\n", AST_CLI_YESNO(sip_config.domains_as_realm)); + ast_cli(args->fd, " Our Authentication Realm: %s\n", sip_config.realm); + + if ((authentication_realms = ao2_bump(sip_authentication_realms))) { + struct ao2_iterator iter; + struct sip_authentication_realm *authentication_realm; + int count; + + ast_cli(args->fd, " Authentication Realms: "); + iter = ao2_iterator_init(authentication_realms, 0); + + for (count = 0; (authentication_realm = ao2_iterator_next(&iter)); count++) { + ast_cli(args->fd, "%s%s@%s\n", count ? " " : "", + authentication_realm->user, authentication_realm->realm); + ao2_ref(authentication_realm, -1); + } + + ao2_iterator_destroy(&iter); + ao2_ref(authentication_realms, -1); + } + + ast_cli(args->fd, " Always Send Unauthorized: %s\n", AST_CLI_YESNO(sip_config.always_send_unauthorized)); + ast_cli(args->fd, " Match Auth. Username: %s\n", AST_CLI_YESNO(sip_config.match_authorization_username)); + ast_cli(args->fd, " Auth. Failure Events: %s\n", AST_CLI_ONOFF(sip_config.authentication_failure_events)); + ast_cli(args->fd, " Auth. Options Requests: %s\n", AST_CLI_YESNO(sip_config.authenticate_options)); + ast_cli(args->fd, " Allow Message: %s\n", AST_CLI_YESNO(sip_config.allow_message)); + + ast_cli(args->fd, "\n[ Peer Defaults ]\n"); + + if (sip_config.from_domain_port && sip_config.from_domain_port != SIP_STANDARD_PORT) { + ast_cli(args->fd, " From Domain: %s:%d\n", + sip_config.from_domain, sip_config.from_domain_port); + } else { + ast_cli(args->fd, " From Domain: %s\n", sip_config.from_domain); + } + + ast_cli(args->fd, " Max Qualify: %dms\n", sip_config.qualify_max); + ast_cli(args->fd, " Qualify Expires: %ds\n", sip_config.qualify_expires); + ast_cli(args->fd, " Language: %s\n", sip_config.language); + ast_cli(args->fd, " Tone Zone: %s\n", sip_config.tone_zone); + ast_cli(args->fd, " MOH Interpret: %s\n", sip_config.moh_interpret); + ast_cli(args->fd, " MOH Suggest: %s\n", sip_config.moh_suggest); + ast_cli(args->fd, " Proxy: %s%s\n", + sip_config.proxy.host, sip_config.proxy.force ? ", forced" : ""); + ast_cli(args->fd, " DNS SRV Lookup: %s\n", AST_CLI_YESNO(sip_config.srv_lookup)); + + ast_cli(args->fd, "\n[ Signalling ]\n"); + ast_cli(args->fd, " Timer T1: %dms\n", sip_config.timer_t1); + ast_cli(args->fd, " Timer T1 Minimum: %dms\n", sip_config.min_timer_t1); + ast_cli(args->fd, " Timer B: %dms\n", sip_config.timer_b); + ast_cli(args->fd, " User Agent: %s\n", sip_config.useragent); + ast_cli(args->fd, " SDP Session Name: %s\n", sip_config.sdp_session); + ast_cli(args->fd, " SDP Owner Name: %s\n", sip_config.sdp_username); + + format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + + ast_cli(args->fd, " Codecs: %s\n", + ast_format_cap_get_names(sip_config.format_cap, &format_names)); + ast_cli(args->fd, " Direct RTP Setup: %s\n", AST_CLI_YESNO(sip_config.direct_rtp_setup)); + ast_cli(args->fd, " RTP Timeout: %ds\n", sip_config.rtp_timeout); + ast_cli(args->fd, " RTP Hold Timeout: %ds\n", sip_config.rtp_hold_timeout); + ast_cli(args->fd, " RTP Keepalive: %ds\n", sip_config.rtp_keepalive); + ast_cli(args->fd, " Default Expires: %ds\n", sip_config.default_expires); + ast_cli(args->fd, " Register Min-Expires: %ds\n", sip_config.register_min_expires); + ast_cli(args->fd, " Register Max-Expires: %ds\n", sip_config.register_max_expires); + ast_cli(args->fd, " Register Timeout: %ds\n", sip_config.register_timeout); + ast_cli(args->fd, " Register Attempts: %d\n", sip_config.register_max_attempts); + ast_cli(args->fd, " Register Retry Forbidden: %s\n", AST_CLI_YESNO(sip_config.register_retry_forbidden)); + ast_cli(args->fd, " Subscribe Min-Expires: %ds\n", sip_config.subscribe_min_expires); + ast_cli(args->fd, " Subscribe Max-Expires: %ds\n", sip_config.subscribe_max_expires); + + if (sip_config.notify_callerid) { + ast_cli(args->fd, "Notify Includes Caller ID: %s\n", AST_CLI_YESNO(sip_config.notify_callerid)); + } + + ast_cli(args->fd, " Pickup Uses Context: %s\n", AST_CLI_YESNO(sip_config.pickup_context)); + + ast_cli(args->fd, "\n[ Network ]\n"); + ast_cli(args->fd, " Address Remapping: "); + + if (!sip_config.internal_networks) { + ast_cli(args->fd, "Disabled, no local networks\n"); + } else if (ast_sockaddr_isnull(&sip_config.external_address)) { + ast_cli(args->fd, "Disabled"); + } else if (!ast_strlen_zero(sip_config.external_host)) { + ast_cli(args->fd, "Enabled using external host\n"); + } else { + ast_cli(args->fd, "Enabled using external address\n"); + } + + if (sip_config.internal_networks) { + ast_cli(args->fd, " Local Networks: "); + + for (internal_network = sip_config.internal_networks; internal_network; internal_network = internal_network->next) { + const char *address, *mask; + + address = ast_strdupa(ast_sockaddr_stringify_addr(&internal_network->addr)); + mask = ast_strdupa(ast_sockaddr_stringify_addr(&internal_network->netmask)); + + ast_cli(args->fd, "%s%s/%s\n", + internal_network != sip_config.internal_networks ? " " : "", address, mask); + } + } + + ast_cli(args->fd, " External Host: %s\n", sip_config.external_host); + ast_cli(args->fd, " External Address: %s\n", + !ast_sockaddr_isnull(&sip_config.external_address) ? ast_sockaddr_stringify(&sip_config.external_address) : ""); + ast_cli(args->fd, " External Expiries: %ds\n", sip_config.external_expires); + + ast_cli(args->fd, "\n[ QoS ]\n"); + ast_cli(args->fd, " IP ToS SIP: %s\n", ast_tos2str(sip_config.tos_sip)); + ast_cli(args->fd, " IP ToS RTP Audio: %s\n", ast_tos2str(sip_config.tos_audio)); + ast_cli(args->fd, " IP ToS RTP Video: %s\n", ast_tos2str(sip_config.tos_video)); + ast_cli(args->fd, " IP ToS RTP Text: %s\n", ast_tos2str(sip_config.tos_text)); + ast_cli(args->fd, " 802.1p CoS SIP: %u\n", sip_config.cos_sip); + ast_cli(args->fd, " 802.1p CoS RTP Audio: %u\n", sip_config.cos_audio); + ast_cli(args->fd, " 802.1p CoS RTP Video: %u\n", sip_config.cos_video); + ast_cli(args->fd, " 802.1p CoS RTP Text: %u\n", sip_config.cos_text); + ast_cli(args->fd, " Jitter-buffer Enabled: %s\n", + AST_CLI_YESNO(ast_test_flag(&sip_jb_config, AST_JB_ENABLED))); + + if (ast_test_flag(&sip_jb_config, AST_JB_ENABLED)) { + ast_cli(args->fd, " Jitter-buffer Forced: %s\n", + AST_CLI_YESNO(ast_test_flag(&sip_jb_config, AST_JB_FORCED))); + ast_cli(args->fd, " Jitter-buffer Max Size: %ld\n", sip_jb_config.max_size); + ast_cli(args->fd, " Jitter-buffer Resync: %ld\n", sip_jb_config.resync_threshold); + ast_cli(args->fd, " Jitter-buffer Impl.: %s\n", sip_jb_config.impl); + + if (!strcasecmp(sip_jb_config.impl, "adaptive")) { + ast_cli(args->fd, " Jitter-buffer Target Extra: %ld\n", sip_jb_config.target_extra); + } + + ast_cli(args->fd, " Jitter-buffer Log: %s\n", + AST_CLI_YESNO(ast_test_flag(&sip_jb_config, AST_JB_LOG))); + } + + ast_cli(args->fd, "\n[ Realtime ]\n"); + ast_cli(args->fd, " Realtime Enabled: %s\n", AST_CLI_YESNO(realtime)); + + if (realtime) { + ast_cli(args->fd, " Cache Peers: %s\n", AST_CLI_YESNO(sip_config.realtime_cache_peer)); + ast_cli(args->fd, " Update Peer: %s\n", AST_CLI_YESNO(sip_config.realtime_update_peer)); + ast_cli(args->fd, " Auto Clear: %ds\n", sip_config.realtime_auto_clear); + ast_cli(args->fd, " Ignore Register Expires: %s\n", AST_CLI_YESNO(sip_config.realtime_ignore_expires)); + ast_cli(args->fd, " Save Sys. Name: %s\n", AST_CLI_YESNO(sip_config.realtime_save_sysname)); + ast_cli(args->fd, " Save Path Header: %s\n", AST_CLI_YESNO(sip_config.realtime_save_path)); + } + + ast_cli(args->fd, "\n"); + return CLI_SUCCESS; +} + +/* Show peers */ +char *sip_cli_show_peers(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct sip_peer *peer; + struct ao2_iterator iter; + int i, count, realtime, has_pattern; + struct sip_peer **peers; + regex_t pattern; + + if (command == CLI_INIT) { + entry->command = "sip show peers [like]"; + entry->usage = "Usage: sip show peers [like ]\n" + " Lists all known SIP peers.\n" + " Optional regular expression pattern is used to filter the peer list.\n"; + return NULL; + } else if (command == CLI_GENERATE) { + return NULL; + } + + has_pattern = FALSE; + + if (args->argc == 5) { + if (!strcasecmp(args->argv[3], "like")) { + if (regcomp(&pattern, args->argv[4], REG_EXTENDED | REG_NOSUB)) { + return CLI_SHOWUSAGE; + } + + has_pattern = TRUE; + } else { + return CLI_SHOWUSAGE; + } + } else if (args->argc != 3) { + return CLI_SHOWUSAGE; + } + + realtime = ast_check_realtime("sippeers"); + ao2_lock(sip_peers); + + if (!(peers = ast_calloc(sizeof(peer), ao2_container_count(sip_peers)))) { + ao2_unlock(sip_peers); + return CLI_FAILURE; + } + + ao2_unlock(sip_peers); + iter = ao2_iterator_init(sip_peers, 0); + + for (count = 0; (peer = ao2_iterator_next(&iter)); count++) { + ao2_lock(peer); + + if (has_pattern && regexec(&pattern, peer->name, 0, NULL, 0)) { + ao2_unlock(peer); + ao2_ref(peer, -1); + continue; + } + + peers[count] = peer; + ao2_unlock(peer); + } + + ao2_iterator_destroy(&iter); + qsort(peers, count, sizeof(peer), sip_cli_peer_cmp); + + ast_cli(args->fd, "%-25.25s %-34.34s %-16s %-32.32s %-12.12s\n", + "Name", "Host", "Status", "Description", realtime ? "Realtime" : ""); + + for (i = 0; i < count; i++) { + peer = peers[i]; + ao2_lock(peer); + + ast_cli(args->fd, "%-25.25s %-34.34s %-16s %-32.32s %-12.12s\n", + peer->name, + !ast_sockaddr_isnull(&peer->address) ? ast_sockaddr_stringify_addr(&peer->address) : "", + sip_peer_status2str(peer), + peer->description ? peer->description : "", + realtime && peer->realtime ? "Cached" : ""); + + ao2_unlock(peer); + ao2_ref(peer, -1); + } + + ast_cli(args->fd, "\n%d peer%s\n", count, ESS(count)); + + if (has_pattern) { + regfree(&pattern); + } + + ast_free(peers); + return CLI_SUCCESS; +} + +/* Show one peer in detail */ +char *sip_cli_show_peer(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + char callerid[AST_MAX_EXTENSION], groups[64]; + struct sip_peer *peer; + int load_realtime, realtime; + struct ast_str *format_names, *mailboxes, *namedgroups, *transports; + struct ao2_container *authentication_realms; + + if (command == CLI_INIT) { + entry->command = "sip show peer"; + entry->usage = "Usage: sip show peer [load]\n" + " Shows all details on one SIP peer and the current status.\n" + " Option \"load\" forces lookup of peer in realtime storage.\n"; + return NULL; + } else if (command == CLI_GENERATE) { + if (args->pos == 4) { + const char *completions[] = {"load", NULL}; + + return ast_cli_complete(args->word, completions, args->n); + } else if (args->pos == 3) { + return sip_cli_complete_peer(args, FALSE); + } else { + return NULL; + } + } + + if (args->argc < 4) { + return CLI_SHOWUSAGE; + } + + realtime = ast_check_realtime("sippeers"); + load_realtime = (args->argc == 5 && !strcmp(args->argv[4], "load")); + + if (!(peer = sip_peer_find(args->argv[3], load_realtime, FALSE))) { + ast_cli(args->fd, "Peer %s not found\n", args->argv[3]); + ast_cli(args->fd, "\n"); + + return CLI_SUCCESS; + } + + ast_cli(args->fd, "\n"); + ast_cli(args->fd, "[ Peer '%s' ]\n", peer->name); + ast_cli(args->fd, " Description: %s\n", peer->description); + + if (realtime) { /* Realtime is enabled */ + ast_cli(args->fd, " Realtime: %s\n", peer->realtime ? "Yes, Cached" : "No"); + } + + if (!ast_strlen_zero(peer->authorization_user)) { + ast_cli(args->fd, " Authorization User: %s\n", peer->authorization_user); + } + + ast_cli(args->fd, " Secret: %s\n", AST_CLI_YESNO(!ast_strlen_zero(peer->secret))); + + if (!ast_strlen_zero(peer->md5_secret)) { + ast_cli(args->fd, " MD5 Secret: %s\n", + AST_CLI_YESNO(!ast_strlen_zero(peer->md5_secret))); + } + + if (!ast_strlen_zero(peer->remote_secret)) { + ast_cli(args->fd, " Remote Secret: %s\n", + AST_CLI_YESNO(!ast_strlen_zero(peer->remote_secret))); + } + + if ((authentication_realms = ao2_bump(peer->authentication_realms))) { + struct ao2_iterator iter; + struct sip_authentication_realm *authentication_realm; + int count; + + ast_cli(args->fd, " Authentication Realms: "); + + iter = ao2_iterator_init(authentication_realms, 0); + + for (count = 0; (authentication_realm = ao2_iterator_next(&iter)); count++) { + ast_cli(args->fd, "%s%s@%s\n", count ? " " : "", + authentication_realm->user, authentication_realm->realm); + ao2_ref(authentication_realm, -1); + } + + ao2_iterator_destroy(&iter); + ao2_ref(authentication_realms, -1); + } + + ast_cli(args->fd, " Context: %s\n", peer->context); + + if (!ast_strlen_zero(peer->subscribe_context)) { + ast_cli(args->fd, " Subscribe Context: %s\n", peer->subscribe_context); + } + + if (!ast_strlen_zero(peer->message_context)) { + ast_cli(args->fd, " Message Context: %s\n", peer->message_context); + } + + ast_cli(args->fd, " Parkinglot: %s\n", peer->parkinglot); + ast_cli(args->fd, " Language: %s\n", peer->language); + ast_cli(args->fd, " Tone Zone: %s\n", peer->tone_zone); + + if (!ast_strlen_zero(peer->accountcode)) { + ast_cli(args->fd, " Account Code: %s\n", peer->accountcode); + } + + ast_cli(args->fd, " Caller ID: %s\n", + ast_callerid_merge(callerid, sizeof(callerid), peer->caller_name, peer->caller_number, "")); + ast_cli(args->fd, " Caller Presentation: %s\n", + ast_describe_caller_presentation(peer->caller_presentation)); + + if (peer->amaflags) { + ast_cli(args->fd, " AMA Flags: %s\n", ast_channel_amaflags2string(peer->amaflags)); + } + + if (!ast_strlen_zero(peer->from_user)) { + ast_cli(args->fd, " From User: %s\n", peer->from_user); + } + + if (!ast_strlen_zero(peer->from_domain)) { + ast_cli(args->fd, " From Domain: %s:%d\n", + peer->from_domain, peer->from_domain_port ? peer->from_domain_port : SIP_STANDARD_PORT); + } + + ast_cli(args->fd, " Max Calls: %d\n", peer->max_calls); + + if (peer->busy_level) { + ast_cli(args->fd, " Busy Level: %d\n", peer->busy_level); + } + + ast_cli(args->fd, " IP Address ACL: %s\n", + AST_CLI_YESNO(!ast_acl_list_is_empty(peer->address_acl))); + ast_cli(args->fd, " Contact ACL: %s\n", + AST_CLI_YESNO(!ast_acl_list_is_empty(peer->contact_acl))); + ast_cli(args->fd, " Direct Media ACL: %s\n", + AST_CLI_YESNO(!ast_acl_list_is_empty(peer->direct_media_acl))); + + format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + ast_cli(args->fd, " Codecs: %s\n", + ast_format_cap_get_names(peer->format_cap, &format_names)); + + ast_cli(args->fd, " Call Group: %s\n", + ast_print_group(groups, sizeof(groups), peer->callgroup)); + ast_cli(args->fd, " Pickup Group: %s\n", + ast_print_group(groups, sizeof(groups), peer->pickupgroup)); + + namedgroups = ast_str_alloca(512); + ast_cli(args->fd, " Named Call Group: %s\n", + ast_print_namedgroups(&namedgroups, peer->named_callgroups)); + + ast_str_reset(namedgroups); + ast_cli(args->fd, " Named Pickup Group: %s\n", + ast_print_namedgroups(&namedgroups, peer->named_pickupgroups)); + + ast_cli(args->fd, " MOH Suggest: %s\n", peer->moh_suggest); + ast_cli(args->fd, " MOH Interpret: %s\n", peer->moh_interpret); + + mailboxes = ast_str_alloca(512); + ast_cli(args->fd, " Mailbox: %s\n", sip_peer_get_mailboxes(peer, &mailboxes)); + ast_cli(args->fd, " Voice Mail Extension: %s\n", peer->mwi_exten); + ast_cli(args->fd, " Voice Messages: %d New, %d Old\n", peer->new_messages, peer->old_messages); + ast_cli(args->fd, " Max Qualify: %dms\n", peer->qualify_max); + ast_cli(args->fd, " Qualify Expires: %ds\n", peer->qualify_expires); + ast_cli(args->fd, " Allow Transfer: %s\n", AST_CLI_YESNO(peer->allow_transfer)); + ast_cli(args->fd, " Allow Subscriptions: %s\n", AST_CLI_YESNO(peer->allow_subscribe)); + ast_cli(args->fd, " Direct Media: %s\n", AST_CLI_YESNO(peer->direct_media)); + ast_cli(args->fd, " Timer T1: %dms\n", peer->timer_t1); + ast_cli(args->fd, " Timer B: %dms\n", peer->timer_b); + ast_cli(args->fd, " User=Phone: %s\n", AST_CLI_YESNO(peer->user_eq_phone)); + ast_cli(args->fd, " DTMF Mode: "); + + if (peer->dtmf_mode == SIP_DTMF_MODE_INBAND) { + ast_cli(args->fd, "Inband"); + } else if (peer->dtmf_mode == SIP_DTMF_MODE_RFC2833) { + ast_cli(args->fd, "RFC2833"); + } + + ast_cli(args->fd, "\n"); + ast_cli(args->fd, " Relax DTMF: %s\n", AST_CLI_YESNO(peer->relax_dtmf)); + ast_cli(args->fd, " Allow Overlap: "); + + if (peer->allow_overlap == SIP_ALLOW_OVERLAP_INVITE) { + ast_cli(args->fd, "INVITE"); + } else if (peer->allow_overlap == SIP_ALLOW_OVERLAP_DTMF) { + ast_cli(args->fd, "DTMF"); + } else { + ast_cli(args->fd, "No"); + } + + ast_cli(args->fd, "\n"); + ast_cli(args->fd, " Video Support: %s\n", AST_CLI_YESNO(peer->video_support)); + ast_cli(args->fd, " Text Support: %s\n", AST_CLI_YESNO(peer->text_support)); + ast_cli(args->fd, " Identity Support: "); + + if (peer->identity_support == SIP_IDENTITY_REMOTE_PARTY) { + ast_cli(args->fd, "Remote-Party-ID"); + } else if (peer->identity_support == SIP_IDENTITY_P_ASSERTED) { + ast_cli(args->fd, "P-Asserted-Identity"); + } else { + ast_cli(args->fd, "No"); + } + + if (peer->allow_identity) { + ast_cli(args->fd, ", Allow Update"); + } + + if (peer->trust_identity_outgoing) { + ast_cli(args->fd, ", Trusted Outbound"); + } + + ast_cli(args->fd, "\n"); + ast_cli(args->fd, " Reason Support: %s\n", AST_CLI_YESNO(peer->reason_support)); + ast_cli(args->fd, " Diversion Support: %s\n", AST_CLI_YESNO(peer->diversion_support)); + ast_cli(args->fd, " Busy When DND: %s\n", AST_CLI_YESNO(peer->busy_when_dnd)); + ast_cli(args->fd, " Max Call Bitrate: %dkbps\n", peer->max_call_bitrate); + ast_cli(args->fd, " Max Forwards: %d\n", peer->max_forwards); + ast_cli(args->fd, " Auto-Framing: %s\n", AST_CLI_YESNO(peer->auto_framing)); + ast_cli(args->fd, " RTP Encryption: %s\n", AST_CLI_YESNO(peer->secure_media)); + ast_cli(args->fd, " RTCP Mux: %s\n", AST_CLI_YESNO(peer->rtcp_mux)); + ast_cli(args->fd, " NAT Force Rport: %s%s\n", + AST_CLI_YESNO(peer->nat_force_rport), peer->nat_auto_rport ? ", Auto" : ""); + ast_cli(args->fd, " NAT RTP: %s%s\n", + AST_CLI_YESNO(peer->nat_rtp), peer->nat_auto_rtp ? ", Auto" : ""); + ast_cli(args->fd, " ICE Support: %s\n", AST_CLI_YESNO(peer->ice_support)); + ast_cli(args->fd, " Path Support: %s\n", AST_CLI_YESNO(peer->path_support)); + + if (peer->path_support) { + struct ast_str *path; + + if ((path = sip_route_list(&peer->path, 0))) { + ast_cli(args->fd, " Path: %s\n", ast_str_buffer(path)); + ast_free(path); + } + } + + ast_cli(args->fd, " RTP Timeout: %ds\n", peer->rtp_timeout); + ast_cli(args->fd, " RTP Hold Timeout: %ds\n", peer->rtp_hold_timeout); + ast_cli(args->fd, " RTP Keepalive: %ds\n", peer->rtp_keepalive); + ast_cli(args->fd, " Fax Support: %s\n", AST_CLI_YESNO(peer->fax_support)); + + if (peer->fax_support) { + ast_cli(args->fd, " Fax Error Correction: "); + + if (peer->udptl_error_correction == UDPTL_ERROR_CORRECTION_FEC) { + ast_cli(args->fd, "FEC"); + } else if (peer->udptl_error_correction == UDPTL_ERROR_CORRECTION_REDUNDANCY) { + ast_cli(args->fd, "Redundancy"); + } else { + ast_cli(args->fd, "None"); + } + + ast_cli(args->fd, " Fax Max Datagram: %u\n", peer->fax_max_datagram); + } + + if (peer->proxy) { + ast_cli(args->fd, " Proxy: %s%s\n", + peer->proxy->host, peer->proxy->force ? ", Forced" : ""); + } + + ast_cli(args->fd, " Cisco Mode: %s\n", AST_CLI_YESNO(peer->cisco_mode)); + + if (peer->cisco_mode) { + struct sip_subscription *subscription; + struct sip_alias *alias; + struct ast_str *pickup_notify; + + ast_cli(args->fd, " Keep Conference: %s\n", AST_CLI_YESNO(peer->keep_conference)); + ast_cli(args->fd, " Multi-Admin Conference: %s\n", AST_CLI_YESNO(peer->multi_admin_conference)); + + pickup_notify = ast_str_alloca(16); + + if (peer->pickup_notify & SIP_PICKUP_NOTIFY_FROM) { + ast_str_append(&pickup_notify, 0, "%s", "From"); + } + + if (peer->pickup_notify & SIP_PICKUP_NOTIFY_TO) { + ast_str_append(&pickup_notify, 0, "%s%s", ast_str_strlen(pickup_notify) ? ", " : "", "To"); + } + + if (peer->pickup_notify & SIP_PICKUP_NOTIFY_BEEP) { + ast_str_append(&pickup_notify, 0, "%s%s", ast_str_strlen(pickup_notify) ? ", " : "", "Beep"); + } + + if (!ast_str_strlen(pickup_notify)) { + ast_str_set(&pickup_notify, 0, "None"); + } + + ast_cli(args->fd, " Pickup Notify: %s\n", ast_str_buffer(pickup_notify)); + ast_cli(args->fd, " Pickup Notify Timer: %ds\n", peer->pickup_notify_timer); + ast_cli(args->fd, " Park Notify Timer: %ds\n", peer->park_notify_timer); + + if (!AST_LIST_EMPTY(&peer->aliases)) { + ast_cli(args->fd, " Register Peers: "); + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + ast_cli(args->fd, "%s%s, Line %d\n", + alias != AST_LIST_FIRST(&peer->aliases) ? " " : "", + alias->name, alias->line_index); + } + } + + if (!AST_LIST_EMPTY(&peer->subscriptions)) { + ast_cli(args->fd, " Subscriptions: "); + + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, next) { + ast_cli(args->fd, "%s%s@%s\n", + subscription != AST_LIST_FIRST(&peer->subscriptions) ? " " : "", + subscription->exten, subscription->context); + } + } + + if (!ast_strlen_zero(peer->qrt_url)) { + ast_cli(args->fd, " QRT URL: %s\n", peer->qrt_url); + } + } + + if (peer->channel_variables) { + struct ast_variable *variable; + + ast_cli(args->fd, " Variables: "); + + for (variable = peer->channel_variables; variable; variable = variable->next) { + ast_cli(args->fd, "%s%s=%s\n", + variable != peer->channel_variables ? " " : "", + variable->name, variable->value); + } + } + + ast_cli(args->fd, " Timer Mode: "); + + if (peer->session_timer_mode == SIP_SESSION_TIMER_MODE_ORIGINATE) { + ast_cli(args->fd, "Originate"); + } else if (peer->session_timer_mode == SIP_SESSION_TIMER_MODE_ACCEPT) { + ast_cli(args->fd, "Accept"); + } else if (peer->session_timer_mode == SIP_SESSION_TIMER_MODE_REFUSE) { + ast_cli(args->fd, "Refuse"); + } + + ast_cli(args->fd, "\n"); + + if (peer->session_timer_mode != SIP_SESSION_TIMER_MODE_REFUSE) { + ast_cli(args->fd, " Timer Refresher: "); + + if (peer->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_AUTO) { + ast_cli(args->fd, "Auto"); + } else if (peer->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_UAS) { + ast_cli(args->fd, "UAS"); + } else if (peer->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_UAC) { + ast_cli(args->fd, "UAC"); + } + + ast_cli(args->fd, "\n"); + ast_cli(args->fd, " Timer Min-Expires: %ds\n", peer->session_timer_min_expires); + ast_cli(args->fd, " Timer Max-Expires: %ds\n", peer->session_timer_max_expires); + } + + ast_cli(args->fd, " Primary Transport: "); + + if (peer->default_transport == AST_TRANSPORT_UDP) { + ast_cli(args->fd, "UDP"); + } else if (peer->default_transport == AST_TRANSPORT_TCP) { + ast_cli(args->fd, "TCP"); + } else if (peer->default_transport == AST_TRANSPORT_TLS) { + ast_cli(args->fd, "TLS"); + } else { + ast_cli(args->fd, "UNKNOWN"); + } + + ast_cli(args->fd, "\n"); + transports = ast_str_alloca(16); + + if (peer->transports & AST_TRANSPORT_UDP) { + ast_str_append(&transports, 0, "%s", "UDP"); + } + + if (peer->transports & AST_TRANSPORT_TCP) { + ast_str_append(&transports, 0, "%s%s", ast_str_strlen(transports) ? ", " : "", "TCP"); + } + + if (peer->transports & AST_TRANSPORT_TLS) { + ast_str_append(&transports, 0, "%s%s", ast_str_strlen(transports) ? ", " : "", "TLS"); + } + + ast_cli(args->fd, " Transports: %s\n", ast_str_buffer(transports)); + ast_cli(args->fd, " Host: %s\n", peer->host_dynamic ? "Dynamic" : peer->host); + ast_cli(args->fd, " IP Address: %s\n", + !ast_sockaddr_isnull(&peer->address) ? ast_sockaddr_stringify(&peer->address) : ""); + + ast_cli(args->fd, " Status: %s\n", sip_peer_status2str(peer)); + ast_cli(args->fd, " User Agent: %s\n", peer->useragent); + ast_cli(args->fd, " Register Contact: %s\n", peer->contact); + + if (peer->register_expires_sched_id != -1) { + ast_cli(args->fd, " Register Expires: %lds\n", + ast_sched_when(sip_sched_context, peer->register_expires_sched_id)); + } + + ast_cli(args->fd, " Allow Methods: %s\n", sip_methods2str(peer->allow_methods)); + ast_cli(args->fd, " Supported Options: %s\n", sip_options2str(peer->supported_options)); + ast_cli(args->fd, " Do Not Disturb: %s\n", AST_CLI_YESNO(peer->do_not_disturb)); + ast_cli(args->fd, " Call Forward Extension: %s\n", peer->call_forward); + ast_cli(args->fd, " Hunt Group: %s\n", AST_CLI_YESNO(peer->hunt_group)); + + if (peer->cisco_mode) { + ast_cli(args->fd, " Device Name: %s\n", peer->device_name); + ast_cli(args->fd, " Active Firmware Load: %s\n", peer->active_load); + ast_cli(args->fd, " Inactive Firmware Load: %s\n", peer->inactive_load); + } + + ast_cli(args->fd, "\n"); + ao2_ref(peer, -1); + return CLI_SUCCESS; +} + +/* Show outbound SIP registrations */ +char *sip_cli_show_registrations(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct ao2_iterator iter; + struct sip_registration *registration; + int count; + + if (command == CLI_INIT) { + entry->command = "sip show registrations"; + entry->usage = "Usage: sip show registrations\n" + " Lists all registration requests and status.\n"; + return NULL; + } else if (command == CLI_GENERATE) { + return NULL; + } + + if (args->argc != 3) { + return CLI_SHOWUSAGE; + } + + ast_cli(args->fd, "%-39.39s %-6.6s %-12.12s %8.8s %-20.20s %-25.25s\n", + "Host", "DnsMgr", "Username", "Refresh", "State", "When"); + iter = ao2_iterator_init(sip_registrations, 0); + + for (count = 0; (registration = ao2_iterator_next(&iter)); count++) { + char host[64], user[64], last_registered[32]; + const char *state; + int len; + + ao2_lock(registration); + + snprintf(host, sizeof(host), "%s:%d", registration->host, + registration->port ? registration->port : SIP_STANDARD_PORT); + snprintf(user, sizeof(user), "%s", registration->user); + + if (!ast_strlen_zero(registration->domain)) { + len = strlen(user); + snprintf(user + len, sizeof(user) - len, "@%s", registration->domain); + } + + if (registration->domain_port) { + len = strlen(user); + snprintf(user + len, sizeof(user) - len, ":%d", registration->domain_port); + } + + if (registration->last_registered) { + struct timeval when; + struct ast_tm tm; + + when = ast_tv(registration->last_registered, 0); + ast_localtime(&when, &tm, NULL); + ast_strftime(last_registered, sizeof(last_registered), "%a, %d %b %Y %T", &tm); + } else { + last_registered[0] = '\0'; + } + + switch (registration->state) { + case SIP_REGISTRATION_REQUEST_SENT: + state = "Request Sent"; + break; + case SIP_REGISTRATION_REGISTERED: + case SIP_REGISTRATION_TIMEOUT: + state = "Registered"; + break; + case SIP_REGISTRATION_AUTHORIZATION_SENT: + state = "Auth. Sent"; + break; + case SIP_REGISTRATION_AUTHORIZATION_FAILED: + state = "Auth. Failed"; + break; + case SIP_REGISTRATION_REJECTED: + state = "Rejected"; + break; + case SIP_REGISTRATION_FAILED: + state = "Failed"; + break; + case SIP_REGISTRATION_UNREGISTERED: + default: + state = "Unregistered"; + break; + } + + ast_cli(args->fd, "%-39.39s %-6.6s %-12.12s %8ld %-20.20s %-25.25s\n", + host, AST_CLI_YESNO(registration->dnsmgr), user, + ast_sched_when(sip_sched_context, registration->expires_sched_id), + state, last_registered); + + ao2_unlock(registration); + ao2_ref(registration, -1); + } + + ao2_iterator_destroy(&iter); + ast_cli(args->fd, "\n%d registration%s\n", count, ESS(count)); + return CLI_SUCCESS; +} + +/* Show outgoing MWI subscriptions */ +char *sip_cli_show_mwi(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + char host[MAXHOSTNAMELEN]; + struct ao2_iterator iter; + struct sip_mwi_subscription *mwi_subscription; + int count; + + if (command == CLI_INIT) { + entry->command = "sip show mwi"; + entry->usage = "Usage: sip show mwi\n" + " Provides a list of MWI subscriptions and status.\n"; + return NULL; + } else if (command == CLI_GENERATE) { + return NULL; + } + + ast_cli(args->fd, "%-30.30s %-20.20s %-20.20s %-5.5s %-5.5s %-10.10s\n", + "Host", "Username", "Mailbox", "New", "Old", "Subscribed"); + iter = ao2_iterator_init(sip_mwi_subscriptions, 0); + + for (count = 0; (mwi_subscription = ao2_iterator_next(&iter)); count++) { + ao2_lock(mwi_subscription); + + snprintf(host, sizeof(host), "%s:%d", + mwi_subscription->host, mwi_subscription->port ? mwi_subscription->port : SIP_STANDARD_PORT); + ast_cli(args->fd, "%-30.30s %-20.20s %-20.20s %-5d %-5d %-10.10s\n", + host, mwi_subscription->user, mwi_subscription->mailbox, + mwi_subscription->new_messages, mwi_subscription->old_messages, + AST_CLI_YESNO(mwi_subscription->subscribed)); + + ao2_unlock(mwi_subscription); + ao2_ref(mwi_subscription, -1); + } + + ao2_iterator_destroy(&iter); + ast_cli(args->fd, "\n%d MWI subscription%s\n", count, ESS(count)); + return CLI_SUCCESS; +} + +/* Show active SIP dialogs */ +char *sip_cli_show_calls(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct sip_dialog *dialog; + struct ao2_iterator iter; + int count; + + if (command == CLI_INIT) { + entry->command = "sip show calls"; + entry->usage = "Usage: sip show calls\n" + " Lists all currently active SIP calls (dialogs).\n"; + return NULL; + } else if (command == CLI_GENERATE) { + return NULL; + } + + if (args->argc != entry->args) { + return CLI_SHOWUSAGE; + } + + ast_cli(args->fd, "%-15.15s %-24.24s %-10.10s %-15.15s %-4.4s %-7.7s %-10.10s\n", + "IP Address", "Call ID", "Method", "Codec", "Hold", "Destroy", "Peer"); + iter = ao2_iterator_init(sip_dialogs, 0); + + for (count = 0; (dialog = ao2_iterator_next(&iter)); count++) { + const struct ast_sockaddr *address; + struct ast_str *format_names; + + if (dialog->subscribe_event) { + ao2_ref(dialog, -1); + count--; + continue; + } + + ao2_lock(dialog); + + if (dialog->socket.transport == AST_TRANSPORT_UDP && + (dialog->nat_force_rport || dialog->rport_present)) { + address = &dialog->socket.address; + } else { + address = &dialog->address; + } + + format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + ast_cli(args->fd, "%-15.15s %-24.24s %-10.10s %-15.15s %-4.4s %-7.7s %-10.10s\n", + ast_sockaddr_stringify_addr(address), + dialog->call_id, + sip_method2str(dialog->method), + dialog->channel ? ast_format_cap_get_names(ast_channel_nativeformats(dialog->channel), &format_names) : "", + AST_CLI_YESNO(dialog->onhold), + AST_CLI_YESNO(dialog->need_destroy), + dialog->peer ? dialog->peer->name : ""); + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + } + + ao2_iterator_destroy(&iter); + ast_cli(args->fd, "\n%d active call%s\n", count, ESS(count)); + return CLI_SUCCESS; +} + +/* Show presence and mwi subscriptions */ +char *sip_cli_show_subscriptions(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct sip_dialog *dialog; + struct ao2_iterator iter; + int count; + + if (command == CLI_INIT) { + entry->command = "sip show subscriptions"; + entry->usage = "Usage: sip show subscriptions\n" + " Lists active SIP subscriptions.\n"; + return NULL; + } else if (command == CLI_GENERATE) { + return NULL; + } + + if (args->argc != entry->args) { + return CLI_SHOWUSAGE; + } + + ast_cli(args->fd, "%-15.15s %-24.24s %-15.15s %-15.15s %-10.10s %-8.8s %-10.10s\n", + "IP Address", "Call ID", "Event", "Extension", "State", "Expires", "Peer"); + iter = ao2_iterator_init(sip_dialogs, 0); + + for (count = 0; (dialog = ao2_iterator_next(&iter)); count++) { + const struct ast_sockaddr *address; + char *subscribe_event; + + if (!dialog->subscribe_event) { + ao2_ref(dialog, -1); + count--; + continue; + } + + ao2_lock(dialog); + + if (dialog->socket.transport == AST_TRANSPORT_UDP && + (dialog->nat_force_rport || dialog->rport_present)) { + address = &dialog->socket.address; + } else { + address = &dialog->address; + } + + switch (dialog->subscribe_event) { + case SIP_SUBSCRIBE_DIALOG: + subscribe_event ="dialog-info"; + break; + case SIP_SUBSCRIBE_PRESENCE: + subscribe_event ="pidf-xml"; + break; + case SIP_SUBSCRIBE_MESSAGE_SUMMARY: + subscribe_event ="message-summary"; + break; + case SIP_SUBSCRIBE_FEATURE_EVENTS: + subscribe_event ="feature-events"; + break; + default: + subscribe_event =""; + break; + } + + ast_cli(args->fd, "%-15.15s %-24.24s %-15.15s %-15.15s %-10.10s %-8d %-10.10s\n", + ast_sockaddr_stringify_addr(address), + dialog->call_id, + subscribe_event, + dialog->subscribe_event == SIP_SUBSCRIBE_DIALOG || + dialog->subscribe_event == SIP_SUBSCRIBE_PRESENCE ? dialog->to_user : "", + dialog->subscribe_event == SIP_SUBSCRIBE_DIALOG || + dialog->subscribe_event == SIP_SUBSCRIBE_PRESENCE ? ast_extension_state2str(dialog->last_exten_state) : "", + dialog->expires, + dialog->peer->name); + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + } + + ao2_iterator_destroy(&iter); + ast_cli(args->fd, "\n%d active subscription%s\n", count, ESS(count)); + + return CLI_SUCCESS; +} + +/* Show details of one active dialog */ +char *sip_cli_show_dialog(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct sip_dialog *dialog; + int arg_len, found; + struct ao2_iterator iter; + + if (command == CLI_INIT) { + entry->command = "sip show dialog"; + entry->usage = "Usage: sip show dialog \n" + " Provides detailed status on a given SIP dialog identified by Call-ID.\n"; + return NULL; + } else if (command == CLI_GENERATE) { + return sip_cli_complete_call_id(args); + } + + if (args->argc != 4) { + return CLI_SHOWUSAGE; + } + + arg_len = strlen(args->argv[3]); + found = FALSE; + iter = ao2_iterator_init(sip_dialogs, 0); + + while ((dialog = ao2_iterator_next(&iter))) { + ao2_lock(dialog); + + if (strncasecmp(dialog->call_id, args->argv[3], arg_len)) { + ao2_unlock(dialog); + ao2_ref(dialog, -1); + continue; + } + + ast_cli(args->fd, "\n"); + + if (dialog->subscribe_event) { + ast_cli(args->fd, "[ Subscription ]\n"); + } else { + ast_cli(args->fd, "[ Call ]\n"); + } + + ast_cli(args->fd, " Need Destroy: %s\n", AST_CLI_YESNO(dialog->need_destroy)); + ast_cli(args->fd, " Direction: %s\n", dialog->outgoing ? "Outgoing" : "Incoming"); + ast_cli(args->fd, " Call-ID: %s\n", dialog->call_id); + ast_cli(args->fd, " Tag: %s\n", dialog->local_tag); + ast_cli(args->fd, " Remote Tag: %s\n", dialog->remote_tag); + + if (dialog->peer) { + ast_cli(args->fd, " Peer Name: %s\n", dialog->peer->name); + } + + if (!ast_strlen_zero(dialog->uri)) { + ast_cli(args->fd, " URI: %s\n", dialog->uri); + } + + ast_cli(args->fd, " Transport: %s\n", ast_transport2str(dialog->socket.transport)); + ast_cli(args->fd, " IP Address: %s\n", ast_sockaddr_stringify(&dialog->address)); + ast_cli(args->fd, " Received IP Address: %s\n", ast_sockaddr_stringify(&dialog->socket.address)); + + if (dialog->subscribe_event) { + const char *subscribe_event; + + switch (dialog->subscribe_event) { + case SIP_SUBSCRIBE_DIALOG: + subscribe_event = "dialog-info"; + break; + case SIP_SUBSCRIBE_PRESENCE: + subscribe_event = "pidf-xml"; + break; + case SIP_SUBSCRIBE_MESSAGE_SUMMARY: + subscribe_event = "message-summary"; + break; + case SIP_SUBSCRIBE_FEATURE_EVENTS: + subscribe_event = "feature-events"; + break; + default: + subscribe_event = ""; + break; + } + + ast_cli(args->fd, " Subscribed Event: %s\n", subscribe_event); + } else { + char callerid[AST_MAX_EXTENSION]; + struct ast_str *format_names; + + ast_cli(args->fd, " Channel: %s\n", + dialog->channel ? ast_channel_name(dialog->channel) : ""); + ast_cli(args->fd, " Caller ID: %s\n", + ast_callerid_merge(callerid, sizeof(callerid), dialog->caller_name, dialog->caller_number, "")); + ast_cli(args->fd, " Caller Presentation: %s\n", + ast_describe_caller_presentation(dialog->caller_presentation)); + ast_cli(args->fd, " Allow Methods: %s\n", sip_methods2str(dialog->allow_methods)); + ast_cli(args->fd, " Supported Options: %s\n", + sip_options2str(dialog->supported_options)); + ast_cli(args->fd, " User Agent: %s\n", dialog->useragent); + + if (dialog->peer->path_support) { + struct ast_str *path; + + if ((path = sip_route_list(&dialog->route, 0))) { + ast_cli(args->fd, " Route: %s\n", + ast_str_buffer(path)); + ast_free(path); + } + } + + ast_cli(args->fd, " Media: "); + + if (dialog->secure_audio_rtp) { + ast_cli(args->fd, "SRTP"); + } else if (dialog->audio_rtp) { + ast_cli(args->fd, "RTP"); + } else { + ast_cli(args->fd, "None"); + } + + ast_cli(args->fd, "\n"); + + if (dialog->audio_rtp) { + if (ast_sockaddr_isnull(&dialog->audio_redirect_address)) { + ast_cli(args->fd, " RTP Address: %s, Local\n", + ast_sockaddr_stringify_addr(&dialog->our_address)); + } else { + ast_cli(args->fd, " RTP Address: %s, Outside Bridge\n", + ast_sockaddr_stringify_addr(&dialog->audio_redirect_address)); + } + } + + format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + + ast_cli(args->fd, " Codecs: %s\n", + ast_format_cap_get_names(dialog->format_cap, &format_names)); + ast_cli(args->fd, " Non-Codecs: %s\n", + ast_rtp_lookup_mime_multiple2(format_names, NULL, dialog->non_format_cap, 0, 0)); + ast_cli(args->fd, " Remote Codecs: %s\n", + ast_format_cap_get_names(dialog->remote_format_cap, &format_names)); + ast_cli(args->fd, " Joint Codecs: %s\n", + ast_format_cap_get_names(dialog->joint_format_cap, &format_names)); + ast_cli(args->fd, " Channel Codec: %s\n", + dialog->channel ? ast_format_cap_get_names(ast_channel_nativeformats(dialog->channel), &format_names) : ""); + + ast_cli(args->fd, " Max Call Bitrate: %dkbps\n", + dialog->peer->max_call_bitrate); + ast_cli(args->fd, " Allow Transfer: %s\n", + AST_CLI_YESNO(dialog->peer->allow_transfer)); + ast_cli(args->fd, " Allow Subscriptions: %s\n", + AST_CLI_YESNO(dialog->peer->allow_subscribe)); + ast_cli(args->fd, " Video Support: %s\n", AST_CLI_YESNO(dialog->video_rtp != NULL)); + ast_cli(args->fd, " Text Support: %s\n", AST_CLI_YESNO(dialog->text_rtp != NULL)); + ast_cli(args->fd, " Fax Support: %s\n", AST_CLI_YESNO(dialog->udptl != NULL)); + ast_cli(args->fd, " Timer Mode: "); + + if (dialog->peer->session_timer_mode == SIP_SESSION_TIMER_MODE_ORIGINATE) { + ast_cli(args->fd, "Originate"); + } else if (dialog->peer->session_timer_mode == SIP_SESSION_TIMER_MODE_ACCEPT) { + ast_cli(args->fd, "Accept"); + } else if (dialog->peer->session_timer_mode == SIP_SESSION_TIMER_MODE_REFUSE) { + ast_cli(args->fd, "Refuse"); + } + + ast_cli(args->fd, "\n"); + ast_cli(args->fd, " Timer Active: %s\n", + AST_CLI_YESNO(dialog->session_timer_active)); + + if (dialog->session_timer_active) { + ast_cli(args->fd, " Timer Remote Active: %s\n", + AST_CLI_YESNO(dialog->session_timer_remote_active)); + ast_cli(args->fd, " Timer Refresher: "); + + if (dialog->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_AUTO) { + ast_cli(args->fd, "Auto"); + } else if (dialog->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_UAS) { + ast_cli(args->fd, "UAS"); + } else if (dialog->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_UAC) { + ast_cli(args->fd, "UAC"); + } + + ast_cli(args->fd, "\n"); + ast_cli(args->fd, " Timer Expires: %lds\n", + ast_sched_when(sip_sched_context, dialog->session_timer_sched_id)); + ast_cli(args->fd, " Timer Min-Expires: %ds\n", + dialog->peer->session_timer_max_expires); + ast_cli(args->fd, " Timer Max-Expires: %ds\n", + dialog->peer->session_timer_min_expires); + } + } + + ast_cli(args->fd, "\n"); + found = TRUE; + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + } + + ao2_iterator_destroy(&iter); + + if (!found) { + ast_cli(args->fd, "No Call-ID starting with '%s'\n", args->argv[3]); + } + + return CLI_SUCCESS; +} + +/* Show channel RTP stats */ +char *sip_cli_show_stats(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct sip_dialog *dialog; + struct ao2_iterator iter; + int count; + + if (command == CLI_INIT) { + entry->command = "sip show stats"; + entry->usage = "Usage: sip show stats\n" + " Lists all currently active SIP call's RTCP statistics.\n" + " Note that calls in the much optimized RTP P2P bridge mode will not show any packets here."; + return NULL; + } else if (command == CLI_GENERATE) { + return NULL; + } + + if (args->argc != 3) { + return CLI_SHOWUSAGE; + } + + ast_cli(args->fd, "%-15.15s %-11.11s %-8.8s %-10.10s %-10.10s ( %%) %-6.6s %-10.10s %-10.10s ( %%) %-6.6s\n", + "Peer", "Call ID", "Duration", "Recv Pkts", "Lost", "Jitter", "Sent Pkts", "Lost", "Jitter"); + iter = ao2_iterator_init(sip_dialogs, 0); + + for (count = 0; (dialog = ao2_iterator_next(&iter)); count++) { + struct ast_rtp_instance_stats stats; + char duration[10]; + + ao2_lock(dialog); + + if (!dialog->audio_rtp) { + ao2_unlock(dialog); + ao2_ref(dialog, -1); + count--; + continue; + } + + if (ast_rtp_instance_get_stats(dialog->audio_rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) { + ao2_unlock(dialog); + ao2_ref(dialog, -1); + count--; + continue; + } + + if (dialog->channel) { + ast_format_duration_hh_mm_ss(ast_channel_get_duration(dialog->channel), duration, sizeof(duration)); + } else { + duration[0] = '\0'; + } + + ast_cli(args->fd, "%-15.15s %-11.11s %-8.8s %-10u%-1.1s %-10u (%5.2f%%) %-4lf %-10u%-1.1s %-10u (%5.2f%%) %-4lf\n", + ast_sockaddr_stringify_addr(&dialog->address), + dialog->call_id, + duration, + stats.rxcount > (unsigned int) 100000 ? (unsigned int) (stats.rxcount) / (unsigned int) 1000 : stats.rxcount, + stats.rxcount > (unsigned int) 100000 ? "K" : " ", + stats.rxploss, + (stats.rxcount + stats.rxploss) > 0 ? (double) stats.rxploss / (stats.rxcount + stats.rxploss) * 100 : 0, + stats.rxjitter, + stats.txcount > (unsigned int) 100000 ? (unsigned int) (stats.txcount) / (unsigned int) 1000 : stats.txcount, + stats.txcount > (unsigned int) 100000 ? "K" : " ", + stats.txploss, + stats.txcount > 0 ? (double) stats.txploss / stats.txcount * 100 : 0, + stats.txjitter); + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + } + + ao2_iterator_destroy(&iter); + ast_cli(args->fd, "%d active call%s\n", count, ESS(count)); + return CLI_SUCCESS; +} + +/* Show calls within limits set by call_limit */ +char *sip_cli_show_inuse(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct ao2_iterator iter; + struct sip_peer *peer; + int show_all, count; + + if (command == CLI_INIT) { + entry->command = "sip show inuse [all]"; + entry->usage = "Usage: sip show inuse [all]\n" + " List all SIP devices usage counters and limits.\n" + " Add option \"all\" to show all devices, not only those with a max calls.\n"; + return NULL; + } else if (command == CLI_GENERATE) { + return NULL; + } + + if (args->argc < 3) { + return CLI_SHOWUSAGE; + } + + if (args->argc == 4 && !strcmp(args->argv[3], "all")) { + show_all = TRUE; + } else { + show_all = FALSE; + } + + ast_cli(args->fd, "%-25.25s %-9.9s %-9.9s %-9.9s %-9.9s %-9.9s %-10.10s\n", + "Peer Name", "In Use", "Ringing", "On Hold", "Off Hook", "Max Calls", "Busy Level"); + iter = ao2_iterator_init(sip_peers, 0); + + for (count = 0; (peer = ao2_iterator_next(&iter)); count++) { + ao2_lock(peer); + + if (show_all || peer->max_calls) { + /* OnHold is counted as InUse so to show that separately we subtract from the count */ + ast_cli(args->fd, "%-25.25s %-9d %-9d %-9d %-9d %-9d %-10d\n", + peer->name, peer->inuse - peer->onhold, peer->ringing, peer->onhold, peer->offhook, + peer->max_calls, peer->busy_level); + } + + ao2_unlock(peer); + ao2_ref(peer, -1); + } + + ao2_iterator_destroy(&iter); + ast_cli(args->fd, "\n%d peer%s\n", count, ESS(count)); + return CLI_SUCCESS; +} + +/* List local domains */ +char *sip_cli_show_domains(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct sip_domain *domain; + struct ao2_iterator iter; + int count; + + if (command == CLI_INIT) { + entry->command = "sip show domains"; + entry->usage = "Usage: sip show domains\n" + " Lists all configured SIP local domains.\n" + " Asterisk only responds to SIP messages to local domains.\n"; + return NULL; + } else if (command == CLI_GENERATE) { + return NULL; + } + + ast_cli(args->fd, "%-40.40s %-20.20s\n", "Domain", "Context"); + iter = ao2_iterator_init(sip_domains, 0); + + for (count = 0; (domain = ao2_iterator_next(&iter)); count++) { + ast_cli(args->fd, "%-40.40s %-20.20s\n", domain->name, domain->context); + ao2_ref(domain, -1); + } + + ao2_iterator_destroy(&iter); + ast_cli(args->fd, "\n%d domain%s\n", count, ESS(count)); + return CLI_SUCCESS; +} + +/* List all allocated SIP Objects (realtime or static) */ +char *sip_cli_show_objects(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct ao2_iterator iter; + struct sip_peer *peer; + struct sip_registration *registration; + struct sip_mwi_subscription *mwi_subscription; + struct sip_dialog *dialog; + + if (command == CLI_INIT) { + entry->command = "sip show objects"; + entry->usage = "Usage: sip show objects\n" + " Lists ref-counts of SIP objects\n"; + return NULL; + } else if (command == CLI_GENERATE) { + return NULL; + } + + if (args->argc != 3) { + return CLI_SHOWUSAGE; + } + + ast_cli(args->fd, "\n[ Peers: %d static, %d realtime ]\n", sip_peer_static_count, sip_peer_realtime_count); + iter = ao2_iterator_init(sip_peers, 0); + + while ((peer = ao2_iterator_next(&iter))) { + ao2_lock(peer); + ast_cli(args->fd, " Name: '%s' Ref-count: %d\n", peer->name, ao2_ref(peer, 0)); + ao2_unlock(peer); + + ao2_ref(peer, -1); + } + + ao2_iterator_destroy(&iter); + + ast_cli(args->fd, "\n[ Peer IP-addresses: %d ]\n", ao2_container_count(sip_peer_addresses)); + iter = ao2_iterator_init(sip_peer_addresses, 0); + + while ((peer = ao2_iterator_next(&iter))) { + ao2_lock(peer); + ast_cli(args->fd, " Name: '%s' IP-address: '%s' Ref-count: %d\n", + peer->name, ast_sockaddr_stringify(&peer->address), ao2_ref(peer, 0)); + ao2_unlock(peer); + + ao2_ref(peer, -1); + } + + ao2_iterator_destroy(&iter); + + ast_cli(args->fd, "\n[ Dialogs: %d ]\n", ao2_container_count(sip_dialogs)); + iter = ao2_iterator_init(sip_dialogs, 0); + + while ((dialog = ao2_iterator_next(&iter))) { + ao2_lock(dialog); + ast_cli(args->fd, " Call-ID: '%s' Method: %s Expires: %lds Ref-count: %d\n", + dialog->call_id, sip_method2str(dialog->method), + ast_sched_when(sip_sched_context, dialog->auto_destroy_sched_id), ao2_ref(dialog, 0)); + ao2_unlock(dialog); + + ao2_ref(dialog, -1); + } + + ao2_iterator_destroy(&iter); + + ast_cli(args->fd, "\n[ Registrations: %d ]\n", ao2_container_count(sip_registrations)); + iter = ao2_iterator_init(sip_registrations, 0); + + while ((registration = ao2_iterator_next(&iter))) { + ao2_lock(registration); + ast_cli(args->fd, " User: '%s' Host: '%s' Ref-count: %d\n", + registration->user, registration->host, ao2_ref(registration, 0)); + ao2_unlock(registration); + + ao2_ref(registration, -1); + } + + ao2_iterator_destroy(&iter); + + ast_cli(args->fd, "\n[ MWI Subscriptions: %d ]\n", ao2_container_count(sip_mwi_subscriptions)); + iter = ao2_iterator_init(sip_mwi_subscriptions, 0); + + while ((mwi_subscription = ao2_iterator_next(&iter))) { + ao2_lock(mwi_subscription); + ast_cli(args->fd, " User: '%s' Host: '%s' Mailbox: '%s' Ref-count: %d\n", + mwi_subscription->user, mwi_subscription->host, mwi_subscription->mailbox, + ao2_ref(mwi_subscription, 0)); + + ao2_unlock(mwi_subscription); + ao2_ref(mwi_subscription, -1); + } + + ao2_iterator_destroy(&iter); + ast_cli(args->fd, "\n"); + return CLI_SUCCESS; +} + +/* Show scheduler callback entries */ +char *sip_cli_show_sched(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct ast_cb_names cb_names = { + 10, /* Number of functions */ + {"sip_packet_resend", + "sip_dialog_auto_destroy", "sip_dialog_auto_congest", "sip_dialog_need_reinvite", + "sip_peer_unregister", "sip_peer_qualify", "sip_peer_qualify_timeout", + "sip_registration_resend", "sip_registration_timeout", + "sip_mwi_subscription_resend"}, + {sip_packet_resend, + sip_dialog_auto_destroy, sip_dialog_auto_congest, sip_dialog_need_reinvite, + sip_peer_unregister, sip_peer_qualify, sip_peer_qualify_timeout, + sip_registration_resend, sip_registration_timeout, + sip_mwi_subscription_resend} + }; + struct ast_str *sched_report; + + if (command == CLI_INIT) { + entry->command = "sip show sched"; + entry->usage = "Usage: sip show sched\n" + " Shows stats on what is in the sched queue at the moment\n"; + return NULL; + } else if (command == CLI_GENERATE) { + return NULL; + } + + sched_report = ast_str_alloca(4096); + ast_sched_report(sip_sched_context, &sched_report, &cb_names); + + ast_cli(args->fd, "\n%s\n", ast_str_buffer(sched_report)); + return CLI_SUCCESS; +} + +/* Show active TCP connections */ +char *sip_cli_show_tcp(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args) +{ + struct sip_tcptls_thread *thread; + struct ao2_iterator iter; + int count; + + if (command == CLI_INIT) { + entry->command = "sip show tcp"; + entry->usage = "Usage: sip show tcp\n" + " Lists all active TCP/TLS sessions.\n"; + return NULL; + } else if (command == CLI_GENERATE) { + return NULL; + } + + if (args->argc != 3) { + return CLI_SHOWUSAGE; + } + + ast_cli(args->fd, "%-40.40s %-9.9s %-6.6s\n", "Host", "Transport", "Type"); + iter = ao2_iterator_init(sip_tcptls_threads, 0); + + for (count = 0; (thread = ao2_iterator_next(&iter)); count++) { + ast_cli(args->fd, "%-40.40s %-9.9s %-6.6s\n", + ast_sockaddr_stringify(&thread->tcptls_session->remote_address), + ast_transport2str(ast_iostream_get_ssl(thread->tcptls_session->stream) ? AST_TRANSPORT_TLS : AST_TRANSPORT_TCP), + thread->tcptls_session->client ? "Client" : "Server"); + + ao2_ref(thread, -1); + } + + ao2_iterator_destroy(&iter); + ast_cli(args->fd, "\n%d active TCP/TLS session%s\n", count, ESS(count)); + return CLI_SUCCESS; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/conference.c asterisk-22.6.0/channels/sip/conference.c --- asterisk-22.6.0.orig/channels/sip/conference.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/conference.c 2025-10-21 18:38:17.438218466 +1300 @@ -0,0 +1,969 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/callerid.h" +#include "asterisk/channel.h" +#include "asterisk/bridge.h" +#include "asterisk/indications.h" +#include "asterisk/pbx.h" +#include "asterisk/message.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/channel_tech.h" +#include "include/remotecc.h" +#include "include/conference.h" + +/* Information required to start or join an ad-hoc conference */ +struct sip_conference_data { + struct sip_dialog *dialog; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(call_id); + AST_STRING_FIELD(local_tag); + AST_STRING_FIELD(remote_tag); + AST_STRING_FIELD(join_call_id); + AST_STRING_FIELD(join_local_tag); + AST_STRING_FIELD(join_remote_tag); + ); + AST_LIST_HEAD_NOLOCK(, sip_selected) selected; + unsigned int joining:1; +}; + +static void sip_conference_destroy(void *data); +static void *sip_conference_thread(void *data); +static int sip_conference_alloc(struct sip_dialog *dialog); +static int sip_conference_talk_detector(struct ast_bridge_channel *channel, void *data, int talking); +static int sip_conference_leave(struct ast_bridge_channel *channel, void *data); +static int sip_conference_join(struct sip_conference *conference, struct ast_channel *channel, int administrator); + +/* Ad-hoc conference list */ +struct ao2_container *sip_conferences = NULL; + +int sip_conference_cmp(void *data, void *arg, int flags) +{ + struct sip_conference *conference; + int id; + + conference = (struct sip_conference *) data; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + struct sip_conference *conference = (struct sip_conference *) arg; + + id = conference->id; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + id = *(int *) arg; + } else { + return 0; + } + + if (conference->id == id) { + return CMP_MATCH | CMP_STOP; + } + + return 0; +} + +/* Start sip_conference_thread to create an ad-hoc conference */ +int sip_conference_build(struct sip_dialog *dialog, struct sip_message *request, + const char *call_id, const char *local_tag, const char *remote_tag, + const char *join_call_id, const char *join_local_tag, const char *join_remote_tag, int joining) +{ + pthread_t threadid; + struct sip_conference_data *conference_data; + struct sip_selected *selected; + + if (!(conference_data = ast_calloc_with_stringfields(1, struct sip_conference_data, 512))) { + return -1; + } + + conference_data->dialog = ao2_bump(dialog); + conference_data->joining = joining; + + ast_string_field_set(conference_data, call_id, call_id); + ast_string_field_set(conference_data, local_tag, remote_tag); + ast_string_field_set(conference_data, remote_tag, local_tag); + + if (!conference_data->joining) { + ast_string_field_set(conference_data, join_call_id, join_call_id); + ast_string_field_set(conference_data, join_local_tag, join_remote_tag); + ast_string_field_set(conference_data, join_remote_tag, join_local_tag); + } else { + ao2_lock(dialog->peer); + + while ((selected = AST_LIST_REMOVE_HEAD(&dialog->peer->selected, next))) { + AST_LIST_INSERT_TAIL(&conference_data->selected, selected, next); + } + + ao2_unlock(dialog->peer); + } + + if (ast_pthread_create_detached_background(&threadid, NULL, sip_conference_thread, conference_data)) { + ao2_ref(conference_data->dialog, -1); + ast_string_field_free_memory(conference_data); + ast_free(conference_data); + return -1; + } + + /* If the conference fails we send back a NOTIFY telling the phone */ + sip_response_send(dialog, "202 Accepted", request); + + if (!conference_data->joining) { + struct sip_message notify_request; + + dialog->outgoing = TRUE; + dialog->established = TRUE; + dialog->subscribe_event = SIP_SUBSCRIBE_REMOTECC; + dialog->expires = 60; + + sip_message_copy(&dialog->initial_request, request); + sip_message_build_initial_request(¬ify_request, dialog, SIP_METHOD_NOTIFY, NULL); + + sip_message_add_header(¬ify_request, "Event", "refer"); + sip_message_build_header(¬ify_request, "Subscription-State", "active;expires=%d", dialog->expires); + + sip_message_send(dialog, ¬ify_request, SIP_SEND_RELIABLE, dialog->outgoing_cseq); + } + + return 0; +} + +/* Create conference and assign it to a sip_dialog */ +static int sip_conference_alloc(struct sip_dialog *dialog) +{ + static unsigned int next_conference_id = 0; + struct sip_conference *conference; + + if (!(conference = ao2_alloc(sizeof(*conference), sip_conference_destroy))) { + return -1; + } + + if (!(conference->bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_MULTIMIX, + AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY, "SIP", "Conference", NULL))) { + ao2_ref(conference, -1); + return -1; + } + + ast_bridge_set_internal_sample_rate(conference->bridge, 8000); + ast_bridge_set_mixing_interval(conference->bridge, 20); + + ast_bridge_set_talker_src_video_mode(conference->bridge); + + conference->id = ++next_conference_id; + conference->keep = dialog->peer->keep_conference; + conference->multi_admin = dialog->peer->multi_admin_conference; + + AST_LIST_HEAD_INIT_NOLOCK(&conference->participants); + dialog->conference = ao2_bump(conference); + + ao2_link(sip_conferences, conference); + ao2_ref(conference, -1); + + ast_debug(1, "Creating ad-hoc conference %d\n", conference->id); + return 0; +} + +/* Destroy conference callback for ao2_alloc */ +static void sip_conference_destroy(void *data) +{ + struct sip_conference *conference = (struct sip_conference *) data; + + ast_debug(1, "Destroying ad-hoc conference %d\n", conference->id); + + if (conference->bridge) { + ast_bridge_destroy(conference->bridge, 0); + } +} + +/* Allocate participant structure and move channel into conference bridge */ +static int sip_conference_join(struct sip_conference *conference, struct ast_channel *channel, int administrator) +{ + struct sip_participant *participant; + struct ast_bridge_channel *bridge_channel; + + if (!administrator && conference->multi_admin) { + ast_channel_lock(channel); + + if (ast_channel_tech(channel) == &sip_channel_tech) { + struct sip_dialog *dialog = ast_channel_tech_pvt(channel); + + ao2_lock(dialog); + + if (dialog->peer->cisco_mode && conference->multi_admin) { + dialog->conference = ao2_bump(conference); + administrator = TRUE; + } + + ao2_unlock(dialog); + } + + ast_channel_unlock(channel); + } + + if (!(participant = ast_calloc(1, sizeof(*participant)))) { + return -1; + } + + ast_channel_ref(channel); + participant->channel = channel; + + participant->conference = ao2_bump(conference); + participant->id = ++conference->next_participant_id; + participant->administrator = administrator; + + bridge_channel = ao2_bump(ast_channel_internal_bridge_channel(channel)); + bridge_channel->inhibit_colp = TRUE; + + if (ast_bridge_move(conference->bridge, ast_channel_internal_bridge(channel), channel, NULL, 0)) { + ao2_ref(bridge_channel, -1); + ao2_ref(conference, -1); + + ast_channel_unref(channel); + ast_free(participant); + return -1; + } + + ast_bridge_features_remove(bridge_channel->features, AST_BRIDGE_HOOK_REMOVE_ON_PULL); + ast_bridge_leave_hook(bridge_channel->features, sip_conference_leave, participant, NULL, + AST_BRIDGE_HOOK_REMOVE_ON_PULL); + + ast_bridge_talk_detector_hook(bridge_channel->features, sip_conference_talk_detector, + participant, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL); + ao2_ref(bridge_channel, -1); + + ao2_lock(conference); + AST_LIST_INSERT_HEAD(&conference->participants, participant, next); + + if (administrator) { + conference->administrator_count++; + } else { + conference->user_count++; + } + + ao2_unlock(conference); + + if (conference->administrator_count + conference->user_count > 2) { + struct sip_participant *participant; + + AST_LIST_TRAVERSE(&conference->participants, participant, next) { + ast_bridge_channel_queue_playfile(ast_channel_internal_bridge_channel(participant->channel), + NULL, "confbridge-join", NULL); + } + } + + ast_debug(1, "%s joined ad-hoc conference %d\n", ast_channel_name(channel), conference->id); + + if (administrator) { + struct ast_party_connected_line connected_line; + + ast_party_connected_line_init(&connected_line); + + connected_line.id.name.str = "Conference"; + connected_line.id.name.valid = TRUE; + + connected_line.id.number.str = ""; + connected_line.id.number.valid = TRUE; + + connected_line.id.name.presentation = AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_PASSED_SCREEN; + connected_line.id.number.presentation = AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_PASSED_SCREEN; + connected_line.source = AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE; + + ast_channel_update_connected_line(channel, &connected_line, NULL); + } + + return 0; +} + +/* Cleanup participant structure after leaving bridge */ +static int sip_conference_leave(struct ast_bridge_channel *channel, void *data) +{ + struct sip_participant *participant; + struct sip_conference *conference; + + participant = (struct sip_participant *) data; + conference = ao2_bump(participant->conference); + + ast_debug(1, "%s left ad-hoc conference %d\n", ast_channel_name(participant->channel), conference->id); + + ao2_lock(conference); + AST_LIST_REMOVE(&conference->participants, participant, next); + + if (participant->administrator) { + conference->administrator_count--; + } else { + conference->user_count--; + } + + ast_channel_unref(participant->channel); + ao2_ref(participant->conference, -1); + ast_free(participant); + + ao2_unlock(conference); + + if (conference->administrator_count + conference->user_count > 1) { + struct sip_participant *participant; + + AST_LIST_TRAVERSE(&conference->participants, participant, next) { + ast_bridge_channel_queue_playfile(ast_channel_internal_bridge_channel(participant->channel), + NULL, "confbridge-leave", NULL); + } + } + + if (conference->administrator_count + conference->user_count == 1) { + struct sip_participant *participant = AST_LIST_FIRST(&conference->participants); + + ast_debug(1, "Only one participant left in ad-hoc conference %d, removing\n", conference->id); + ast_bridge_remove(conference->bridge, participant->channel); + ao2_unlink(sip_conferences, conference); + } else if (conference->user_count && !conference->administrator_count && !conference->keep) { + struct sip_participant *participant; + + ast_debug(1, "No more administrators in ad-hoc conference %d\n", conference->id); + + AST_LIST_TRAVERSE(&conference->participants, participant, next) { + ast_bridge_remove(conference->bridge, participant->channel); + } + + ao2_unlink(sip_conferences, conference); + } + + ao2_ref(conference, -1); + return -1; /* Remove hook */ +} + +/* Set whether a participant is talking */ +static int sip_conference_talk_detector(struct ast_bridge_channel *channel, void *data, int talking) +{ + struct sip_participant *participant; + struct sip_conference *conference; + + participant = (struct sip_participant *) data; + conference = participant->conference; + participant->talking = talking; + + ast_debug(1, "%s %s talking in ad-hoc conference %d\n", + ast_channel_name(participant->channel), talking ? "started" : "stopped", conference->id); + return 0; +} + +/* Add channels to conference */ +static void *sip_conference_thread(void *data) +{ + struct sip_conference_data *conference_data; + struct sip_dialog *dialog; + struct ast_channel *channel, *bridge_channel; + struct sip_conference *conference; + struct ast_str *content; + int res; + + conference_data = (struct sip_conference_data *) data; + res = -1; + conference = NULL; + + if (!(dialog = sip_dialog_find(conference_data->call_id, conference_data->local_tag, conference_data->remote_tag))) { + ast_debug(1, "No dialog with Call-ID: '%s' From: tag='%s' To: tag='%s'\n", + conference_data->call_id, conference_data->local_tag, conference_data->remote_tag); + goto cleanup; + } + + ao2_lock(dialog); + + /* Is this a new ad-hoc conference? */ + if (!dialog->conference) { + if (sip_conference_alloc(dialog)) { + ast_debug(1, "Unable to create conference\n"); + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + goto cleanup; + } + + /* Increase ref on conference so we don't need to keep a ref on it's parent dialog */ + conference = ao2_bump(dialog->conference); + + if (!(channel = dialog->channel)) { + ast_debug(1, "No owner channel\n"); + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + goto cleanup; + } + + ast_channel_ref(channel); + ao2_unlock(dialog); + ao2_ref(dialog, -1); + + if (!(bridge_channel = ast_channel_bridge_peer(channel))) { + ast_debug(1, "No bridged channel\n"); + ast_channel_unref(channel); + goto cleanup; + } + + if (sip_conference_join(conference, channel, TRUE)) { + ast_channel_unref(channel); + ast_channel_unref(bridge_channel); + goto cleanup; + } + + ast_indicate(bridge_channel, AST_CONTROL_UNHOLD); + + if (sip_conference_join(conference, bridge_channel, FALSE)) { + ast_channel_unref(channel); + ast_channel_unref(bridge_channel); + goto cleanup; + } + + ast_channel_unref(channel); + ast_channel_unref(bridge_channel); + } else { + conference = ao2_bump(dialog->conference); + ao2_unlock(dialog); + ao2_ref(dialog, -1); + } + + content = ast_str_alloca(4096); + + if (!conference_data->joining) { + struct sip_dialog *conference_dialog; + + if (!(dialog = sip_dialog_find(conference_data->join_call_id, + conference_data->join_local_tag, conference_data->join_remote_tag))) { + ast_debug(1, "No dialog with Call-ID: '%s' From; tag='%s' To: tag='%s'\n", + conference_data->join_call_id, conference_data->join_local_tag, conference_data->join_remote_tag); + goto cleanup; + } + + ao2_lock(dialog); + + if (!(channel = dialog->channel)) { + ast_debug(1, "No owner channel\n"); + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + goto cleanup; + } + + ast_channel_ref(channel); + ao2_unlock(dialog); + ao2_ref(dialog, -1); + + if (!(bridge_channel = ast_channel_bridge_peer(channel))) { + ast_debug(1, "No bridge channel\n"); + ast_channel_unref(channel); + goto cleanup; + } + + ast_indicate(bridge_channel, AST_CONTROL_UNHOLD); + + if (sip_conference_join(conference, bridge_channel, FALSE)) { + ast_channel_unref(channel); + ast_channel_unref(bridge_channel); + goto cleanup; + } + + ast_softhangup(channel, AST_SOFTHANGUP_EXPLICIT); + ast_channel_unref(channel); + ast_channel_unref(bridge_channel); + + res = 0; + + /* We need to signal to the phone to take the first call leg off hold, even though the generator on that + * channel has gone due to the masquerade as the phone still thinks that it is on hold */ + if (!(conference_dialog = sip_dialog_alloc(NULL, &conference_data->dialog->socket, SIP_METHOD_REFER, NULL, 0))) { + goto cleanup; + } + + sip_dialog_copy(conference_dialog, conference_data->dialog); + + ast_str_append(&content, 0, "\n" + "\n" + " \n" + " \n"); + ast_str_append(&content, 0, " %s\n", conference_data->call_id); + ast_str_append(&content, 0, " %s\n", conference_data->remote_tag); + ast_str_append(&content, 0, " %s\n", conference_data->local_tag); + ast_str_append(&content, 0, " \n" + " \n" + "\n"); + + sip_request_send_refer_with_content(conference_dialog, "application/x-cisco-remotecc-request+xml", + ast_str_buffer(content)); + ao2_ref(conference_dialog, -1); + } else { + struct sip_selected *selected; + + AST_LIST_TRAVERSE(&conference_data->selected, selected, next) { + /* Skip the join dialog as that was added to the conference above */ + if (!strcmp(conference_data->call_id, selected->call_id) && + !strcmp(conference_data->local_tag, selected->local_tag) && + !strcmp(conference_data->remote_tag, selected->remote_tag)) { + continue; + } + + if (!(dialog = sip_dialog_find(selected->call_id, selected->local_tag, selected->remote_tag))) { + ast_debug(1, "No dialog with Call-ID: '%s' From: tag='%s', To: tag='%s'\n", + selected->call_id, selected->local_tag, selected->remote_tag); + continue; + } + + ao2_lock(dialog); + + if (!(channel = dialog->channel)) { + ast_debug(1, "No owner channel\n"); + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + goto cleanup; + } + + ast_channel_ref(channel); + ao2_unlock(dialog); + ao2_ref(dialog, -1); + + if (!(bridge_channel = ast_channel_bridge_peer(channel))) { + ast_debug(1, "No bridged channel\n"); + ast_channel_unref(channel); + goto cleanup; + } + + ast_indicate(bridge_channel, AST_CONTROL_UNHOLD); + + if (sip_conference_join(conference, bridge_channel, 0)) { + ast_channel_unref(channel); + ast_channel_unref(bridge_channel); + goto cleanup; + } + + ast_softhangup(channel, AST_SOFTHANGUP_EXPLICIT); + ast_channel_unref(channel); + ast_channel_unref(bridge_channel); + } + + res = 0; + } + +cleanup: + ao2_cleanup(conference); /* Can be NULL here if allocation failed */ + ast_str_reset(content); + + if (!conference_data->joining) { + struct sip_message request; + + ao2_lock(conference_data->dialog); + sip_message_build_request(&request, conference_data->dialog, SIP_METHOD_NOTIFY, 0, TRUE); + + sip_message_add_header(&request, "Event", "refer"); + sip_message_add_header(&request, "Subscription-State", "terminated;reason=noresource"); + sip_message_add_header(&request, "Content-Type", "application/x-cisco-remotecc-response+xml"); + + ast_str_append(&content, 0, "\n" + "\n" + " \n" + " %d\n", res ? 500 : 200); + ast_str_append(&content, 0, " \n" + "\n"); + + sip_message_add_content(&request, ast_str_buffer(content)); + sip_message_send(conference_data->dialog, &request, SIP_SEND_RELIABLE, + conference_data->dialog->outgoing_cseq); + + ao2_unlock(conference_data->dialog); + } else { + struct sip_dialog *conference_dialog; + + if ((conference_dialog = sip_dialog_alloc(NULL, &conference_data->dialog->socket, SIP_METHOD_REFER, NULL, 0))) { + sip_dialog_copy(conference_dialog, conference_data->dialog); + + if (res) { + ast_str_append(&content, 0, "\n" + "\n" + " \n" + " notify_display\n" + " \n"); + ast_str_append(&content, 0, " %s\n", conference_data->call_id); + ast_str_append(&content, 0, " %s\n", conference_data->remote_tag); + ast_str_append(&content, 0, " %s\n", conference_data->local_tag); + ast_str_append(&content, 0, " \n" + " \200S\n" + " 7\n" + " 0\n" + " 1\n" + " \n" + "\n"); + } else { + ast_str_append(&content, 0, "\n" + "\n" + " \n" + " \n"); + ast_str_append(&content, 0, " %s\n", conference_data->call_id); + ast_str_append(&content, 0, " %s\n", conference_data->remote_tag); + ast_str_append(&content, 0, " %s\n", conference_data->local_tag); + ast_str_append(&content, 0, " \n" + " Join\n" + " Complete\n" + " \n" + "\n"); + } + + sip_request_send_refer_with_content(conference_dialog, "application/x-cisco-remotecc-request+xml", + ast_str_buffer(content)); + ao2_ref(conference_dialog, -1); + } + } + + if (conference_data->joining) { + struct sip_selected *selected; + + while ((selected = AST_LIST_REMOVE_HEAD(&conference_data->selected, next))) { + sip_selected_destroy(selected); + } + } + + ao2_ref(conference_data->dialog, -1); + ast_string_field_free_memory(conference_data); + ast_free(conference_data); + + return NULL; +} + +/* Send list of conference participants as XML */ +int sip_conference_participants(struct sip_dialog *dialog, struct sip_message *request, + const char *call_id, const char *local_tag, const char *remote_tag, int conference_id, const char *user_call_data) +{ + struct sip_dialog *conference_dialog; + struct sip_conference *conference; + struct sip_participant *participant; + struct ast_str *content; + int is_79xx; + + conference = NULL; + + if (!ast_strlen_zero(call_id)) { + struct sip_dialog *target_dialog; + + if (!(target_dialog = sip_dialog_find(call_id, remote_tag, local_tag))) { + ast_debug(1, "No dialog with Call-ID: '%s' From: tag='%s' To: tag='%s'\n", + call_id, remote_tag, local_tag); + return -1; + } + + ao2_lock(target_dialog); + conference = ao2_bump(target_dialog->conference); + + ao2_unlock(target_dialog); + ao2_ref(target_dialog, -1); + } else if (conference_id) { + conference = ao2_find(sip_conferences, &conference_id, OBJ_SEARCH_KEY); + } + + if (!conference) { + ast_debug(1, "Unable to find conference\n"); + return -1; + } + + sip_response_send(dialog, "202 Accepted", request); + + if (!ast_strlen_zero(user_call_data) && strcmp(user_call_data, "Update")) { + int participant_id; + + if (!strcmp(user_call_data, "Remove") || !strcmp(user_call_data, "Mute")) { + ast_string_field_set(dialog->peer, soft_key, user_call_data); + ao2_ref(conference, -1); + return 0; + } + + /* Default action is to mute/unmute */ + if (ast_strlen_zero(dialog->peer->soft_key)) { + ast_string_field_set(dialog->peer, soft_key, "Mute"); + } + + participant_id = atoi(user_call_data); + ao2_lock(conference); + + AST_LIST_TRAVERSE(&conference->participants, participant, next) { + if (participant->id == participant_id) { + if (!strcmp(dialog->peer->soft_key, "Remove")) { + ast_debug(1, "%s is being removed from ad-hoc conference %d\n", + ast_channel_name(participant->channel), conference->id); + + ast_bridge_remove(conference->bridge, participant->channel); + participant->removed = TRUE; + } else if (!strcmp(dialog->peer->soft_key, "Mute")) { + ast_debug(1, "%s is being %s in ad-hoc conference %d\n", + ast_channel_name(participant->channel), + participant->muted ? "unmuted" : "muted", conference->id); + participant->muted = !participant->muted; + + ast_channel_lock(participant->channel); + ast_channel_internal_bridge_channel(participant->channel)->features->mute = participant->muted; + ast_channel_unlock(participant->channel); + } + + break; + } + } + + ao2_unlock(conference); + ast_string_field_set(dialog->peer, soft_key, NULL); + } + + if (!(content = ast_str_create(4096))) { + ao2_ref(conference, -1); + return 0; + } + + if (!(conference_dialog = sip_dialog_alloc(NULL, &dialog->socket, SIP_METHOD_REFER, NULL, 0))) { + ao2_ref(conference, -1); + ast_free(content); + return 0; + } + + sip_dialog_copy(conference_dialog, dialog); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n"); + ast_str_append(&content, 0, " %d\n", SIP_REMOTECC_CONFLIST); + ast_str_append(&content, 0, " 0\n" + " StationSequenceLast\n" + " 2\n" + " 0\n" + " 0\n"); + ast_str_append(&content, 0, " %d\n", conference->id); + ast_str_append(&content, 0, " \n" + "\n" + "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-cm+xml\r\n" + "\r\n" + "\n" + "\n" + " Conference\n"); + + ao2_lock(conference); + AST_LIST_TRAVERSE(&conference->participants, participant, next) { + char *status, name[64]; + + if (participant->removed) { + continue; + } + + if (participant->muted) { + status = "- "; + } else if (participant->talking) { + status = "+ "; + } else { + status = ""; + } + + ast_channel_lock(participant->channel); + name[0] = '\0'; + + if (ast_channel_caller(participant->channel)->id.name.valid) { + ast_xml_escape(ast_channel_caller(participant->channel)->id.name.str, name, sizeof(name)); + } + + if (ast_strlen_zero(name) && ast_channel_caller(participant->channel)->id.number.valid) { + ast_xml_escape(ast_channel_caller(participant->channel)->id.number.str, name, sizeof(name)); + } + + if (ast_strlen_zero(name)) { + snprintf(name, sizeof(name), "Anonymous %d", participant->id); + } + + ast_channel_unlock(participant->channel); + + ast_str_append(&content, 0, " \n"); + ast_str_append(&content, 0, " %s%s\n", status, name); + ast_str_append(&content, 0, " UserCallData:%d:0:%d:0:%d\n", + SIP_REMOTECC_CONFLIST, conference->id, participant->id); + ast_str_append(&content, 0, " \n"); + + ast_channel_unlock(participant->channel); + } + + ao2_unlock(conference); + + is_79xx = strstr(sip_message_find_header(request, "User-Agent"), "CP79") != NULL; + + ast_str_append(&content, 0, " Please select\n" + " \n" + " Exit\n" + " %d\n", is_79xx ? 3 : 1); + ast_str_append(&content, 0, " SoftKey:Exit\n" + " \n" + " \n" + " Remove\n" + " %d\n", is_79xx ? 1 : 2); + ast_str_append(&content, 0, " UserCallDataSoftKey:Select:%d:0:%d:0:Remove\n", + SIP_REMOTECC_CONFLIST, conference->id); + ast_str_append(&content, 0, " \n" + " \n" + " Mute\n" + " %d\n", is_79xx ? 2 : 3); + ast_str_append(&content, 0, " UserCallDataSoftKey:Select:%d:0:%d:0:Mute\n", + SIP_REMOTECC_CONFLIST, conference->id); + ast_str_append(&content, 0, " \n" + " \n" + " Update\n" + " %d\n", is_79xx ? 4 : 4); + ast_str_append(&content, 0, " UserCallDataSoftKey:Update:%d:0:%d:0:Update\n", + SIP_REMOTECC_CONFLIST, conference->id); + ast_str_append(&content, 0, " \n" + "\n" + "\r\n" + "--uniqueBoundary--\r\n"); + + sip_request_send_refer_with_content(conference_dialog, "multipart/mixed; boundary=uniqueBoundary", + ast_str_buffer(content)); + ao2_ref(conference_dialog, -1); + + ao2_ref(conference, -1); + ast_free(content); + return 0; +} + +/* Remove last participant that joined conference */ +int sip_conference_remove_last(struct sip_dialog *dialog, struct sip_message *request, + const char *call_id, const char *local_tag, const char *remote_tag) +{ + struct sip_dialog *target_dialog; + struct sip_conference *conference; + struct sip_participant *participant; + + if (!(target_dialog = sip_dialog_find(call_id, remote_tag, local_tag))) { + ast_debug(1, "No dialog with Call-ID: '%s' From: tag='%s' To: tag='%s'\n", + call_id, remote_tag, local_tag); + return -1; + } + + ao2_lock(target_dialog); + conference = ao2_bump(target_dialog->conference); + + ao2_unlock(target_dialog); + ao2_ref(target_dialog, -1); + + if (!conference) { + ast_debug(1, "Not in a conference\n"); + return -1; + } + + sip_response_send(dialog, "202 Accepted", request); + ao2_lock(conference); + + if ((participant = AST_LIST_FIRST(&conference->participants))) { + ast_bridge_remove(conference->bridge, participant->channel); + } + + ao2_unlock(conference); + ao2_ref(conference, -1); + return 0; +} + +/* Add a dialog call-id to the list of selected dialogs */ +int sip_selected_add(struct sip_dialog *dialog, const char *call_id, const char *local_tag, const char *remote_tag) +{ + struct sip_selected *selected; + int found; + + ao2_lock(dialog->peer); + + found = FALSE; + + AST_LIST_TRAVERSE(&dialog->peer->selected, selected, next) { + if (!strcmp(selected->call_id, call_id) && + !strcmp(selected->local_tag, remote_tag) && !strcmp(selected->remote_tag, local_tag)) { + found = TRUE; + break; + } + } + + ao2_unlock(dialog->peer); + + if (!found) { + if (!(selected = ast_calloc_with_stringfields(1, struct sip_selected, 256))) { + return -1; + } + + ast_string_field_set(selected, call_id, call_id); + ast_string_field_set(selected, local_tag, remote_tag); + ast_string_field_set(selected, remote_tag, local_tag); + + ao2_lock(dialog->peer); + AST_LIST_INSERT_TAIL(&dialog->peer->selected, selected, next); + ao2_unlock(dialog->peer); + } + + return 0; +} + +/* Remove call-id from the list of selected dialogs */ +int sip_selected_remove(struct sip_dialog *dialog, const char *call_id, const char *local_tag, const char *remote_tag) +{ + struct sip_selected *selected; + + ao2_lock(dialog->peer); + + AST_LIST_TRAVERSE_SAFE_BEGIN(&dialog->peer->selected, selected, next) { + if (!strcmp(selected->call_id, call_id) && + !strcmp(selected->local_tag, remote_tag) && !strcmp(selected->remote_tag, local_tag)) { + AST_LIST_REMOVE_CURRENT(next); + sip_selected_destroy(selected); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + ao2_unlock(dialog->peer); + return 0; +} + +/* Destroy a selected dialog entry */ +void sip_selected_destroy(struct sip_selected *selected) +{ + ast_string_field_free_memory(selected); + ast_free(selected); +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/config.c asterisk-22.6.0/channels/sip/config.c --- asterisk-22.6.0.orig/channels/sip/config.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/config.c 2025-10-21 18:38:17.439218439 +1300 @@ -0,0 +1,1733 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/ast_version.h" +#include "asterisk/module.h" +#include "asterisk/file.h" +#include "asterisk/paths.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/tcptls.h" +#include "asterisk/channel.h" +#include "asterisk/indications.h" +#include "asterisk/rtp_engine.h" +#include "asterisk/format.h" +#include "asterisk/format_cache.h" +#include "asterisk/pbx.h" +#include "asterisk/acl.h" +#include "asterisk/sip_api.h" +#include "asterisk/message.h" +#include "asterisk/stasis.h" +#include "asterisk/parking.h" +#include "asterisk/cli.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/session_timer.h" +#include "include/proxy.h" +#include "include/domains.h" +#include "include/authentication_realms.h" +#include "include/peers.h" +#include "include/realtime.h" +#include "include/registrations.h" +#include "include/mwi_subscriptions.h" +#include "include/notifications.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/events.h" +#include "include/channel_tech.h" +#include "include/rtp_glue.h" +#include "include/cli_commands.h" +#include "include/dialplan_functions.h" +#include "include/dialplan_applications.h" +#include "include/manager.h" +#include "include/pickup.h" +#include "include/conference.h" + +ast_mutex_t sip_config_lock; +int sip_config_reloading = FALSE; /* Flag for avoiding multiple reloads at the same time */ +static enum channelreloadreason sip_reload_reason = 0; + +struct sip_config sip_config; /* SIP configuration data */ + +struct ast_jb_conf sip_jb_config; /* Global jitterbuffer configuration */ +struct ast_tls_config sip_tls_config; /* Default TLS connection configuration */ + +int sip_debug = 0; +struct ast_sockaddr sip_debug_address; + +/* Our (internal) default address/port to put in SIP/SDP messages sip_our_address is initialized picking a suitable + * address from one of the interfaces, and the same port number we bind to. It is used as the default address/port in + * SIP messages, and as the default address (but not port) in SDP messages */ +struct ast_sockaddr sip_our_address; + +STASIS_MESSAGE_TYPE_DEFN(sip_session_timeout_type, + .to_ami = sip_session_timeout_to_ami, +); + +int sip_config_load(void) +{ + /* The fact that ao2_containers can't resize automatically is a major worry! if the number of objects gets + * above bucket limit, things will slow down */ + sip_peers = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 563, sip_peer_hash, NULL, sip_peer_cmp); + sip_peer_addresses = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 563, + sip_peer_address_hash, NULL, sip_peer_address_cmp); + + sip_dialogs = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 1031, sip_dialog_hash, NULL, + sip_dialog_cmp); + sip_dialogs_need_destroy = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 11, NULL, NULL, NULL); + sip_dialogs_with_rtp = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 563, sip_dialog_hash, NULL, + sip_dialog_cmp); + + sip_tcptls_threads = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 1031, + sip_tcptls_thread_hash, NULL, sip_tcptls_thread_cmp); + + sip_domains = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, sip_domain_cmp); + sip_authentication_realms = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, + sip_authentication_realm_cmp); + sip_notifications = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, sip_notification_cmp); + + sip_registrations = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, sip_registration_cmp); + sip_mwi_subscriptions = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, sip_mwi_subscription_cmp); + + sip_conferences = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, sip_conference_cmp); + + if (!sip_peers || !sip_peer_addresses || !sip_dialogs || !sip_dialogs_need_destroy || !sip_dialogs_with_rtp || + !sip_tcptls_threads || !sip_domains || !sip_authentication_realms || !sip_notifications || + !sip_registrations || !sip_mwi_subscriptions || !sip_conferences) { + ast_log(LOG_ERROR, "Unable to create primary SIP container(s)\n"); + return -1; + } + + if (!(sip_config.format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + return -1; + } + + ast_mutex_init(&sip_monitor_lock); + ast_mutex_init(&sip_netsock_lock); + ast_mutex_init(&sip_config_lock); + + if (!(sip_sched_context = ast_sched_context_create())) { + ast_log(LOG_ERROR, "Unable to create scheduler context\n"); + return -1; + } + + if (!(sip_io_context = io_context_create())) { + ast_log(LOG_ERROR, "Unable to create I/O context\n"); + return -1; + } + + sip_reload_reason = CHANNEL_MODULE_LOAD; + + if (sip_config_parse()) { /* Load the configuration from sip.conf */ + return -1; + } + + /* Alloc global invalid peer used for fake auth responses */ + if (!(sip_invalid_peer = sip_peer_temp_alloc("invalid_peer"))) { + ast_log(LOG_ERROR, "Unable to create an invalid peer for authentication\n"); + return -1; + } + + ast_string_field_set(sip_invalid_peer, md5_secret, "invalid_peer_md5secret"); + + if (!(sip_channel_tech.capabilities = ast_format_cap_alloc(0))) { + return -1; + } + + ast_format_cap_append_by_type(sip_channel_tech.capabilities, AST_MEDIA_TYPE_AUDIO); + + /* Make sure we can register our sip channel type */ + if (ast_channel_register(&sip_channel_tech)) { + ast_log(LOG_ERROR, "Unable to register channel type 'SIP'\n"); + return -1; + } + + /* Tell the RTP engine about our RTP glue */ + ast_rtp_glue_register(&sip_rtp_glue); + + if (ast_msg_tech_register(&sip_msg_tech)) { + return -1; + } + + if (STASIS_MESSAGE_TYPE_INIT(sip_session_timeout_type)) { + return -1; + } + + if (ast_sip_api_provider_register(&sip_api_tech)) { + return -1; + } + + /* Register all CLI functions for SIP */ + if (ast_cli_register_multiple(sip_cli_commands, sip_cli_commands_count)) { + return -1; + } + + /* Register dialplan applications */ + ast_register_application_xml("SIPAddHeader", sip_application_add_header); + ast_register_application_xml("SIPRemoveHeader", sip_application_remove_header); + ast_register_application_xml("SIPCiscoPage", sip_application_cisco_page); + + /* Register dialplan functions */ + ast_custom_function_register(&sip_function_peer); + ast_custom_function_register(&sip_function_peer_deprecated); + ast_custom_function_register(&sip_function_header); + ast_custom_function_register(&sip_function_headers); + ast_custom_function_register(&sip_function_check_domain); + ast_custom_function_register(&sip_function_check_domain_deprecated); + + /* Register manager commands */ + ast_manager_register_xml("SIPPeers", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, sip_manager_peers); + ast_manager_register_xml("SIPRegistrations", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, sip_manager_registrations); + ast_manager_register_xml("SIPMWISubscriptions", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, sip_manager_mwi_subscriptions); + ast_manager_register_xml("SIPShowPeer", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, sip_manager_show_peer); + ast_manager_register_xml("SIPPeerStatus", EVENT_FLAG_SYSTEM, sip_manager_peer_status); + ast_manager_register_xml("SIPQualifyPeer", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, sip_manager_qualify_peer); + ast_manager_register_xml("SIPNotify", EVENT_FLAG_SYSTEM, sip_manager_notify); + + if (sip_config.realtime_update_peer) { + ast_realtime_require_field("sippeers", "name", RQ_CHAR, 10, "ipaddr", RQ_CHAR, INET6_ADDRSTRLEN - 1, + "port", RQ_UINTEGER2, 5, "expires", RQ_INTEGER4, 11, "contact", RQ_CHAR, 35, "sysname", RQ_CHAR, 20, + "useragent", RQ_CHAR, 20, "lastqualify", RQ_INTEGER4, 11, SENTINEL); + } + + sip_peer_qualify_all(); + sip_registration_send_all(); + sip_mwi_subscription_send_all(); + + /* And start the monitor for the first time */ + sip_monitor_restart(); + + sip_network_change_subscribe(); + sip_pickup_notify_subscribe(); + + if (ast_fully_booted) { + sip_module_notice(); + } else { + stasis_subscribe_pool(ast_manager_get_topic(), sip_startup_event, NULL); + } + + return 0; +} + +int sip_config_unload(void) +{ + struct sip_dialog *dialog; + struct sip_tcptls_thread *thread; + struct ao2_iterator iter; + struct timeval yield_start; + + ast_sched_dump(sip_sched_context); + + sip_network_change_unsubscribe(); + sip_acl_change_unsubscribe(); + sip_pickup_notify_unsubscribe(); + + /* First, take us out of the channel type list */ + ast_channel_unregister(&sip_channel_tech); + /* Disconnect from RTP engine */ + ast_rtp_glue_unregister(&sip_rtp_glue); + + ast_msg_tech_unregister(&sip_msg_tech); + ast_sip_api_provider_unregister(); + + /* Unregister dial plan applications */ + ast_unregister_application("SIPAddHeader"); + ast_unregister_application("SIPRemoveHeader"); + ast_unregister_application("SIPCiscoPage"); + + /* Unregister dial plan functions */ + ast_custom_function_unregister(&sip_function_peer); + ast_custom_function_unregister(&sip_function_peer_deprecated); + ast_custom_function_unregister(&sip_function_headers); + ast_custom_function_unregister(&sip_function_header); + ast_custom_function_unregister(&sip_function_check_domain); + ast_custom_function_unregister(&sip_function_check_domain_deprecated); + + /* Unregister AMI actions */ + ast_manager_unregister("SIPPeers"); + ast_manager_unregister("SIPRegistrations"); + ast_manager_unregister("SIPMWISubscriptions"); + ast_manager_unregister("SIPShowPeer"); + ast_manager_unregister("SIPPeerStatus"); + ast_manager_unregister("SIPQualifyPeer"); + ast_manager_unregister("SIPNotify"); + + /* Unregister CLI commands */ + ast_cli_unregister_multiple(sip_cli_commands, sip_cli_commands_count); + + /* Kill TCP/TLS server threads */ + if (sip_tcp_session.master) { + ast_tcptls_server_stop(&sip_tcp_session); + } + + if (sip_tls_session.master) { + ast_tcptls_server_stop(&sip_tls_session); + } + + ast_ssl_teardown(sip_tls_session.tls_cfg); + /* Kill all existing TCP/TLS threads */ + iter = ao2_iterator_init(sip_tcptls_threads, 0); + + while ((thread = ao2_iterator_next(&iter))) { + pthread_kill(thread->threadid, SIGURG); + ao2_ref(thread, -1); + } + + ao2_iterator_destroy(&iter); + /* Hangup all dialogs if they have an owner */ + iter = ao2_iterator_init(sip_dialogs, 0); + + while ((dialog = ao2_iterator_next(&iter))) { + if (dialog->channel) { + ast_softhangup(dialog->channel, AST_SOFTHANGUP_APPUNLOAD); + } + + ao2_ref(dialog, -1); + } + + ao2_iterator_destroy(&iter); + ast_mutex_lock(&sip_monitor_lock); + + if (sip_monitor_threadid != AST_PTHREADT_STOP && sip_monitor_threadid != AST_PTHREADT_NULL) { + pthread_t threadid = sip_monitor_threadid; + + sip_monitor_threadid = AST_PTHREADT_STOP; + + pthread_cancel(threadid); + pthread_kill(threadid, SIGURG); + + ast_mutex_unlock(&sip_monitor_lock); + pthread_join(threadid, NULL); + } else { + sip_monitor_threadid = AST_PTHREADT_STOP; + ast_mutex_unlock(&sip_monitor_lock); + } + + /* Clear containers */ + ao2_callback(sip_domains, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, NULL, NULL); + ao2_callback(sip_authentication_realms, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, NULL, NULL); + ao2_callback(sip_notifications, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, NULL, NULL); + + ao2_callback(sip_registrations, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, sip_registration_unlink, NULL); + ao2_callback(sip_mwi_subscriptions, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, sip_mwi_subscription_unlink, NULL); + + ao2_callback(sip_peers, OBJ_NODATA | OBJ_MULTIPLE, sip_peer_set_removed, NULL); + ao2_callback(sip_peers, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, sip_peer_unlink, NULL); + + /* Destroy all the dialogs and free their memory */ + iter = ao2_iterator_init(sip_dialogs, 0); + + while ((dialog = ao2_iterator_next(&iter))) { + sip_dialog_unlink(dialog); + ao2_ref(dialog, -1); + } + + ao2_iterator_destroy(&iter); + /* Since the monitor thread runs the scheduled events and we just stopped the monitor thread above, we have to + * run any pending scheduled immediate events in this thread */ + ast_sched_runq(sip_sched_context); + + /* Wait awhile for the TCP/TLS thread container to become empty. FIXME: This is a hack, but the worker threads + * cannot be created joinable. They can die on their own and remove themselves from the container thus resulting + * in a huge memory leak */ + yield_start = ast_tvnow(); + + while (ao2_container_count(sip_tcptls_threads) && (ast_tvdiff_sec(ast_tvnow(), yield_start) < 5)) { + sched_yield(); + } + + if (ao2_container_count(sip_tcptls_threads)) { + ast_debug(2, "TCP/TLS thread container did not become empty\n"); + return -1; + } + + /* Free memory for local network address mask */ + ast_free_ha(sip_config.internal_networks); + + ast_free(sip_tls_config.certfile); + ast_free(sip_tls_config.pvtfile); + ast_free(sip_tls_config.cipher); + ast_free(sip_tls_config.cafile); + ast_free(sip_tls_config.capath); + + ast_unload_realtime("sippeers"); + + ao2_cleanup(sip_invalid_peer); + ao2_cleanup(sip_peers); + ao2_cleanup(sip_peer_addresses); + + ao2_cleanup(sip_dialogs); + ao2_cleanup(sip_dialogs_need_destroy); + ao2_cleanup(sip_dialogs_with_rtp); + ao2_cleanup(sip_tcptls_threads); + + ao2_cleanup(sip_domains); + ao2_cleanup(sip_authentication_realms); + ao2_cleanup(sip_notifications); + + ao2_cleanup(sip_registrations); + ao2_cleanup(sip_mwi_subscriptions); + ao2_cleanup(sip_conferences); + + if (sip_socket_io_id) { + ast_io_remove(sip_io_context, sip_socket_io_id); + + sip_socket_io_id = NULL; + } + + if (sip_socket_fd != -1) { + close(sip_socket_fd); + + sip_socket_fd = -1; + } + + io_context_destroy(sip_io_context); + ast_sched_context_destroy(sip_sched_context); + + sip_io_context = NULL; + sip_sched_context = NULL; + + ast_mutex_destroy(&sip_netsock_lock); + ast_mutex_destroy(&sip_monitor_lock); + ast_mutex_destroy(&sip_config_lock); + + ao2_cleanup(sip_channel_tech.capabilities); + sip_channel_tech.capabilities = NULL; + + ao2_cleanup(sip_config.format_cap); + sip_config.format_cap = NULL; + + STASIS_MESSAGE_TYPE_CLEANUP(sip_session_timeout_type); + return 0; +} + +int sip_config_reload(enum channelreloadreason reason) +{ + ast_mutex_lock(&sip_config_lock); + + if (sip_config_reloading) { + ast_verb(3, "Previous SIP reload not yet done\n"); + ast_mutex_unlock(&sip_config_lock); + + return -1; + } + + sip_config_reloading = TRUE; + sip_reload_reason = reason; + + ast_mutex_unlock(&sip_config_lock); + sip_monitor_restart(); + + return 0; +} + +/* Re-read sip.conf config file. This function reloads all config data, except for active peers (with registrations). + * They will only change configuration data at restart, not at reload */ +int sip_config_parse(void) +{ + struct ast_config *config; + struct ast_variable *variable; + struct sip_peer *peer; + char *category; + struct ast_flags flags; + struct ast_sockaddr old_bind_address; + int subscribe_network_change, subscribe_acl_change; + struct timeval reload_start, reload_end; + struct ao2_iterator iter; + + reload_start = ast_tvnow(); + + ast_unload_realtime("sippeers"); + memset(&flags, 0, sizeof(flags)); + + if (sip_reload_reason != CHANNEL_MODULE_LOAD && sip_reload_reason != CHANNEL_ACL_RELOAD && + !sip_config.realtime_cache_peer) { + ast_set_flag(&flags, CONFIG_FLAG_FILEUNCHANGED); + } + + config = ast_config_load("sip.conf", flags); + + /* We *must* have a config file otherwise stop immediately */ + if (config == CONFIG_STATUS_FILEMISSING) { + ast_log(LOG_NOTICE, "Unable to load config sip.conf\n"); + return -1; + } else if (config == CONFIG_STATUS_FILEUNCHANGED) { + return 0; + } else if (config == CONFIG_STATUS_FILEINVALID) { + ast_log(LOG_ERROR, "Contents of sip.conf are invalid and cannot be parsed\n"); + return -1; + } + + if (sip_reload_reason != CHANNEL_MODULE_LOAD) { + ast_debug(4, "SIP reload started\n"); + + ao2_callback(sip_domains, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, NULL, NULL); + ao2_callback(sip_authentication_realms, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, NULL, NULL); + ao2_callback(sip_notifications, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, NULL, NULL); + + ao2_callback(sip_registrations, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, sip_registration_unlink, NULL); + ao2_callback(sip_mwi_subscriptions, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, sip_mwi_subscription_unlink, NULL); + + ao2_callback(sip_peers, OBJ_NODATA | OBJ_MULTIPLE, sip_peer_set_removed, NULL); + } + + /* Reset certificate handling for TLS sessions */ + if (sip_reload_reason != CHANNEL_MODULE_LOAD) { + ast_free(sip_tls_config.certfile); + ast_free(sip_tls_config.pvtfile); + ast_free(sip_tls_config.cipher); + ast_free(sip_tls_config.cafile); + ast_free(sip_tls_config.capath); + } + + sip_tls_config.certfile = ast_strdup(AST_CERTFILE); /* Not sure if this is useful */ + sip_tls_config.pvtfile = ast_strdup(""); + sip_tls_config.cipher = ast_strdup(""); + sip_tls_config.cafile = ast_strdup(""); + sip_tls_config.capath = ast_strdup(""); + + ast_format_cap_remove_by_type(sip_config.format_cap, AST_MEDIA_TYPE_UNKNOWN); + + ast_format_cap_append(sip_config.format_cap, ast_format_ulaw, 0); + ast_format_cap_append(sip_config.format_cap, ast_format_alaw, 0); + ast_format_cap_append(sip_config.format_cap, ast_format_gsm, 0); + ast_format_cap_append(sip_config.format_cap, ast_format_h263, 0); + + /* Clear all flags before setting default values. Preserve debugging settings for console */ + sip_debug &= SIP_DEBUG_CONSOLE; + sip_debug &= ~SIP_DEBUG_CONFIG; + + /* Copy the default jb config over sip_jb_config */ + memset(&sip_jb_config, 0, sizeof(sip_jb_config)); + ast_copy_string(sip_jb_config.impl, "fixed", sizeof(sip_jb_config.impl)); + + sip_jb_config.max_size = 200; + sip_jb_config.resync_threshold = 1000; + sip_jb_config.target_extra = 40; + + /* Reset IP addresses */ + ast_sockaddr_parse(&sip_config.udp_bind_address, "0.0.0.0:0", 0); + ast_sockaddr_setnull(&sip_our_address); + + ast_sockaddr_setnull(&sip_config.external_address); + ast_sockaddr_setnull(&sip_config.media_address); + ast_sockaddr_setnull(&sip_config.rtp_bind_address); + + ast_str2tos("CS3", &sip_config.tos_sip); + ast_str2tos("EF", &sip_config.tos_audio); + ast_str2tos("AF41", &sip_config.tos_video); + ast_str2tos("AF41", &sip_config.tos_text); + + sip_config.cos_sip = 4; + sip_config.cos_audio = 5; + sip_config.cos_video = 6; + sip_config.cos_text = 5; + + sip_config.tcp_enabled = FALSE; + sip_config.tcp_authentication_limit = 100; + sip_config.tcp_authentication_timeout = 30; + + sip_tls_config.enabled = FALSE; /* Default: Disable TLS */ + + sip_config.nat_auto_rport = FALSE; + sip_config.from_domain[0] = '\0'; + sip_config.from_domain_port = 0; + sip_config.tone_zone[0] = '\0'; + sip_config.language[0] = '\0'; + sip_config.moh_suggest[0] = '\0'; + + snprintf(sip_config.useragent, sizeof(sip_config.useragent), "Asterisk PBX %s", ast_get_version()); + snprintf(sip_config.sdp_session, sizeof(sip_config.sdp_session), "Asterisk PBX %s", ast_get_version()); + + ast_copy_string(sip_config.sdp_username, "Asterisk-SIP", sizeof(sip_config.sdp_username)); + ast_copy_string(sip_config.realm, S_OR(ast_config_AST_SYSTEM_NAME, "asterisk"), sizeof(sip_config.realm)); + ast_copy_string(sip_config.moh_interpret, "default", sizeof(sip_config.moh_interpret)); + + sip_config.timer_t1 = 500; /* SIP timer T1 (according to RFC 3261) */ + sip_config.min_timer_t1 = 100; /* 100 MS for minimal roundtrip time */ + sip_config.timer_b = sip_config.timer_t1 * 64; + + sip_config.shrink_callerid = TRUE; + sip_config.srv_lookup = FALSE; + sip_config.allow_early_media = TRUE; + sip_config.direct_rtp_setup = FALSE; /* Experimental feature, disabled by default */ + + sip_config.match_authorization_username = FALSE; /* Match Authorization: username if available instead of From: */ + sip_config.always_send_unauthorized = TRUE; + sip_config.authentication_failure_events = FALSE; + sip_config.authenticate_options = FALSE; + sip_config.allow_message = FALSE; + + sip_config.default_expires = 120; + sip_config.register_min_expires = 60; + sip_config.register_max_expires = 3600; + sip_config.subscribe_min_expires = sip_config.register_min_expires; + sip_config.subscribe_max_expires = sip_config.register_max_expires; + sip_config.mwi_expires = sip_config.default_expires; + + sip_config.register_timeout = 20; + sip_config.register_max_attempts = 1; + sip_config.register_retry_forbidden = FALSE; + + sip_config.rtp_timeout = 0; + sip_config.rtp_hold_timeout = 0; + sip_config.rtp_keepalive = FALSE; + + /* Peer qualification settings */ + sip_config.qualify_peers = 1; /* Number of peers to qualify at one time */ + sip_config.qualify_max = 2000; + sip_config.qualify_gap = 100; + sip_config.qualify_expires = 60; + + sip_config.domains_as_realm = FALSE; + sip_config.allow_external_domains = TRUE; /* Allow external invites */ + + /* Free memory for local network address mask */ + ast_free_ha(sip_config.internal_networks); + + sip_config.internal_networks = NULL; + sip_config.external_host[0] = '\0'; /* External host name (for behind NAT DynDNS support) */ + sip_config.external_expires = 10; /* Expiration for DNS re-issuing */ + sip_config.external_tcp_port = 0; + sip_config.external_tls_port = 0; + + sip_config.notify_callerid = FALSE; + sip_config.pickup_context = FALSE; + + memset(&sip_config.proxy, 0, sizeof(sip_config.proxy)); + sip_config.proxy.force = FALSE; /* Don't force proxy usage, use route: headers */ + + sip_config.realtime_auto_clear = 0; + sip_config.realtime_update_peer = TRUE; + + subscribe_network_change = FALSE; + subscribe_acl_change = FALSE; + + ast_sockaddr_copy(&old_bind_address, &sip_config.udp_bind_address); + + /* Read the [general] config section of sip.conf (or from realtime config) */ + for (variable = ast_variable_browse(config, "general"); variable; variable = variable->next) { + if (!ast_jb_read_conf(&sip_jb_config, variable->name, variable->value)) { + continue; + } else if (!ast_tls_read_conf(&sip_tls_config, &sip_tls_session, variable->name, variable->value)) { + continue; + } else if (!strncasecmp(variable->name, "dtls", 4)) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d\n", + variable->name, variable->lineno); + continue; + } + + if (!strcasecmp(variable->name, "debug") || !strcasecmp(variable->name, "sipdebug")) { + if (strcasecmp(variable->name, "debug")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "debug", variable->lineno); + } + + if (ast_true(variable->value)) { + sip_debug |= SIP_DEBUG_CONFIG; + } else { + sip_debug &= ~SIP_DEBUG_CONFIG; + } + } else if (!strcasecmp(variable->name, "timert1")) { + /* Defaults to 500ms, but RFC 3261 states that it is recommended for the value to be set higher, + * though a lower value is only allowed on private networks unconnected to the Internet */ + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.timer_t1, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "t1_min") || !strcasecmp(variable->name, "timert1min")) { + if (strcasecmp(variable->name, "timert1min")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "timert1min", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.min_timer_t1, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "timerb")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.timer_b, 500, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "bindaddr") || !strcasecmp(variable->name, "udpbindaddr")) { + if (strcasecmp(variable->name, "udpbindaddr")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "udpbindaddr", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_ADDR, &sip_config.udp_bind_address)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "tcpenable")) { + sip_config.tcp_enabled = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "tcpbindaddr")) { + if (ast_parse_arg(variable->value, PARSE_ADDR, &sip_tcp_session.local_address)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "tcpauthtimeout")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.tcp_authentication_timeout, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "tcpauthlimit")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.tcp_authentication_limit, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "nat")) { + char *nat, *option; + + sip_config.nat_force_rport = FALSE; + sip_config.nat_auto_rport = FALSE; + sip_config.nat_rtp = FALSE; + sip_config.nat_auto_rtp = FALSE; + + nat = ast_strdupa(variable->value); + + while ((option = strsep(&nat, ","))) { + if (ast_false(option)) { + sip_config.nat_force_rport = FALSE; + sip_config.nat_auto_rport = FALSE; + sip_config.nat_rtp = FALSE; + sip_config.nat_auto_rtp = FALSE; + } else if (ast_true(option)) { + ast_log(LOG_WARNING, + "Option '%s=%s' is no longer supported use 'force_rport,rtp' instead at line %d\n", + variable->name, option, variable->lineno); + } else if (!strcasecmp(option, "force_rport") && !sip_config.nat_auto_rport) { + sip_config.nat_force_rport = TRUE; + } else if (!strcasecmp(option, "auto_force_rport")) { + sip_config.nat_auto_rport = TRUE; + sip_config.nat_force_rport = FALSE; /* In case someone did something dumb like + * nat=force_rport,auto_force_rport */ + } else if ((!strcasecmp(option, "comedia") || !strcasecmp(option, "rtp")) + && !sip_config.nat_auto_rtp) { + if (strcasecmp(option, "rtp")) { + ast_log(LOG_WARNING, + "Option '%s=%s' is deprecated, use '%s' instead at line %d\n", + variable->name, option, "rtp", variable->lineno); + } + + sip_config.nat_rtp = TRUE; + } else if (!strcasecmp(option, "auto_comedia") || !strcmp(option, "auto_rtp")) { + if (strcasecmp(option, "auto_rtp")) { + ast_log(LOG_WARNING, + "Option '%s=%s' is deprecated, use '%s' instead at line %d\n", + variable->name, option, "auto_rtp", variable->lineno); + } + + sip_config.nat_auto_rtp = TRUE; + sip_config.nat_rtp = FALSE; /* In case someone did something dumb like + * nat=rtp,auto_rtp */ + } else { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, option, variable->lineno); + } + } + } else if (!strcasecmp(variable->name, "useragent")) { + ast_copy_string(sip_config.useragent, variable->value, sizeof(sip_config.useragent)); + } else if (!strcasecmp(variable->name, "sdpsession")) { + ast_copy_string(sip_config.sdp_session, variable->value, sizeof(sip_config.sdp_session)); + } else if (!strcasecmp(variable->name, "sdpowner") || !strcasecmp(variable->name, "sdpusername")) { + if (strcasecmp(variable->name, "sdpusername")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "sdpusername", variable->lineno); + } + + if (strchr(variable->value, ' ')) { + ast_log(LOG_WARNING, "'%s' must not contain spaces at line %d\n", + variable->value, variable->lineno); + } else { + ast_copy_string(sip_config.sdp_username, variable->value, sizeof(sip_config.sdp_username)); + } + } else if (!strcasecmp(variable->name, "rtptimeout")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.rtp_timeout, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "rtpholdtimeout")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.rtp_hold_timeout, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "rtpkeepalive")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.rtp_keepalive, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "directrtpsetup")) { + sip_config.direct_rtp_setup = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "allow")) { + if (ast_format_cap_update_by_allow_disallow(sip_config.format_cap, variable->value, TRUE)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "disallow")) { + if (ast_format_cap_update_by_allow_disallow(sip_config.format_cap, variable->value, FALSE)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "alwaysauthreject")) { + sip_config.always_send_unauthorized = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "authfailureevents")) { + sip_config.authentication_failure_events = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "auth_options_requests") || + !strcasecmp(variable->name, "authoptions")) { + if (strcasecmp(variable->name, "authoptions")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "authoptionsrequests", variable->lineno); + } + + sip_config.authenticate_options = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "accept_outofcall_message") || + !strcasecmp(variable->name, "allowmessage")) { + if (strcasecmp(variable->name, "allowmessage")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "allowmessage", variable->lineno); + } + + sip_config.allow_message = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "match_auth_username") || + !strcasecmp(variable->name, "matchauthusername")) { + if (strcasecmp(variable->name, "matchauthusername")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "matchauthusername", variable->lineno); + } + + sip_config.match_authorization_username = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "mohinterpret")) { + ast_copy_string(sip_config.moh_interpret, variable->value, sizeof(sip_config.moh_interpret)); + } else if (!strcasecmp(variable->name, "mohsuggest")) { + ast_copy_string(sip_config.moh_suggest, variable->value, sizeof(sip_config.moh_suggest)); + } else if (!strcasecmp(variable->name, "tonezone")) { + struct ast_tone_zone *tone_zone; + + if ((tone_zone = ast_get_indication_zone(variable->value))) { + ast_tone_zone_unref(tone_zone); + ast_copy_string(sip_config.tone_zone, variable->value, sizeof(sip_config.tone_zone)); + } else { + ast_log(LOG_ERROR, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "language")) { + ast_copy_string(sip_config.language, variable->value, sizeof(sip_config.language)); + } else if (!strcasecmp(variable->name, "srvlookup")) { + sip_config.srv_lookup = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "shrinkcallerid")) { + sip_config.shrink_callerid = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "prematuremedia") || + !strcasecmp(variable->name, "allowearlymedia")) { + if (strcasecmp(variable->name, "allowearlymedia")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "allowearlymedia", variable->lineno); + } + + sip_config.allow_early_media = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "defaultexpiry") || !strcasecmp(variable->name, "defaultexpires")) { + if (strcasecmp(variable->name, "defaultexpires")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "defaultexpires", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.default_expires, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "maxexpiry") || + !strcasecmp(variable->name, "registermaxexpires")) { + if (strcasecmp(variable->name, "registermaxexpires")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "registermaxexpires", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.register_max_expires, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "minexpiry") || + !strcasecmp(variable->name, "registerminexpires")) { + if (strcasecmp(variable->name, "registerminexpires")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "registerminexpires", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.register_min_expires, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "submaxexpiry") || + !strcasecmp(variable->name, "subscribemaxexpires")) { + if (strcasecmp(variable->name, "subscribemaxexpires")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "subscribemaxexpires", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.subscribe_max_expires, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "subminexpiry") || + !strcasecmp(variable->name, "subscribeminexpires")) { + if (strcasecmp(variable->name, "subscribeminexpires")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "subscribeminexpires", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.subscribe_min_expires, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "localnet") || !strcasecmp(variable->name, "localnetwork")) { + struct ast_ha *internal_networks; + int ha_error; + + if (strcasecmp(variable->name, "localnetwork")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "localnetwork", variable->lineno); + } + + ha_error = 0; + + if ((internal_networks = ast_append_ha("d", variable->value, sip_config.internal_networks, + &ha_error)) && !ha_error) { + sip_config.internal_networks = internal_networks; + } else { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "media_address") || !strcasecmp(variable->name, "mediaaddr")) { + if (strcasecmp(variable->name, "mediaaddr")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "mediaaddr", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_ADDR, &sip_config.media_address)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "rtpbindaddr")) { + if (ast_parse_arg(variable->value, PARSE_ADDR, &sip_config.rtp_bind_address)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "externaddr") || !strcasecmp(variable->name, "externaladdr")) { + if (strcasecmp(variable->name, "externaladdr")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "externaladdr", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_ADDR, &sip_config.external_address)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + + sip_config.external_expires = 0; + } else if (!strcasecmp(variable->name, "externhost") || !strcasecmp(variable->name, "externalhost")) { + if (strcasecmp(variable->name, "externalhost")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "externalhost", variable->lineno); + } + + ast_copy_string(sip_config.external_host, variable->value, sizeof(sip_config.external_host)); + + if (ast_sockaddr_resolve_first_af(&sip_config.external_address, sip_config.external_host, 0, AST_AF_INET)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + + sip_config.external_expires = time(NULL); + } else if (!strcasecmp(variable->name, "externrefresh") || !strcasecmp(variable->name, "externalexpiry")) { + if (strcasecmp(variable->name, "externalexpiry")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "externalexpiry", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.external_expires, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "externtcpport") || !strcasecmp(variable->name, "externaltcpport")) { + if (strcasecmp(variable->name, "externaltcpport")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "externaltcpport", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.external_tcp_port, 0, USHRT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' must be a positive integer between 1 and 65535 at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "externtlsport") || !strcasecmp(variable->name, "externaltlsport")) { + if (strcasecmp(variable->name, "externaltlsport")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "externaltlsport", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.external_tls_port, 0, USHRT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' must be a positive integer between 1 and 65535 at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "fromdomain")) { + char *from_domain = ast_strdupa(variable->value); + + if (sip_parse_port(from_domain, &sip_config.from_domain_port)) { + ast_log(LOG_NOTICE, "Invalid option '%s=%s' at line %d'\n", + variable->name, variable->value, variable->lineno); + } else { + ast_copy_string(sip_config.from_domain, from_domain, sizeof(sip_config.from_domain)); + + if (!sip_config.from_domain_port) { + sip_config.from_domain_port = SIP_STANDARD_PORT; + } + } + } else if (!strcasecmp(variable->name, "domain")) { + sip_domain_build(variable->value, variable->lineno); + } else if (!strcasecmp(variable->name, "allowexternaldomains")) { + sip_config.allow_external_domains = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "realm")) { + ast_copy_string(sip_config.realm, variable->value, sizeof(sip_config.realm)); + } else if (!strcasecmp(variable->name, "domainsasrealm")) { + sip_config.domains_as_realm = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "authentication")) { + sip_authentication_realm_build(sip_authentication_realms, variable->value, variable->lineno); + } else if (!strcasecmp(variable->name, "register")) { + sip_registration_build(variable->value, variable->lineno); + } else if (!strcasecmp(variable->name, "registertimeout")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.register_timeout, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "registerattempts")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.register_max_attempts, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "register_retry_403") || + !strcasecmp(variable->name, "registerretryforbidden")) { + if (strcasecmp(variable->name, "registerretryforbidden")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "registerretryforbidden", variable->lineno); + } + + sip_config.register_retry_forbidden = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "mwi")) { + sip_mwi_subscription_build(variable->value, variable->lineno); + } else if (!strcasecmp(variable->name, "outboundproxy") || !strcasecmp(variable->name, "proxy")) { + if (strcasecmp(variable->name, "proxy")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "proxy", variable->lineno); + } + + sip_proxy_build(variable->value, variable->lineno, &sip_config.proxy); + } else if (!strcasecmp(variable->name, "tos_sip") || !strcasecmp(variable->name, "tossip")) { + if (strcasecmp(variable->name, "tossip")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "tossip", variable->lineno); + } + + if (ast_str2tos(variable->value, &sip_config.tos_sip)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "tos_audio") || !strcasecmp(variable->name, "tosaudio")) { + if (strcasecmp(variable->name, "tosaudio")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "tosaudio", variable->lineno); + } + + if (ast_str2tos(variable->value, &sip_config.tos_audio)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "tos_video") || !strcasecmp(variable->name, "tosvideo")) { + if (strcasecmp(variable->name, "tosvideo")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "tosvideo", variable->lineno); + } + + if (ast_str2tos(variable->value, &sip_config.tos_video)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "tos_text") || !strcasecmp(variable->name, "tostext")) { + if (strcasecmp(variable->name, "tostext")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "tostext", variable->lineno); + } + + if (ast_str2tos(variable->value, &sip_config.tos_text)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "cos_sip") || !strcasecmp(variable->name, "cossip")) { + if (strcasecmp(variable->name, "cossip")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "cossip", variable->lineno); + } + + if (ast_str2cos(variable->value, &sip_config.cos_sip)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "cos_audio") || !strcasecmp(variable->name, "cosaudio")) { + if (strcasecmp(variable->name, "cosaudio")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "cosaudio", variable->lineno); + } + + if (ast_str2cos(variable->value, &sip_config.cos_audio)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "cos_video") || !strcasecmp(variable->name, "cosvideo")) { + if (strcasecmp(variable->name, "cosvideo")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "cosvideo", variable->lineno); + } + + if (ast_str2cos(variable->value, &sip_config.cos_video)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "cos_text") || !strcasecmp(variable->name, "costext")) { + if (strcasecmp(variable->name, "costext")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "costext", variable->lineno); + } + + if (ast_str2cos(variable->value, &sip_config.cos_text)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "qualify") || !strcasecmp(variable->name, "maxqualify")) { + if (strcasecmp(variable->name, "maxqualify")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "maxqualify", variable->lineno); + } + + if (!strcasecmp(variable->value, "no")) { + sip_config.qualify_max = 0; + } else if (!strcasecmp(variable->value, "yes")) { + sip_config.qualify_max = 2000; + } else if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.qualify_max, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s', should be 'yes', 'no', or a number of milliseconds at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "qualifyfreq") || !strcasecmp(variable->name, "qualifyexpiry")) { + if (strcasecmp(variable->name, "qualifyexpiry")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "qualifyexpiry", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.qualify_expires, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "qualifypeers")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.qualify_peers, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid qualifypeers '%s' at line %d\n", + variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "qualifygap")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.qualify_gap, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "notifycid") || !strcasecmp(variable->name, "notifycallerid")) { + if (strcasecmp(variable->name, "notifycallerid")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "notifycallerid", variable->lineno); + } + + if (!strcasecmp(variable->value, "ignore-context")) { + ast_log(LOG_WARNING, "Option '%s=%s' is deprecated, include 'pickupcontext=yes' instead at line %d\n", + variable->name, variable->value, variable->lineno); + + sip_config.notify_callerid = TRUE; + sip_config.pickup_context = TRUE; + } else { + sip_config.notify_callerid = ast_true(variable->value); + } + } else if (!strcasecmp(variable->name, "pickupcontext")) { + sip_config.pickup_context = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "subscribe_network_change_event") || + !strcasecmp(variable->name, "networkchangeevent")) { + if (strcasecmp(variable->name, "networkchangeevent")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "networkchangeevent", variable->lineno); + } + + subscribe_network_change = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "rtcachepeers") || + !strcasecmp(variable->name, "rtcachefriends") || + !strcasecmp(variable->name, "realtimecachepeers")) { + if (strcasecmp(variable->name, "realtimecachepeers")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "realtimecachepeers", variable->lineno); + } + + sip_config.realtime_cache_peer = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "rtsavesysname") || + !strcasecmp(variable->name, "realtimesavesysname")) { + if (strcasecmp(variable->name, "realtimesavesysname")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "realtimesavesysname", variable->lineno); + } + + sip_config.realtime_save_sysname = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "rtsavepath") || + !strcasecmp(variable->name, "realtimesavepath")) { + if (strcasecmp(variable->name, "realtimesavepath")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "realtimesavepath", variable->lineno); + } + + sip_config.realtime_save_path = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "rtupdate") || + !strcasecmp(variable->name, "realtimeupdate")) { + if (strcasecmp(variable->name, "realtimeupdate")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "realtimeupdate", variable->lineno); + } + + sip_config.realtime_update_peer = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "ignoreregexpire") || + !strcasecmp(variable->name, "realtimeignoreexpires")) { + if (strcasecmp(variable->name, "realtimeignoreexpires")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "realtimeignoreexpires", variable->lineno); + } + + sip_config.realtime_ignore_expires = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "rtautoclear") || + !strcasecmp(variable->name, "realtimeautoclear")) { + if (strcasecmp(variable->name, "realtimeautoclear")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "realtimeautoclear", variable->lineno); + } + + if (ast_true(variable->value)) { + sip_config.realtime_auto_clear = 120; + } else if (ast_false(variable->value)) { + sip_config.realtime_auto_clear = 0; + } else if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &sip_config.realtime_auto_clear, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "notifyringing")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d (always enabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "notifyhold")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d (always enabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "rpid_update") || !strcasecmp(variable->name, "rpid_immediate")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d (always enabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "pedantic")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d (always enabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "keepalive")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "matchexternaladdrlocally")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "dumphistory") || !strcasecmp(variable->name, "recordhistory")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "insecure")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "allowguest")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "regextenonqualify")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "legacy_useroption_parsing")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "compactheaders")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "autocreatepeer")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "snom_aoc_enabled")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "discard_remote_hold_retrieval")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "ignoresdpversion")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "dynamic_exclude_static") || + !strcasecmp(variable->name, "dynamic_excludes_static")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "refer_addheaders")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "autodomain")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "transport")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "context") || !strcasecmp(variable->name, "subscribecontext")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "outofcall_message_context")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "callcounter")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "vmexten")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "parkinglot")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "maxforwards")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "usereqphone")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "preferred_codec_only")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "relaxdtmf")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "use_q850_reason")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "icesupport")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "send_diversion")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "callerid")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "mwi_from")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "fromdomain")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "sendrpid") || !strcasecmp(variable->name, "trustrpid")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "supportpath")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "trust_id_outbound")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "g726nonstandard")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "dtmfmode")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "directmedia") || !strcasecmp(variable->name, "canreinvite")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "promiscredir")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "videosupport") || !strcasecmp(variable->name, "textsupport")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "autoframing")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "allowoverlap")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "allowsubscribe")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "allowtransfer")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "contactpermit") || !strcasecmp(variable->name, "contactdeny") || + !strcasecmp(variable->name, "contactacl")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "faxdetect")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "rfc2833compensate")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "maxcallbitrate")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "rtcp_mux")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "dndbusy") || !strcasecmp(variable->name, "huntgroup_default")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "cisco_usecallmanager")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "cisco_keep_conference") || + !strcasecmp(variable->name, "cisco_multiadmin_conference")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "session-timers")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "session-expires")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "session-minse")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "session-refresher")) { + ast_log(LOG_WARNING, "Option '%s' cannot be set in [general] anymore, instead set it on a peer at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "websocket_write_timeout")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "websocket_enabled")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "recordonfeature") || + !strcasecmp(variable->name, "recordofffeature")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "auth_message_requests")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "regcontext")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "notifymimetype")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "disallowed_methods")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "bindport")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "mwiexpiry")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported, use 'subscribemaxexpiry' instead at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "storesipcause")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported, use HANGUPCAUSE instead at line %d\n", + variable->name, variable->lineno); + } else { + ast_log(LOG_WARNING, "Unknown option '%s' at line %d\n", + variable->name, variable->lineno); + } + } + + if (sip_config.timer_t1 < sip_config.min_timer_t1) { + ast_log(LOG_WARNING, "'timert1' cannot be lower than global 'timert1min', setting to %d\n", + sip_config.min_timer_t1); + sip_config.timer_t1 = sip_config.min_timer_t1; + } + + if (sip_config.timer_b < sip_config.timer_t1 * 64) { + ast_log(LOG_WARNING, "'timerb' set lower than recommended value of %d\n", sip_config.timer_t1 * 64); + } + + if (!sip_config.allow_external_domains && !ao2_container_count(sip_domains)) { + ast_log(LOG_WARNING, "To disallow external domains, you need to configure local SIP domains\n"); + sip_config.allow_external_domains = TRUE; + } + + if (!ast_sockaddr_port(&sip_config.udp_bind_address)) { + ast_sockaddr_set_port(&sip_config.udp_bind_address, SIP_STANDARD_PORT); + } + + /* Set UDP address and open socket */ + ast_sockaddr_copy(&sip_our_address, &sip_config.udp_bind_address); + + if (ast_find_ourip(&sip_our_address, &sip_config.udp_bind_address, 0)) { + ast_log(LOG_WARNING, "Unable to get own IP address, SIP disabled\n"); + ast_config_destroy(config); + return -1; + } + + ast_mutex_lock(&sip_netsock_lock); + + if (sip_socket_fd != -1 && ast_sockaddr_cmp(&old_bind_address, &sip_config.udp_bind_address)) { + close(sip_socket_fd); + sip_socket_fd = -1; + } + + if (sip_socket_fd == -1) { + if ((sip_socket_fd = socket(ast_sockaddr_is_ipv6(&sip_config.udp_bind_address) ? AF_INET6 : AF_INET, SOCK_DGRAM, 0)) == -1) { + ast_log(LOG_WARNING, "Unable to create UDP socket: %s\n", strerror(errno)); + + ast_config_destroy(config); + ast_mutex_unlock(&sip_netsock_lock); + return -1; + } + + if (ast_bind(sip_socket_fd, &sip_config.udp_bind_address) == -1) { + ast_log(LOG_WARNING, "Failed to bind to UDP socket %s: %s\n", + ast_sockaddr_stringify(&sip_config.udp_bind_address), strerror(errno)); + + close(sip_socket_fd); + sip_socket_fd = -1; + return -1; + } + + ast_enable_packet_fragmentation(sip_socket_fd); + } + + if (sip_socket_fd > -1) { + int reuse = TRUE; + + if (setsockopt(sip_socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))) { + ast_log(LOG_ERROR, "Error enabling UDP reuse-addr on socket: %s\n", strerror(errno)); + } + + ast_set_qos(sip_socket_fd, sip_config.tos_sip, sip_config.cos_sip, "SIP"); + ast_verb(5, "SIP listening on 'UDP:%s'\n", ast_sockaddr_stringify(&sip_config.udp_bind_address)); + } + + ast_mutex_unlock(&sip_netsock_lock); + + /* Start TCP server */ + if (sip_config.tcp_enabled) { + if (ast_sockaddr_isnull(&sip_tcp_session.local_address)) { + ast_sockaddr_copy(&sip_tcp_session.local_address, &sip_config.udp_bind_address); + } + + if (!ast_sockaddr_port(&sip_tcp_session.local_address)) { + ast_sockaddr_set_port(&sip_tcp_session.local_address, SIP_STANDARD_PORT); + } + } else { + ast_sockaddr_setnull(&sip_tcp_session.local_address); + } + + ast_tcptls_server_start(&sip_tcp_session); + + if (sip_config.tcp_enabled && sip_tcp_session.accept_fd == -1) { + /* TCP server start failed. Tell the admin */ + ast_log(LOG_ERROR, "SIP TCP server start failed. Not listening on TCP socket\n"); + } else { + ast_debug(2, "SIP TCP server started\n"); + + if (sip_tcp_session.accept_fd > -1) { + int keepalive = TRUE; + + if (setsockopt(sip_tcp_session.accept_fd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive))) { + ast_log(LOG_ERROR, "Error enabling TCP keep-alive on socket: %s\n", strerror(errno)); + } + + ast_set_qos(sip_tcp_session.accept_fd, sip_config.tos_sip, sip_config.cos_sip, "SIP"); + ast_verb(5, "SIP listening on 'TCP:%s'\n", + ast_sockaddr_stringify(&sip_tcp_session.local_address)); + } + } + + /* Start TLS server if needed */ + sip_tls_session.tls_cfg = &sip_tls_config; + + if (ast_ssl_setup(sip_tls_session.tls_cfg)) { + /* Allow clients with any valid certificate */ + ast_set_flag(&sip_tls_session.tls_cfg->flags, AST_SSL_IGNORE_COMMON_NAME); + + if (ast_sockaddr_isnull(&sip_tls_session.local_address)) { + ast_sockaddr_copy(&sip_tls_session.local_address, &sip_config.udp_bind_address); + ast_sockaddr_set_port(&sip_tls_session.local_address, SIP_STANDARD_TLS_PORT); + } + + if (!ast_sockaddr_port(&sip_tls_session.local_address)) { + ast_sockaddr_set_port(&sip_tls_session.local_address, SIP_STANDARD_TLS_PORT); + } + + ast_tcptls_server_start(&sip_tls_session); + + if (sip_tls_config.enabled && sip_tls_session.accept_fd == -1) { + ast_log(LOG_ERROR, "SIP TLS server start failed. Not listening on TLS socket\n"); + } else { + ast_debug(2, "SIP TLS server started\n"); + + if (sip_tls_session.accept_fd > -1) { + int keepalive = TRUE; + + if (setsockopt(sip_tls_session.accept_fd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive))) { + ast_log(LOG_ERROR, "Error enabling TCP keep-alive on socket: %s\n", strerror(errno)); + } + + ast_set_qos(sip_tls_session.accept_fd, sip_config.tos_sip, sip_config.cos_sip, "SIP"); + ast_verb(5, "SIP listening on 'TLS:%s'\n", + ast_sockaddr_stringify(&sip_tcp_session.local_address)); + } + } + } else if (sip_tls_session.tls_cfg->enabled) { + sip_tls_session.tls_cfg = NULL; + ast_log(LOG_WARNING, "SIP TLS server did not load because of errors\n"); + } + + /* Load peers, users and friends */ + category = NULL; + + while ((category = ast_category_browse(config, category))) { + const char *type; + + if (!strcasecmp(category, "general")) { + continue; + } + + if (!strcasecmp(category, "authentication")) { + ast_log(LOG_WARNING, + "Ignoring section [authentication] as authentication realms are now set in [general]\n"); + continue; + } + + if (!(type = ast_variable_retrieve(config, category, "type"))) { + ast_log(LOG_WARNING, "Section '%s' lacks type\n", category); + continue; + } + + if (!strcasecmp(type, "peer") || !strcasecmp(type, "friend")) { + if ((peer = sip_peer_build(category, ast_variable_browse(config, category), FALSE, FALSE))) { + if (sip_reload_reason == CHANNEL_MODULE_LOAD && + peer->nat_force_rport != sip_config.nat_force_rport) { + ast_log(LOG_WARNING, + "Setting 'nat' for a peer that differs from the global setting can make the name\n"); + ast_log(LOG_WARNING, + "of that peer discoverable by an attacker. Replies for non-existent peers will be sent\n"); + ast_log(LOG_WARNING, + "to a different port than replies for an existing peer. If at all possible, use the\n"); + ast_log(LOG_WARNING, + "global 'nat' setting and do not set 'nat' for peer '%s'\n", category); + } + + ao2_link(sip_peers, peer); + + if (!ast_sockaddr_isnull(&peer->address)) { + ao2_link(sip_peer_addresses, peer); + } + + ao2_ref(peer, -1); + } + } else if (!strcasecmp(type, "notify")) { + sip_notification_build(category, ast_variable_browse(config, category), FALSE); + } else { + ast_log(LOG_WARNING, "Unknown section type '%s' for '%s'\n", type, category); + } + } + + /* Release configuration from memory */ + ast_config_destroy(config); + memset(&flags, 0, sizeof(flags)); + + config = ast_config_load("sip_notify.conf", flags); + + if (config == CONFIG_STATUS_FILEINVALID) { + ast_log(LOG_ERROR, "Contents of sip_notify.conf are invalid and cannot be parsed\n"); + } else if (config != CONFIG_STATUS_FILEMISSING && config != CONFIG_STATUS_FILEUNCHANGED) { + ast_log(LOG_WARNING, "sip_notify.conf is deprecated, use a type=notify section in sip.conf instead\n"); + + while ((category = ast_category_browse(config, category))) { + sip_notification_build(category, ast_variable_browse(config, category), TRUE); + } + + ast_config_destroy(config); + } + + reload_end = ast_tvnow(); + ast_debug(3, "SIP config reload done in %ldms\n", ast_tvdiff_ms(reload_end, reload_start)); + + /* Register aliases now that all peers have been added */ + iter = ao2_iterator_init(sip_peers, 0); + + while ((peer = ao2_iterator_next(&iter))) { + ao2_lock(peer); + sip_peer_update_aliases(peer); + ao2_unlock(peer); + + ao2_ref(peer, -1); + } + + ao2_iterator_destroy(&iter); + + if (subscribe_network_change) { + sip_network_change_subscribe(); + } else { + sip_network_change_unsubscribe(); + } + + /* If an ACL change subscription is needed and doesn't exist, we need one */ + if (subscribe_acl_change) { + sip_acl_change_subscribe(); + } else { + sip_acl_change_unsubscribe(); + } + + return 0; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/dialog.c asterisk-22.6.0/channels/sip/dialog.c --- asterisk-22.6.0.orig/channels/sip/dialog.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/dialog.c 2025-10-21 18:38:17.441218386 +1300 @@ -0,0 +1,3416 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/utils.h" +#include "asterisk/iostream.h" +#include "asterisk/tcptls.h" +#include "asterisk/channel.h" +#include "asterisk/bridge.h" +#include "asterisk/callerid.h" +#include "asterisk/causes.h" +#include "asterisk/indications.h" +#include "asterisk/format.h" +#include "asterisk/format_cache.h" +#include "asterisk/rtp_engine.h" +#include "asterisk/sdp_srtp.h" +#include "asterisk/dsp.h" +#include "asterisk/pbx.h" +#include "asterisk/devicestate.h" +#include "asterisk/astdb.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_channels.h" +#include "asterisk/stasis_endpoints.h" +#include "asterisk/features_config.h" +#include "asterisk/acl.h" +#include "asterisk/app.h" +#include "asterisk/message.h" +#include "asterisk/srv.h" +#include "asterisk/udptl.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/response.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/authentication_realms.h" +#include "include/domains.h" +#include "include/peers.h" +#include "include/realtime.h" +#include "include/registrations.h" +#include "include/mwi_subscriptions.h" +#include "include/dialog.h" +#include "include/sdp.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/channel_tech.h" +#include "include/manager.h" +#include "include/fax.h" + +static void sip_dialog_destroy(void *data); +static int __sip_dialog_unlink(const void *data); + +static int __sip_dialog_cancel_destroy(const void *data); +static int __sip_dialog_sched_destroy(const void *data); + +static int __sip_dialog_sched_check_pending(const void *data); + +static int sip_dialog_alloc_rtp(struct sip_dialog *dialog); +static void sip_dialog_destroy_rtp(struct sip_dialog *dialog); +static void sip_dialog_build_call_id(struct sip_dialog *dialog); + +static void sip_dialog_get_peer_config(struct sip_dialog *dialog, struct sip_peer *peer); +static int sip_dialog_check_authorization(struct sip_dialog *dialog, struct sip_peer *peer, struct sip_message *request, + int reliable); + +static int sip_dialog_check_transport(struct sip_dialog *dialog, struct sip_peer *peer); + +static int __sip_dialog_stop_need_reinvite(const void *data); +static int __sip_dialog_stop_reinvite(const void *data); + +static int sip_dialog_provisional_keepalive(const void *data); +static int __sip_dialog_sched_provisional_keepalive(const void *data); +static int __sip_dialog_cancel_provisional_keepalive(const void *data); + +/* Here we implement the container for all dialogs */ +struct ao2_container *sip_dialogs = NULL; + +/* Here we implement the container for dialogs which are in the dialog->need_destroy state to iterate only through + * the dialogs and unlink them instead of iterate through all dialogs */ +struct ao2_container *sip_dialogs_need_destroy = NULL; + +/* Here we implement the container for dialogs which have rtp traffic and rtptimeout, rtpholdtimeout or rtpkeepalive + * set. We use this container instead the whole dialog list */ +struct ao2_container *sip_dialogs_with_rtp = NULL; + +int sip_dialog_hash(const void *data, int flags) +{ + const char *call_id; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + const struct sip_dialog *dialog = (struct sip_dialog *) data; + + call_id = dialog->call_id; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + call_id = (const char *) data; + } else { + return 0; + } + + return ast_str_hash(call_id); +} + +int sip_dialog_cmp(void *data, void *arg, int flags) +{ + struct sip_dialog *dialog; + const char *call_id; + + dialog = (struct sip_dialog *) data; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + struct sip_dialog *dialog = (struct sip_dialog *) arg; + + call_id = dialog->call_id; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + call_id = (const char *) arg; + } else { + return 0; + } + + if (!strcmp(dialog->call_id, call_id)) { + return CMP_MATCH | ((flags & OBJ_MULTIPLE) ? 0 : CMP_STOP); + } + + return 0; +} + +/* Allocate sip_dialog structure, set defaults and link in the container */ +struct sip_dialog *sip_dialog_alloc(const char *call_id, struct sip_socket *socket, int method, + struct sip_message *message, ast_callid logger_callid) +{ + struct sip_dialog *dialog; + + if (!(dialog = ao2_alloc_options(sizeof(*dialog), sip_dialog_destroy, AO2_ALLOC_OPT_LOCK_MUTEX))) { + return NULL; + } + + if (ast_string_field_init(dialog, 512)) { + ao2_ref(dialog, -1); + return NULL; + } + + if (socket && !ast_sockaddr_isnull(&socket->address)) { + ast_sockaddr_copy(&dialog->address, &socket->address); + sip_socket_copy(&dialog->socket, socket); + + sip_dialog_set_our_address(dialog); + } else { + ast_sockaddr_copy(&dialog->our_address, &sip_our_address); + + dialog->socket.transport = 0; + dialog->socket.fd = -1; + } + + dialog->format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + dialog->joint_format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + dialog->remote_format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + dialog->redirect_format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + dialog->outgoing_format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + + if (!dialog->format_cap || !dialog->joint_format_cap || !dialog->remote_format_cap || + !dialog->redirect_format_cap || !dialog->outgoing_format_cap) { + ao2_cleanup(dialog->format_cap); + ao2_cleanup(dialog->joint_format_cap); + ao2_cleanup(dialog->remote_format_cap); + ao2_cleanup(dialog->redirect_format_cap); + ao2_cleanup(dialog->outgoing_format_cap); + + ao2_ref(dialog, -1); + return NULL; + } + + /* If this dialog is created as a result of a request or response, lets store some information about it + * in the dialog */ + if (message) { + /* Get branch parameter from initial Request that started this dialog, only store the branch if it + * begins with the magic prefix "z9hG4bK", otherwise it is not useful to us to have it */ + if (!ast_strlen_zero(message->via_branch) && !strncmp(message->via_branch, SIP_MAGIC_COOKIE, 7)) { + ast_string_field_set(dialog, via_sent_by, message->via_sent_by); + ast_string_field_set(dialog, via_branch, message->via_branch); + } + + /* Store initial incoming CSeq, validation must take place before dialog creation in + * sip_message_find_dialog */ + dialog->initial_incoming_cseq = message->cseq; + } + + dialog->logger_callid = logger_callid; + dialog->method = method; + + dialog->invite_sched_id = -1; + dialog->need_reinvite_sched_id = -1; + dialog->reinvite_sched_id = -1; + + dialog->auto_destroy_sched_id = -1; + dialog->provisional_keepalive_sched_id = -1; + + dialog->session_timer_sched_id = -1; + dialog->fax_abort_sched_id = -1; + + dialog->subscribe_event = SIP_SUBSCRIBE_NONE; + dialog->extension_state_id = -1; + + dialog->sdp_changed = TRUE; + dialog->max_forwards = 70; + + dialog->timer_t1 = sip_config.timer_t1; /* Default SIP retransmission timer T1 (RFC 3261) */ + dialog->timer_b = sip_config.timer_b; /* Default SIP transaction timer B (RFC 3261) */ + + dialog->allow_methods = SIP_METHOD_OPTIONS | SIP_METHOD_INVITE | SIP_METHOD_ACK | SIP_METHOD_CANCEL | + SIP_METHOD_REGISTER | SIP_METHOD_NOTIFY | SIP_METHOD_REFER | SIP_METHOD_SUBSCRIBE | SIP_METHOD_PUBLISH | + SIP_METHOD_UPDATE | SIP_METHOD_INFO; + dialog->outgoing_cseq = 0; + + ast_string_field_build(dialog, branch, "%s%08x", SIP_MAGIC_COOKIE, (unsigned int) ast_random()); + sip_dialog_build_local_tag(dialog); + + if (dialog->method != SIP_METHOD_REGISTER) { + ast_string_field_set(dialog, from_domain, sip_config.from_domain); + dialog->from_domain_port = sip_config.from_domain_port; + } + + if (!call_id) { + sip_dialog_build_call_id(dialog); + } else { + ast_string_field_set(dialog, call_id, call_id); + } + + /* Assign default music on hold class */ + ast_format_cap_append_from_cap(dialog->format_cap, sip_config.format_cap, AST_MEDIA_TYPE_UNKNOWN); + + AST_LIST_HEAD_INIT_NOLOCK(&dialog->route.hops); + AST_LIST_HEAD_INIT_NOLOCK(&dialog->sdp_media); + + /* Add to active dialog list */ + ao2_link(sip_dialogs, dialog); + + ast_debug(1, "Allocating new %s for '%s' %s\n", + sip_method2str(method), dialog->call_id, dialog->audio_rtp ? "with RTP" : "no RTP"); + return dialog; +} + +/* Destructor for SIP dialog structure */ +static void sip_dialog_destroy(void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + ast_debug(1, "Destroying %s '%s'\n", sip_method2str(dialog->method), dialog->call_id); + + if (dialog->inuse || dialog->onhold) { + sip_dialog_change_inuse(dialog, SIP_INUSE_REMOVE); + ast_debug(2, "Call '%s' did not properly clean up limits\n", dialog->call_id); + } + + /* Unlink us from the owner if we have one */ + if (dialog->channel) { + ast_channel_lock(dialog->channel); + + ast_debug(1, "Removing channel '%s' from dialog\n", ast_channel_name(dialog->channel)); + ast_channel_tech_pvt_set(dialog->channel, NULL); + + /* Make sure that the channel knows its backend is going away */ + ast_channel_softhangup_internal_flag_add(dialog->channel, AST_SOFTHANGUP_DEV); + ast_channel_unlock(dialog->channel); + + /* Give the channel a chance to react before deallocation */ + usleep(1); + } + + if (dialog->peer) { + /* Remove link from peer to qualify */ + if (dialog->peer->qualify_dialog == dialog) { + ao2_ref(dialog->peer->qualify_dialog, -1); + } + + /* Remove link from peer to mwi subscription */ + if (dialog->peer->mwi_dialog == dialog) { + ao2_ref(dialog->peer->mwi_dialog, -1); + } + + /* Remove link from peer to subscription for feature events */ + if (dialog->peer->feature_events_dialog == dialog) { + ao2_ref(dialog->peer->feature_events_dialog, -1); + } + + ao2_ref(dialog->peer, -1); + } + + if (dialog->extension_state_id != -1) { + ast_extension_state_del(dialog->extension_state_id, sip_extension_state_event); + } + + if (dialog->registration) { + ao2_cleanup(dialog->registration->dialog); + ao2_ref(dialog->registration, -1); + } + + if (dialog->mwi_subscription) { + ao2_cleanup(dialog->mwi_subscription->dialog); + ao2_ref(dialog->mwi_subscription, -1); + } + + ao2_cleanup(dialog->conference); + ao2_cleanup(dialog->record_outgoing_dialog); + ao2_cleanup(dialog->record_incoming_dialog); + + sip_proxy_set(dialog, NULL); + sip_dialog_destroy_rtp(dialog); + + if (dialog->udptl) { + ast_udptl_destroy(dialog->udptl); + } + + sip_route_destroy(&dialog->route); + sip_message_destroy(&dialog->initial_request); + sip_sdp_media_destroy(dialog); + + ast_variables_destroy(dialog->channel_variables); + ast_variables_destroy(dialog->notify_headers); + ast_variables_destroy(dialog->message_headers); + + ao2_cleanup(dialog->socket.tcptls_session); + ao2_cleanup(dialog->authentication_realms); + ao2_cleanup(dialog->last_device_state_info); + + ao2_cleanup(dialog->format_cap); + ao2_cleanup(dialog->joint_format_cap); + ao2_cleanup(dialog->remote_format_cap); + ao2_cleanup(dialog->redirect_format_cap); + ao2_cleanup(dialog->outgoing_format_cap); + + ast_string_field_free_memory(dialog); +} + +/* Initiate a call in the SIP channel */ +int sip_dialog_alloc_channel(struct sip_dialog *dialog, int state, const char *name, + const struct ast_assigned_ids *assigned_ids, const struct ast_channel *requestor_channel, ast_callid callid) +{ + static unsigned int next_channel = 0; /* Used in naming sip channel */ + struct ast_channel *channel; + struct ast_format_cap *format_cap, *outgoing_format_cap; /* Shallow copy, do not destroy */ + struct ast_format *format; + struct ast_str *format_names; + struct ast_variable *variable; + int need_video, need_text; + char *exten; + + if (!(format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + return -1; + } + + /* Don't hold a sip dialog lock while we allocate a channel */ + ao2_unlock(dialog); + + if (dialog->peer && dialog->peer->endpoint) { + channel = ast_channel_alloc_with_endpoint(TRUE, state, dialog->caller_number, dialog->caller_name, + dialog->peer->accountcode, dialog->to_user, dialog->peer->context, assigned_ids, + requestor_channel, dialog->peer->amaflags, dialog->peer->endpoint, + "SIP/%s-%08x", S_OR(name, dialog->from_domain), + (unsigned int) ast_atomic_fetchadd_int((int *) &next_channel, +1)); + } else { + channel = ast_channel_alloc(TRUE, state, dialog->caller_number, dialog->caller_name, + dialog->peer->accountcode, dialog->to_user, dialog->peer->context, assigned_ids, + requestor_channel, dialog->peer->amaflags, + "SIP/%s-%08x", S_OR(name, dialog->from_domain), + (unsigned int) ast_atomic_fetchadd_int((int *) &next_channel, +1)); + } + + if (!channel) { + ast_log(LOG_WARNING, "Unable to allocate channel for '%s'\n", dialog->call_id); + + ao2_ref(format_cap, -1); + ao2_lock(dialog); + return -1; + } + + ast_channel_stage_snapshot(channel); + + /* If we sent in a callid, bind it to the channel */ + if (callid) { + ast_channel_callid_set(channel, callid); + } + + ao2_lock(dialog); + ast_channel_tech_set(channel, &sip_channel_tech); + + /* Select our native format based on codec preference until we receive something from another device to the + * contrary */ + if (ast_format_cap_count(dialog->joint_format_cap)) { /* The joint capabilities of us and peer */ + outgoing_format_cap = dialog->joint_format_cap; + } else if (ast_format_cap_count(dialog->format_cap)) { /* Our configured capability for this peer */ + outgoing_format_cap = dialog->format_cap; + } else { + outgoing_format_cap = sip_config.format_cap; + } + + /* Set the native formats */ + ast_format_cap_append_from_cap(format_cap, outgoing_format_cap, AST_MEDIA_TYPE_UNKNOWN); + + /* Use only the preferred audio format, which is stored at the '0' index */ + if ((format = ast_format_cap_get_best_by_type(outgoing_format_cap, AST_MEDIA_TYPE_AUDIO))) { + int framing = ast_format_cap_get_format_framing(outgoing_format_cap, format); + + /* Get the best audio format */ + ast_format_cap_remove_by_type(format_cap, AST_MEDIA_TYPE_AUDIO); /* Remove only the other audio formats */ + ast_format_cap_append(format_cap, format, framing); /* Add our best choice back */ + } else { + /* If we don't have an audio format, try to get something */ + if (!(format = ast_format_cap_get_format(format_cap, 0))) { + ast_log(LOG_WARNING, "No compatible formats could be found for %s\n", ast_channel_name(channel)); + ao2_ref(format_cap, -1); + + ast_channel_stage_snapshot_done(channel); + ast_channel_unlock(channel); + ast_hangup(channel); + return -1; + } + } + + ast_channel_nativeformats_set(channel, format_cap); + ao2_ref(format_cap, -1); + + format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + + ast_debug(3, "Our native formats are: %s\n", + ast_format_cap_get_names(ast_channel_nativeformats(channel), &format_names)); + ast_debug(3, "Joint format capabilities are: %s\n", + ast_format_cap_get_names(dialog->joint_format_cap, &format_names)); + ast_debug(3, "Our format capabilities are: %s\n", ast_format_cap_get_names(dialog->format_cap, &format_names)); + ast_debug(3, "Our preferred format is: %s\n", ast_format_get_name(format)); + + if (ast_format_cap_count(dialog->outgoing_format_cap)) { + ast_debug(3, "Our preferred formats from the incoming channel are: %s\n", + ast_format_cap_get_names(dialog->outgoing_format_cap, &format_names)); + } + + /* If we have a prefcodec setting, we have an inbound channel that set a preferred format for this call. + * Otherwise, we check the jointcapability. We also check for vrtp. If it's not there, we are not allowed do + * any video anyway */ + need_video = FALSE; + need_text = FALSE; + + if (dialog->video_rtp) { + if (ast_format_cap_count(dialog->outgoing_format_cap)) { + /* Outbound call */ + need_video = ast_format_cap_has_type(dialog->outgoing_format_cap, AST_MEDIA_TYPE_VIDEO); + } else { + /* Inbound call */ + need_video = ast_format_cap_has_type(dialog->joint_format_cap, AST_MEDIA_TYPE_VIDEO); + } + + if (!need_video) { + ast_rtp_instance_destroy(dialog->video_rtp); + dialog->video_rtp = NULL; + } + } + + if (dialog->text_rtp) { + if (ast_format_cap_count(dialog->outgoing_format_cap)) { + /* Outbound call */ + need_text = ast_format_cap_has_type(dialog->outgoing_format_cap, AST_MEDIA_TYPE_TEXT); + } else { + /* Inbound call */ + need_text = ast_format_cap_has_type(dialog->joint_format_cap, AST_MEDIA_TYPE_TEXT); + } + + if (!need_text) { + ast_rtp_instance_destroy(dialog->text_rtp); + dialog->text_rtp = NULL; + } + } + + if (need_video) { + ast_debug(3, "This channel can handle video\n"); + } else { + ast_debug(3, "This channel will not be able to handle video\n"); + } + + sip_dialog_set_dsp_detect(dialog, TRUE); + + /* Set file descriptors for audio, video, and realtime text. Since UDPTL is created as needed in the lifetime + * of a dialog, its file descriptor is set in sip_fax_alloc */ + if (dialog->audio_rtp) { + ast_channel_set_fd(channel, SIP_AUDIO_RTP_FD, ast_rtp_instance_fd(dialog->audio_rtp, FALSE)); + + if (dialog->peer->dtmf_mode == SIP_DTMF_MODE_INBAND) { + ast_rtp_instance_dtmf_mode_set(dialog->audio_rtp, AST_RTP_DTMF_MODE_INBAND); + } else if (dialog->peer->dtmf_mode == SIP_DTMF_MODE_RFC2833) { + ast_rtp_instance_dtmf_mode_set(dialog->audio_rtp, AST_RTP_DTMF_MODE_RFC2833); + } + + if (dialog->peer->rtcp_mux) { + ast_channel_set_fd(channel, SIP_AUDIO_RTCP_FD, -1); + } else { + ast_channel_set_fd(channel, SIP_AUDIO_RTCP_FD, ast_rtp_instance_fd(dialog->audio_rtp, TRUE)); + } + + ast_rtp_instance_set_write_format(dialog->audio_rtp, format); + ast_rtp_instance_set_read_format(dialog->audio_rtp, format); + } + + if (need_video && dialog->video_rtp) { + ast_channel_set_fd(channel, SIP_VIDEO_RTP_FD, ast_rtp_instance_fd(dialog->video_rtp, FALSE)); + + if (dialog->peer->rtcp_mux) { + ast_channel_set_fd(channel, SIP_VIDEO_RTCP_FD, -1); + } else { + ast_channel_set_fd(channel, SIP_VIDEO_RTCP_FD, ast_rtp_instance_fd(dialog->video_rtp, TRUE)); + } + } + + if (need_text && dialog->text_rtp) { + ast_channel_set_fd(channel, SIP_TEXT_RTP_FD, ast_rtp_instance_fd(dialog->text_rtp, FALSE)); + } + + if (dialog->udptl) { + ast_channel_set_fd(channel, SIP_UDPTL_FD, ast_udptl_fd(dialog->udptl)); + } + + if (state == AST_STATE_RING) { + ast_channel_rings_set(channel, 1); + } + + ast_channel_adsicpe_set(channel, AST_ADSI_UNAVAILABLE); + + ast_channel_set_writeformat(channel, format); + ast_channel_set_rawwriteformat(channel, format); + + ast_channel_set_readformat(channel, format); + ast_channel_set_rawreadformat(channel, format); + ao2_ref(format, -1); + + ast_channel_tech_pvt_set(channel, ao2_bump(dialog)); + + ast_channel_callgroup_set(channel, dialog->peer->callgroup); + ast_channel_pickupgroup_set(channel, dialog->peer->pickupgroup); + + ast_channel_named_callgroups_set(channel, dialog->peer->named_callgroups); + ast_channel_named_pickupgroups_set(channel, dialog->peer->named_pickupgroups); + + ast_channel_caller(channel)->id.name.presentation = dialog->caller_presentation; + ast_channel_caller(channel)->id.number.presentation = dialog->caller_presentation; + + if (!ast_strlen_zero(dialog->peer->parkinglot)) { + ast_channel_parkinglot_set(channel, dialog->peer->parkinglot); + } + + if (!ast_strlen_zero(dialog->peer->accountcode)) { + ast_channel_accountcode_set(channel, dialog->peer->accountcode); + } + + if (dialog->peer->amaflags) { + ast_channel_amaflags_set(channel, dialog->peer->amaflags); + } + + if (!ast_strlen_zero(dialog->peer->language)) { + ast_channel_language_set(channel, dialog->peer->language); + } + + if (!ast_strlen_zero(dialog->peer->tone_zone)) { + struct ast_tone_zone *tone_zone; + + if ((tone_zone = ast_get_indication_zone(dialog->peer->tone_zone))) { + ast_channel_zone_set(channel, tone_zone); + } else { + ast_log(LOG_ERROR, "Unknown country code '%s' for tonezone for '%s'\n", + dialog->peer->tone_zone, dialog->peer->name); + } + } + + sip_dialog_set_channel(dialog, channel); + sip_module_ref(); + + /* Since it is valid to have extensions in the dialplan that have unescaped characters in them we should decode + * the uri before storing it in the channel, but leave it encoded in the sip_dialog structure so that there + * aren't issues when forming URI's */ + exten = ast_strdupa(dialog->to_user); + + ao2_unlock(dialog); + ast_channel_unlock(channel); + + if (!ast_exists_extension(NULL, dialog->peer->context, dialog->to_user, 1, dialog->caller_number)) { + ast_uri_decode(exten, ast_uri_sip_user); + } + + ast_channel_lock(channel); + ao2_lock(dialog); + + ast_channel_context_set(channel, dialog->peer->context); + ast_channel_exten_set(channel, exten); + ast_channel_priority_set(channel, 1); + + /* Don't use ast_set_callerid() here because it will generate an unnecessary NewCallerID event */ + if (!ast_strlen_zero(dialog->caller_number)) { + ast_channel_caller(channel)->ani.number.valid = TRUE; + ast_channel_caller(channel)->ani.number.str = ast_strdup(dialog->caller_number); + } + + ast_channel_caller(channel)->id.tag = ast_strdup(dialog->caller_tag); + + if (!ast_strlen_zero(dialog->to_user) && strcmp(dialog->to_user, "s")) { + ast_channel_dialed(channel)->number.str = ast_strdup(dialog->to_user); + } + + if (dialog->audio_rtp) { + ast_jb_configure(channel, &sip_jb_config); + } + + if (!dialog->peer) { + ast_set_flag(ast_channel_flags(channel), AST_FLAG_DISABLE_DEVSTATE_CACHE); + } + + /* Set channel variables for this call from configuration */ + for (variable = dialog->channel_variables; variable; variable = variable->next) { + char value[2048]; + + pbx_builtin_setvar_helper(channel, variable->name, ast_get_encoded_str(variable->value, + value, sizeof(value))); + } + + ast_channel_stage_snapshot_done(channel); + return 0; +} + +/* Unlink a dialog from the dialogs container, as well as any other places that it may be currently stored. A reference + * to the dialog must be held before calling this function, and this function does not release that reference */ +void sip_dialog_unlink(struct sip_dialog *dialog) +{ + ao2_ref(dialog, +1); + + ao2_unlink(sip_dialogs, dialog); + ao2_unlink(sip_dialogs_need_destroy, dialog); + ao2_unlink(sip_dialogs_with_rtp, dialog); + + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_unlink, ao2_bump(dialog)) == -1) { + ao2_ref(dialog, -1); + } + + ao2_ref(dialog, -1); +} + +/* Run by the sched thread */ +static int __sip_dialog_unlink(const void *data) +{ + struct sip_dialog *dialog; + struct sip_packet *packet; + + dialog = (struct sip_dialog *) data; + + ao2_lock(dialog); + + /* Remove all current packets in this dialog */ + while ((packet = AST_LIST_REMOVE_HEAD(&dialog->packet_queue, next))) { + /* Unlink and destroy the packet object */ + AST_SCHED_DEL_UNREF(sip_sched_context, packet->resend_sched_id, ao2_cleanup(packet)); + ao2_ref(packet, -1); + } + + ao2_unlock(dialog); + + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->need_reinvite_sched_id, ao2_cleanup(dialog)); + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->invite_sched_id, ao2_cleanup(dialog)); + + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->reinvite_sched_id, ao2_cleanup(dialog)); + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->auto_destroy_sched_id, ao2_cleanup(dialog)); + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->provisional_keepalive_sched_id, ao2_cleanup(dialog)); + + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->session_timer_sched_id, ao2_cleanup(dialog)); + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->fax_abort_sched_id, ao2_cleanup(dialog)); + + ao2_ref(dialog, -1); + return 0; +} + +/* Match dialogs that need to be destroyed. Re-work this to improve efficiency. Currently, this function is called on + * _every_ dialog after processing _every_ incoming SIP/UDP packet, or potentially even more often when the scheduler + * has entries to run */ +int sip_dialog_check_need_destroy(void *data, void *arg, int flags) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + if (ao2_trylock(dialog)) { + /* Don't block the monitor thread. This function is called often enough that we can wait for + * the next time around */ + return 0; + } + + /* If we have sessions that needs to be destroyed, do it now check if we have outstanding requests not responded + * to or an active call if that's the case, wait with destruction */ + if (dialog->need_destroy && AST_LIST_EMPTY(&dialog->packet_queue) && !dialog->channel) { + /* We absolutely cannot destroy the rtp struct while a bridge is active or we WILL crash */ + if (dialog->audio_rtp && ast_rtp_instance_get_bridged(dialog->audio_rtp)) { + ast_debug(2, "Bridge still active. Delaying destruction of %s '%s'\n", + sip_method2str(dialog->method), dialog->call_id); + ao2_unlock(dialog); + return 0; + } + + if (dialog->video_rtp && ast_rtp_instance_get_bridged(dialog->video_rtp)) { + ast_debug(2, "Bridge still active. Delaying destroy of %s '%s'\n", + sip_method2str(dialog->method), dialog->call_id); + ao2_unlock(dialog); + return 0; + } + + ao2_unlock(dialog); + sip_dialog_unlink(dialog); + return 0; /* Dialog has been unlinked no need to return a match */ + } + + ao2_unlock(dialog); + return 0; +} + +/* Run by the sched thread */ +static int __sip_dialog_cancel_destroy(const void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + ao2_lock(dialog); + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->auto_destroy_sched_id, ao2_cleanup(dialog)); + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + return 0; +} + +/* Cancel destruction of a dialog */ +void sip_dialog_cancel_destroy(struct sip_dialog *dialog) +{ + if (dialog->destroy_scheduled) { + return; + } + + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_cancel_destroy, ao2_bump(dialog)) == -1) { + ao2_ref(dialog, -1); + } +} + +/* Run by the sched thread */ +static int __sip_dialog_sched_destroy(const void *data) +{ + struct sip_sched_data *sched_data; + struct sip_dialog *dialog; + int when; + + sched_data = (struct sip_sched_data *) data; + + dialog = sched_data->dialog; + when = sched_data->when; + ast_free(sched_data); + + ao2_lock(dialog); + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->auto_destroy_sched_id, ao2_cleanup(dialog)); + + if ((dialog->auto_destroy_sched_id = ast_sched_add(sip_sched_context, when, sip_dialog_auto_destroy, + ao2_bump(dialog))) == -1) { + ao2_ref(dialog, -1); + } + + sip_session_timer_stop(dialog); + ao2_unlock(dialog); + ao2_ref(dialog, -1); + return 0; +} + +int sip_dialog_sched_destroy(struct sip_dialog *dialog, int when) +{ + struct sip_sched_data *sched_data; + + if (dialog->destroy_scheduled) { + return 0; /* Already scheduled destruction */ + } + + if (sip_dialog_debug(dialog)) { + ast_verb(3, "Scheduling destruction of %s '%s' in %dms\n", + sip_method2str(dialog->method), dialog->call_id, when); + } + + if (!(sched_data = ast_malloc(sizeof(*sched_data)))) { + return -1; + } + + sched_data->dialog = ao2_bump(dialog); + sched_data->when = when; + + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_sched_destroy, sched_data) == -1) { + ao2_ref(sched_data->dialog, -1); + ast_free(sched_data); + return -1; + } + + return 0; +} + +/* Automatically destroy a SIP dialog (called only by the scheduler), The scheduler has a reference to this + * dialog->auto_destroy_sched_id != -1, and we are called using that reference. So if the event is not rescheduled, we + * need to drop that reference */ +int sip_dialog_auto_destroy(const void *data) +{ + struct sip_dialog *dialog; + struct ast_channel *channel; + + dialog = (struct sip_dialog *) data; + + /* If this is a subscription, tell the phone that we got a timeout */ + if (dialog->subscribe_event && + (dialog->subscribe_event == SIP_SUBSCRIBE_DIALOG || dialog->subscribe_event == SIP_SUBSCRIBE_PRESENCE)) { + struct ast_state_cb_info state_info = {.exten_state = AST_EXTENSION_DEACTIVATED}; + + sip_request_send_notify_with_extension_state(dialog, &state_info, TRUE); /* Send last notification */ + dialog->subscribe_event = SIP_SUBSCRIBE_NONE; + + ast_debug(3, "Re-scheduled destruction of SUBSCRIBE '%s'\n", dialog->call_id); + return 10000; /* Reschedule this destruction so that we know that it's gone */ + } + + /* If there are packets still waiting for delivery, delay the destruction */ + if (!AST_LIST_EMPTY(&dialog->packet_queue)) { + if (!dialog->need_destroy) { + ast_debug(3, "Re-scheduled destruction of '%s'\n", dialog->call_id); + + if (dialog->ongoing_reinvite || dialog->method & (SIP_METHOD_CANCEL | SIP_METHOD_BYE)) { + sip_dialog_set_need_destroy(dialog, "autodestruct"); + } + + return 10000; + } else { + /* They've had their chance to respond. Time to bail */ + sip_packet_pretend_ack(dialog); + } + } + + /* Lock both the dialog and the channel safely so that we can queue up a frame */ + if ((channel = sip_dialog_lock_with_channel(dialog))) { + ast_log(LOG_WARNING, "Auto-destruct on %s '%s' with channel '%s'. Rescheduling destruction for 10000ms\n", + sip_method2str(dialog->method), dialog->call_id, ast_channel_name(channel)); + + ast_queue_hangup_with_cause(channel, AST_CAUSE_PROTOCOL_ERROR); + + ast_channel_unlock(channel); + ast_channel_unref(channel); + + ao2_unlock(dialog); + return 10000; + } + + /* Reset schedule ID */ + dialog->auto_destroy_sched_id = -1; + + if (dialog->refer_state && !dialog->already_gone) { + ast_debug(3, "Finally hanging up channel after transfer of '%s'\n", dialog->call_id); + + sip_dialog_stop_rtp(dialog); + sip_request_send_bye(dialog); + + sip_dialog_sched_destroy(dialog, dialog->timer_b); + ao2_unlock(dialog); + } else { + ast_debug(3, "Auto-destroying '%s'\n", dialog->call_id); + + ao2_unlock(dialog); + sip_dialog_unlink(dialog); /* Once it's unlinked and unrefd everywhere, it'll be freed automagically */ + } + + ao2_ref(dialog, -1); + return 0; +} + +/* Scheduled congestion on a call. Only called by the scheduler, must return the reference when done */ +int sip_dialog_auto_congest(const void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + ao2_lock(dialog); + dialog->invite_sched_id = -1; /* Event gone, will not be rescheduled */ + + if (dialog->channel) { + /* FIXME: fails on possible deadlock */ + if (!ast_channel_trylock(dialog->channel)) { + ast_queue_control(dialog->channel, AST_CONTROL_CONGESTION); + ast_channel_unlock(dialog->channel); + } + + /* Give the channel a chance to act before we proceed with destruction */ + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + return 0; +} + +/* Check RTP Timeout on dialogs */ +int sip_dialog_check_rtp_timeout(void *data, void *arg, int flags) +{ + struct sip_dialog *dialog; + int res; + + dialog = (struct sip_dialog *) data; + + if (ao2_trylock(dialog)) { + return 0; + } + + if (!dialog->audio_rtp) { + /* We have no RTP. Since we don't do much with video RTP for now, stop checking this dialog */ + res = CMP_MATCH; + } else if (!dialog->rtp_timeout && !dialog->rtp_hold_timeout && !dialog->rtp_keepalive) { + /* If we have no timers set there is nothing to check */ + res = CMP_MATCH; + } else if (!dialog->channel) { + /* If we have no active owner, no need to check timers */ + res = CMP_MATCH; + } else if (ast_channel_state(dialog->channel) != AST_STATE_UP) { + /* If the call is not in UP state return for later check */ + res = 0; + } else if (dialog->fax_state == SIP_FAX_ENABLED) { + /* If the call is involved in a T38 fax session do not check RTP timeout */ + res = CMP_MATCH; + } else { + struct timeval now = ast_tvnow(); + + /* Check Audio RTP keepalives */ + if (!ast_tvzero(dialog->last_rtp_sent) && dialog->rtp_keepalive && + ast_tvcmp(now, ast_tvadd(dialog->last_rtp_sent, ast_tv(dialog->rtp_keepalive, 0))) > 0) { + /* Need to send an empty RTP packet */ + dialog->last_rtp_sent = now; + + ast_rtp_instance_sendcng(dialog->audio_rtp, 0); + } + + /* Has the last time we received RTP exceeded either the timeout or hold timeout */ + if (!ast_tvzero(dialog->last_rtp_received) && + ((dialog->rtp_timeout && + ast_tvcmp(now, ast_tvadd(dialog->last_rtp_received, ast_tv(dialog->rtp_timeout, 0))) > 0) && + (!dialog->onhold || + (dialog->rtp_hold_timeout && + ast_tvcmp(now, ast_tvadd(dialog->last_rtp_received, ast_tv(dialog->rtp_hold_timeout, 0))) > 0)))) { + if (ast_channel_trylock(dialog->channel)) { + /* Don't block, just try again later. If there was no owner, the call is dead already */ + res = 0; + } else { + ast_verb(3, "Disconnecting '%s' due to lack of RTP activity in %lds\n", + ast_channel_name(dialog->channel), + ast_tvdiff_sec(now, dialog->last_rtp_received)); + + sip_publish_session_timeout(dialog->channel, "RTPTimeout"); + + /* Issue a softhangup cause 44 (as used by Cisco for RTP timeouts) */ + ast_channel_hangupcause_set(dialog->channel, AST_CAUSE_REQUESTED_CHAN_UNAVAIL); + ast_softhangup_nolock(dialog->channel, AST_SOFTHANGUP_DEV); + ast_channel_unlock(dialog->channel); + + /* Forget the timeouts for this call, since a hangup has already been requested and we + * don't want to repeatedly request hangups */ + ast_rtp_instance_set_timeout(dialog->audio_rtp, 0); + ast_rtp_instance_set_hold_timeout(dialog->audio_rtp, 0); + + if (dialog->video_rtp) { + ast_rtp_instance_set_timeout(dialog->video_rtp, 0); + ast_rtp_instance_set_hold_timeout(dialog->video_rtp, 0); + } + + res = CMP_MATCH; + } + } else { + /* We have received RTP within the timeout */ + res = 0; + } + } + + ao2_unlock(dialog); + return res; +} + +/* Check pending actions on SIP call both sip_dialog and sip_dialog's owner channel (if present) must be locked for this + * function */ +void sip_dialog_check_pending(struct sip_dialog *dialog) +{ + if (dialog->pending_bye) { + if (dialog->reinvite_sched_id != -1) { + /* Outstanding dialog->reinvite_sched_id timeout, so wait.. */ + return; + } + + if (dialog->invite_state == SIP_INVITE_PROCEEDING || dialog->invite_state == SIP_INVITE_EARLY_MEDIA) { + /* If we can't BYE, then this is really a pending CANCEL */ + sip_request_send_cancel(dialog); + + /* If the cancel occurred on an initial invite, cancel the pending BYE */ + if (!dialog->established) { + dialog->pending_bye = FALSE; + dialog->need_reinvite = FALSE; + } + + /* Actually don't destroy us yet, wait for the 487 on our original INVITE, but do set an + * auto-destruct below just in case we never get it */ + } else { + /* We have a pending outbound invite, don't send something new in-transaction, unless it is a + * pending reinvite, then by the time we are called here, we should probably just hang up */ + if (dialog->pending_invite_cseq && !dialog->ongoing_reinvite) { + return; + } + + if (dialog->channel) { + ast_softhangup_nolock(dialog->channel, AST_SOFTHANGUP_DEV); + } + + sip_request_send_bye(dialog); + + dialog->pending_bye = FALSE; + dialog->need_reinvite = FALSE; + } + + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } else if (dialog->need_reinvite) { + /* If we can't REINVITE, hold it for later */ + if (dialog->pending_invite_cseq || + dialog->invite_state == SIP_INVITE_CALLING || dialog->invite_state == SIP_INVITE_PROCEEDING || + dialog->invite_state == SIP_INVITE_EARLY_MEDIA || dialog->need_reinvite_sched_id != -1) { + ast_debug(2, "Not sending pending reinvite yet on '%s'\n", dialog->call_id); + } else { + ast_debug(2, "Sending pending reinvite on '%s'\n", dialog->call_id); + + /* Didn't get to reinvite yet, so do it now */ + sip_request_send_reinvite_with_sdp(dialog, FALSE, dialog->fax_state == SIP_FAX_LOCAL_REINVITE); + dialog->need_reinvite = FALSE; + } + } +} + +/* Run by the sched thread */ +static int __sip_dialog_sched_check_pending(const void *data) +{ + struct sip_dialog *dialog; + struct ast_channel *channel; + + dialog = (struct sip_dialog *) data; + channel = sip_dialog_lock_with_channel(dialog); + + sip_dialog_check_pending(dialog); + + if (channel) { + ast_channel_unlock(channel); + ast_channel_unref(channel); + } + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + return 0; +} + +void sip_dialog_sched_check_pending(struct sip_dialog *dialog) +{ + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_sched_check_pending, ao2_bump(dialog)) == -1) { + ao2_ref(dialog, -1); + } +} + +int sip_dialog_debug(struct sip_dialog *dialog) +{ + const struct ast_sockaddr *address; + + if (dialog->socket.transport == AST_TRANSPORT_UDP && (dialog->nat_force_rport || dialog->rport_present)) { + address = &dialog->socket.address; + } else { + address = &dialog->address; + } + + if (sip_debug && + (ast_sockaddr_isnull(&sip_debug_address) || !ast_sockaddr_cmp_addr(&sip_debug_address, address))) { + return TRUE; + } + + return FALSE; +} + +/* Make a minimal copy of a dialog, we don't copy the initial request because we have a new Call-ID */ +void sip_dialog_copy(struct sip_dialog *to_dialog, struct sip_dialog *from_dialog) +{ + ao2_lock(from_dialog); + + ast_sockaddr_copy(&to_dialog->address, &from_dialog->address); + ast_sockaddr_copy(&to_dialog->our_address, &from_dialog->our_address); + ast_sockaddr_copy(&to_dialog->socket.address, &from_dialog->socket.address); + + to_dialog->timer_t1 = from_dialog->timer_t1; + to_dialog->timer_b = from_dialog->timer_b; + + ast_string_field_set(to_dialog, from_user, from_dialog->from_user); + ast_string_field_set(to_dialog, from_name, from_dialog->from_name); + ast_string_field_set(to_dialog, from_domain, from_dialog->from_domain); + + ast_string_field_set(to_dialog, to_user, from_dialog->to_user); + ast_string_field_set(to_dialog, to_host, from_dialog->to_host); + ast_string_field_set(to_dialog, contact, from_dialog->contact); + + to_dialog->from_domain_port = from_dialog->from_domain_port; + to_dialog->port_in_uri = from_dialog->port_in_uri; + to_dialog->rport_present = from_dialog->rport_present; + to_dialog->nat_force_rport = from_dialog->nat_force_rport; + + ao2_cleanup(to_dialog->peer); + to_dialog->peer = ao2_bump(from_dialog->peer); + + ao2_unlock(from_dialog); +} + +/* Locks both dialog and dialog owner if owner is present. This function gives a ref to dialog->channel if it is present + * and locked */ +struct ast_channel *sip_dialog_lock_with_channel(struct sip_dialog *dialog) +{ + struct ast_channel *channel; + + /* Locking is simple when it is done right. If you see a deadlock resulting in this function, it is not + * this function's fault, Your problem exists elsewhere. This function is perfect... seriously */ + for (;;) { + /* First, get the channel and grab a reference to it */ + ao2_lock(dialog); + + if (!(channel = dialog->channel)) { + /* No channel, return dialog locked */ + return NULL; + } + + /* The channel can not go away while we hold the dialog lock. Give the channel a ref so it will + * not go away after we let the dialog lock go */ + ast_channel_ref(channel); + /* We had to hold the dialog lock while getting a ref to the owner channel but now we have to let this + * lock go in order to preserve proper locking order when grabbing the channel lock */ + ao2_unlock(dialog); + + /* Look, no deadlock avoidance, hooray! */ + ast_channel_lock(channel); + ao2_lock(dialog); + + if (dialog->channel == channel) { + break; /* Done */ + } + + /* If the owner changed while everything was unlocked, no problem, just start over and everthing will + * work. This is rare, do not be confused by this loop and think this it is an expensive operation. The + * majority of the calls to this function will never involve multiple executions of this loop */ + ast_channel_unlock(channel); + ast_channel_unref(channel); + + ao2_unlock(dialog); + } + + /* If owner exists, it is locked and reffed */ + return dialog->channel; +} + +/* Find a companion dialog */ +struct sip_dialog *sip_dialog_find(const char *call_id, const char *local_tag, const char *remote_tag) +{ + struct sip_dialog *dialog; + + if ((dialog = ao2_find(sip_dialogs, call_id, OBJ_SEARCH_KEY))) { + ao2_lock(dialog); + + if (strcmp(dialog->local_tag, local_tag) || strcmp(dialog->remote_tag, remote_tag)) { + ao2_unlock(dialog); + ao2_ref(dialog, -1); + return NULL; + } + + ao2_unlock(dialog); + } + + return dialog; +} + +/* Find a companion dialog based on Replaces information, This information may come from a Refer-To header in a REFER + * or from a Replaces header in an INVITE. This function will find the appropriate sip_dialog and increment the refcount + * of both the sip_dialog and its owner channel. These two references are returned in the out parameters */ +struct sip_dialog *sip_dialog_find_with_channel(const char *call_id, const char *to_tag, const char *from_tag, + struct ast_channel **channel) +{ + struct sip_dialog *dialog; + int from_mismatch, to_mismatch; + + ast_debug(4, "Looking for Call-ID: '%s' From: tag='%s' To: tag='%s'\n", + call_id, S_OR(from_tag, ""), S_OR(to_tag, "")); + + /* Search dialogs and find the match */ + if (!(dialog = ao2_find(sip_dialogs, (char *) call_id, OBJ_SEARCH_KEY))) { + return NULL; + } + + ao2_lock(dialog); + + /* RFC 3891 states: User Agent Server Behavior: Receiving a Replaces Header The Replaces header + * contains information used to match an existing SIP dialog (call-id, to-tag, and from-tag). + * Upon receiving an INVITE with a Replaces header, the User Agent (UA) attempts to match this information + * with a confirmed or early dialog. The User Agent Server (UAS) matches the to-tag and from-tag parameters + * as if they were tags present in an incoming request. In other words, the to-tag parameter is compared + * to the local tag, and the from-tag parameter is compared to the remote tag. Thus, the to-tag is always + * compared to the local tag, regardless if this our call is an incoming or outgoing call */ + from_mismatch = ast_strlen_zero(from_tag) || !!strcmp(from_tag, dialog->remote_tag); + to_mismatch = ast_strlen_zero(to_tag) || !!strcmp(to_tag, dialog->local_tag); + + /* Don't check from if the dialog is not established, due to multi forking the from can change when the call + * is not answered yet */ + if ((from_mismatch && dialog->established) || to_mismatch) { + if (from_mismatch) { + ast_debug(4, "Found %s on '%s' From: tag mismatch From: tag='%s' Remote tag='%s'\n", + dialog->originated_call ? "outgoing": "incoming", + dialog->call_id, S_OR(from_tag, ""), dialog->remote_tag); + } + + if (to_mismatch) { + ast_debug(4, "Found %s on '%s' To: tag mismatch; To: tag='%s' Local tag='%s'\n", + dialog->originated_call ? "outgoing": "incoming", + dialog->call_id, S_OR(to_tag, ""), dialog->local_tag); + } + + ao2_ref(dialog, -1); + ao2_unlock(dialog); + return NULL; + } + + if (dialog->channel) { + *channel = ast_channel_ref(dialog->channel); + } else { + *channel = NULL; + } + + ao2_unlock(dialog); + return dialog; +} + +/* Initialize RTP portion of a dialog */ +static int sip_dialog_alloc_rtp(struct sip_dialog *dialog) +{ + struct ast_sockaddr address; + struct ast_rtp_engine_ice *ice; + + if (dialog->method != SIP_METHOD_INVITE) { + return 0; + } + + ast_debug(3, "Allocating RTP for '%s'\n", dialog->call_id); + + if (!ast_sockaddr_isnull(&sip_config.rtp_bind_address)) { + ast_sockaddr_copy(&address, &sip_config.rtp_bind_address); + } else { + ast_sockaddr_copy(&address, &sip_config.udp_bind_address); + } + + /* Make sure previous RTP instances/FD's do not leak */ + sip_dialog_destroy_rtp(dialog); + + if (!(dialog->audio_rtp = ast_rtp_instance_new("asterisk", sip_sched_context, &address, NULL))) { + return -1; + } + + if (!dialog->peer->ice_support && (ice = ast_rtp_instance_get_ice(dialog->audio_rtp))) { + ice->stop(dialog->audio_rtp); + } + + ast_rtp_instance_set_timeout(dialog->audio_rtp, dialog->rtp_timeout); + ast_rtp_instance_set_hold_timeout(dialog->audio_rtp, dialog->rtp_hold_timeout); + ast_rtp_instance_set_keepalive(dialog->audio_rtp, dialog->rtp_keepalive); + + ast_rtp_instance_set_prop(dialog->audio_rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_STANDARD); + ast_rtp_instance_set_prop(dialog->audio_rtp, AST_RTP_PROPERTY_DTMF, + dialog->peer->dtmf_mode == SIP_DTMF_MODE_RFC2833); + ast_rtp_instance_set_prop(dialog->audio_rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, FALSE); + + ast_rtp_instance_set_qos(dialog->audio_rtp, sip_config.tos_audio, sip_config.cos_audio, "SIP audio"); + + if (dialog->peer->video_support && ast_format_cap_has_type(dialog->format_cap, AST_MEDIA_TYPE_VIDEO)) { + if (!(dialog->video_rtp = ast_rtp_instance_new("asterisk", sip_sched_context, &address, NULL))) { + return -1; + } + + if (!dialog->peer->ice_support && (ice = ast_rtp_instance_get_ice(dialog->video_rtp))) { + ice->stop(dialog->video_rtp); + } + + ast_rtp_instance_set_timeout(dialog->video_rtp, dialog->rtp_timeout); + ast_rtp_instance_set_hold_timeout(dialog->video_rtp, dialog->rtp_hold_timeout); + ast_rtp_instance_set_keepalive(dialog->video_rtp, dialog->rtp_keepalive); + + ast_rtp_instance_set_prop(dialog->video_rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_STANDARD); + ast_rtp_instance_set_qos(dialog->video_rtp, sip_config.tos_video, sip_config.cos_video, "SIP video"); + } + + if (dialog->peer->text_support) { + if (!(dialog->text_rtp = ast_rtp_instance_new("asterisk", sip_sched_context, &address, NULL))) { + return -1; + } + + if (!dialog->peer->ice_support && (ice = ast_rtp_instance_get_ice(dialog->text_rtp))) { + ice->stop(dialog->text_rtp); + } + + /* Do not timeout text as its not constant*/ + ast_rtp_instance_set_keepalive(dialog->text_rtp, dialog->rtp_keepalive); + ast_rtp_instance_set_prop(dialog->text_rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_STANDARD); + ast_rtp_instance_set_qos(dialog->text_rtp, sip_config.tos_text, sip_config.cos_text, "SIP text"); + } + + sip_dialog_set_rtp_nat(dialog); + return 0; +} + +/* Cleanup the RTP and SRTP portions of a dialog */ +static void sip_dialog_destroy_rtp(struct sip_dialog *dialog) +{ + ast_debug(3, "Destroying RTP for '%s'\n", dialog->call_id); + + ast_rtp_instance_destroy(dialog->audio_rtp); + dialog->audio_rtp = NULL; + + ast_rtp_instance_destroy(dialog->video_rtp); + dialog->video_rtp = NULL; + + ast_rtp_instance_destroy(dialog->text_rtp); + dialog->text_rtp = NULL; + + if (dialog->secure_audio_rtp) { + ast_sdp_srtp_destroy(dialog->secure_audio_rtp); + dialog->secure_audio_rtp = NULL; + } + + if (dialog->secure_video_rtp) { + ast_sdp_srtp_destroy(dialog->secure_video_rtp); + dialog->secure_video_rtp = NULL; + } + + if (dialog->secure_text_rtp) { + ast_sdp_srtp_destroy(dialog->secure_text_rtp); + dialog->secure_text_rtp = NULL; + } +} + +/* Immediately stop RTP, VRTP and UDPTL as applicable */ +void sip_dialog_stop_rtp(struct sip_dialog *dialog) +{ + /* Immediately stop RTP, VRTP and UDPTL as applicable */ + if (dialog->audio_rtp) { + ast_rtp_instance_stop(dialog->audio_rtp); + } + + if (dialog->video_rtp) { + ast_rtp_instance_stop(dialog->video_rtp); + } + + if (dialog->text_rtp) { + ast_rtp_instance_stop(dialog->text_rtp); + } + + if (dialog->udptl) { + ast_udptl_stop(dialog->udptl); + } +} + +static void sip_dialog_get_peer_config(struct sip_dialog *dialog, struct sip_peer *peer) +{ + ast_string_field_set(dialog, to_host, peer->host); + ast_string_field_set(dialog, contact, peer->contact); + + ast_string_field_set(dialog, caller_number, peer->caller_number); + ast_string_field_set(dialog, caller_name, peer->caller_name); + ast_string_field_set(dialog, caller_tag, peer->caller_tag); + dialog->caller_presentation = peer->caller_presentation; + + dialog->secure_signaling = peer->default_transport == AST_TRANSPORT_TLS; + dialog->secure_media = peer->secure_media; + + dialog->nat_force_rport = peer->nat_force_rport; + dialog->nat_auto_rport = peer->nat_auto_rport; + dialog->nat_rtp = peer->nat_rtp; + dialog->nat_auto_rtp = peer->nat_auto_rtp; + dialog->direct_media = peer->direct_media; + + /* Set timer T1 to RTT for this peer (if known by qualify=). Minimum is settable or default to 100 ms. If there + * is a qualify_max and qualify response use that over a manual T1 value. Otherwise, use the peer's T1 value */ + if (peer->qualify_max && peer->qualify) { + dialog->timer_t1 = MAX(peer->qualify, sip_config.min_timer_t1); + } else { + dialog->timer_t1 = peer->timer_t1; + } + + /* Set timer B to control transaction timeouts, the peer setting is the default and overrides the known timer */ + dialog->timer_b = peer->timer_b; + dialog->max_forwards = peer->max_forwards; + + dialog->rtp_timeout = peer->rtp_timeout; + dialog->rtp_hold_timeout = peer->rtp_hold_timeout; + dialog->rtp_keepalive = peer->rtp_keepalive; + + dialog->session_timer_refresher = peer->session_timer_refresher; + dialog->session_timer_expires = peer->session_timer_max_expires; + + if (!dialog->port_in_uri) { + dialog->port_in_uri = peer->port_in_uri; + } + + if (peer->allow_methods) { + dialog->allow_methods = peer->allow_methods; + } + + if (ast_strlen_zero(dialog->to_host)) { + ast_string_field_set(dialog, to_host, ast_sockaddr_stringify_host_remote(&dialog->address)); + } + + if (!ast_strlen_zero(peer->from_domain)) { + ast_string_field_set(dialog, from_domain, peer->from_domain); + } + + if (peer->from_domain_port) { + dialog->from_domain_port = peer->from_domain_port; + } + + if (!ast_strlen_zero(peer->from_user)) { + ast_string_field_set(dialog, from_user, peer->from_user); + } + + sip_route_copy(&dialog->route, &peer->path); + + if (!sip_route_empty(&dialog->route)) { + /* Parse SIP URI of first route-set hop and use it as target address */ + sip_get_uri_address(sip_route_first_uri(&dialog->route), &dialog->address); + } + + /* Take the peer's caps */ + if (peer->format_cap) { + ast_format_cap_remove_by_type(dialog->format_cap, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(dialog->format_cap, peer->format_cap, AST_MEDIA_TYPE_UNKNOWN); + } + + if (dialog->peer->dtmf_mode == SIP_DTMF_MODE_RFC2833) { + dialog->non_format_cap |= AST_RTP_DTMF; + } else { + dialog->non_format_cap &= ~AST_RTP_DTMF; + } + + dialog->joint_non_format_cap = dialog->non_format_cap; +} + +/* Create address structure from peer reference. This function copies data from peer to the dialog, so we don't have + * to look up the peer again from memory or database during the life time of the dialog */ +int sip_dialog_build_from_peer(struct sip_dialog *dialog, struct sip_peer *peer) +{ + /* This checks that the dialog is contacting the peer on a valid transport type based on the peers transport + * configuration, otherwise, this function bails out */ + if (dialog->socket.transport && sip_dialog_check_transport(dialog, peer)) { + return -1; + } + + if (ast_sockaddr_isnull(&peer->address) || (peer->qualify_max && peer->qualify > peer->qualify_max)) { + return -1; + } + + ast_sockaddr_copy(&dialog->address, &peer->address); + ast_sockaddr_copy(&dialog->socket.address, &peer->address); + sip_socket_copy(&dialog->socket, &peer->socket); + + ao2_cleanup(dialog->peer); + dialog->peer = ao2_bump(peer); + + sip_dialog_get_peer_config(dialog, peer); + + /* Update dialog authorization credentials */ + ao2_cleanup(dialog->authentication_realms); + dialog->authentication_realms = ao2_bump(peer->authentication_realms); + + if (sip_dialog_alloc_rtp(dialog)) { + return -1; + } + + return 0; +} + +/* Create a dialog from a peer or address or host name */ +int sip_dialog_build(struct sip_dialog *dialog, const char *name, struct ast_sockaddr *address, int new_dialog) +{ + RAII_VAR(struct sip_peer *, peer, NULL, ao2_cleanup); + + if (!address && (peer = sip_peer_find(name, TRUE, FALSE))) { + if (new_dialog) { + sip_socket_set_transport(&dialog->socket, 0); + } + + return sip_dialog_build_from_peer(dialog, peer); + } + + if (ast_check_digits(name)) { + /* Although an IPv4 hostname *could* be represented as a 32-bit integer, it is uncommon and it makes + * dialing SIP/${EXTEN} for a peer that isn't defined resolve to an IP that is almost certainly not + * intended. It is much better to just reject purely numeric hostnames */ + ast_log(LOG_WARNING, "Rejecting purely numeric hostname '%s' which is not a peer\n", name); + return -1; + } + + if (address) { + /* This address should be updated using dnsmgr */ + ast_sockaddr_copy(&dialog->address, address); + } else { + char service[MAXHOSTNAMELEN], *host; + int port; + + /* Let's see if we can find the host in DNS. First try DNS SRV records, then hostname lookup */ + /* Section 4.2 of RFC 3263 specifies that if a port number is specified, then an A record lookup should + * be used instead of SRV */ + host = ast_strdupa(name); + + if (sip_parse_port(host, &port)) { + ast_log(LOG_WARNING, "Invalid port for host '%s'\n", name); + return -1; + } + + if (!port && sip_config.srv_lookup) { + snprintf(service, sizeof(service), "_%s._%s.%s", + sip_srv_service(dialog->socket.transport), sip_srv_protocol(dialog->socket.transport), + host); + + if (ast_get_srv(NULL, host, sizeof(host), &port, service) < 1) { + host = (char *) name; + } + } + + if (ast_sockaddr_resolve_first_af(&dialog->address, host, 0, AST_AF_INET)) { + ast_log(LOG_WARNING, "No such host: %s\n", host); + return -1; + } + + if (port) { + dialog->port_in_uri = TRUE; + ast_sockaddr_set_port(&dialog->address, port); + } + } + + ast_string_field_set(dialog, to_host, ast_sockaddr_stringify_host(&dialog->address)); + + if (!dialog->socket.transport) { + sip_socket_set_transport(&dialog->socket, AST_TRANSPORT_UDP); + } + + if (!ast_sockaddr_port(&dialog->address)) { + ast_sockaddr_set_port(&dialog->address, + dialog->socket.transport == AST_TRANSPORT_TLS ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + } + + ast_sockaddr_copy(&dialog->socket.address, &dialog->address); + + dialog->timer_t1 = sip_config.timer_t1; + dialog->timer_b = sip_config.timer_b; + + dialog->rtp_timeout = sip_config.rtp_timeout; + dialog->rtp_hold_timeout = sip_config.rtp_hold_timeout; + dialog->rtp_keepalive = sip_config.rtp_keepalive; + + /* Create a temporary peer so that we can get peer config directly rather than having to copy it into dialog */ + dialog->peer = sip_peer_temp_alloc(name); + + if (sip_dialog_alloc_rtp(dialog)) { + return -1; + } + + sip_proxy_set(dialog, sip_proxy_get(dialog, NULL)); /* Get the outbound proxy information */ + return 0; +} + +/* Build SIP Call-ID value for a non-REGISTER transaction. The passed in dialog must not be in a dialogs container since + * this function changes the hash key used by the container */ +static void sip_dialog_build_call_id(struct sip_dialog *dialog) +{ + ast_string_field_build(dialog, call_id, "%08lx%08lx%08lx%08lx%08lx%08lx", + (unsigned long) ast_random(), (unsigned long) ast_random(), (unsigned long) ast_random(), + (unsigned long) ast_random(), (unsigned long) ast_random(), (unsigned long) ast_random()); +} + +/* Make our SIP dialog tag */ +void sip_dialog_build_local_tag(struct sip_dialog *dialog) +{ + ast_string_field_build(dialog, local_tag, "%08lx%08lx", + (unsigned long) ast_random(), (unsigned long) ast_random()); +} + +/* Builds the sip_dialog's nonce field which is used for the authentication challenge. When force is not set, the nonce + * is only updated if the current one is stale. In this case, a stalenonce is one which has already received a response, + * if a nonce has not received a response it is not always necessary or beneficial to create a new one */ +void sip_dialog_build_nonce(struct sip_dialog *dialog, int force) +{ + if (dialog->stale_nonce || force || ast_strlen_zero(dialog->nonce)) { + /* Create nonce for challenge */ + ast_string_field_build(dialog, nonce, "%08lx", (unsigned long) ast_random()); + dialog->stale_nonce = FALSE; + } +} + +/* Build contact header. This is the Contact header that we send out in SIP requests and responses involving this + * dialog. The incoming parameter is used to tell if we are building the request parameter is an incoming SIP request + * that we are building the Contact header in response to, or if the req parameter is an outbound SIP request that we + * will later be adding the Contact header to */ +void sip_dialog_build_contact(struct sip_dialog *dialog, struct sip_message *request) +{ + char encoded_user[512], *user; + int use_tls; + + use_tls = FALSE; + + if (request) { + char *record_route; + + record_route = ast_strdupa(sip_message_find_header(request, "Record-Route")); + record_route = sip_get_uri(record_route); + + if (!strncmp(request->uri, "sips:", 5)) { + use_tls = TRUE; + } else if (!ast_strlen_zero(record_route) && !strncmp(record_route, "sips:", 5)) { + use_tls = TRUE; + } else { + char *contact; + + contact = ast_strdupa(sip_message_find_header(request, "Contact")); + contact = sip_get_uri(contact); + + if (!ast_strlen_zero(contact) && !strncmp(contact, "sips:", 5)) { + use_tls = TRUE; + } + } + } else { + const char *uri = sip_route_first_uri(&dialog->route); + + if (!strncmp(dialog->uri, "sips:", 5)) { + use_tls = TRUE; + } else if (!ast_strlen_zero(uri) && !strncmp(uri, "sips:", 5)) { + use_tls = TRUE; + } + } + + user = ast_uri_encode(dialog->to_user, encoded_user, sizeof(encoded_user), ast_uri_sip_user); + + if (dialog->socket.transport == AST_TRANSPORT_UDP) { + ast_string_field_build(dialog, our_contact, "<%s:%s%s%s>", use_tls ? "sips" : "sip", + user, ast_strlen_zero(user) ? "" : "@", ast_sockaddr_stringify_remote(&dialog->our_address)); + } else { + char *transport = ast_str_to_lower(ast_strdupa(ast_transport2str(dialog->socket.transport))); + + ast_string_field_build(dialog, our_contact, + "<%s:%s%s%s;transport=%s>", use_tls ? "sips" : "sip", user, ast_strlen_zero(user) ? "" : "@", + ast_sockaddr_stringify_remote(&dialog->our_address), transport); + } +} + +/* Build route list from Record-Route header */ +void sip_dialog_build_route(struct sip_dialog *dialog, struct sip_message *request, int backwards) +{ + int iter = 0; + + /* Once a persistent route is set, don't fool with it */ + if (!sip_route_empty(&dialog->route) && dialog->route_persistent) { + ast_debug(1, "Retaining previous route: <%s>\n", sip_route_first_uri(&dialog->route)); + return; + } + + sip_route_destroy(&dialog->route); + + /* We only want to create the route set the first time this is called except it is called from a provisional + * response */ + if (request->code < 100 || request->code > 199) { + dialog->route_persistent = TRUE; + } + + /* Build a tailq, then assign it to dialog->route when done. If backwards, we add entries from the head so they + * end up in reverse order. However, we do need to maintain a correct tail pointer because the contact is always + * at the end. 1st we pass through all the hops in any Record-Route headers */ + for (;;) { + const char *record_route = sip_message_next_header(request, "Record-Route", &iter); + + if (ast_strlen_zero(record_route)) { + break; + } + + sip_route_parse(&dialog->route, record_route, backwards); + } + + /* Only append the contact if we are dealing with a strict router or have no route */ + if (sip_route_empty(&dialog->route) || sip_route_is_strict(&dialog->route)) { + /* 2nd append the Contact: if there is one */ + /* Can be multiple Contact headers, comma separated values - we just take the first */ + const char *contact = sip_message_find_header(request, "Contact"); + + if (!ast_strlen_zero(contact)) { + sip_route_parse(&dialog->route, contact, FALSE); + } + } +} + +/* Set nat mode on the various data sockets */ +void sip_dialog_set_rtp_nat(struct sip_dialog *dialog) +{ + if (dialog->audio_rtp) { + ast_debug(1, "Setting NAT on audio RTP to %s\n", dialog->nat_rtp ? "on" : "off"); + ast_rtp_instance_set_prop(dialog->audio_rtp, AST_RTP_PROPERTY_NAT, dialog->nat_rtp); + } + + if (dialog->video_rtp) { + ast_debug(1, "Setting NAT on video RTP to %s\n", dialog->nat_rtp ? "on" : "off"); + ast_rtp_instance_set_prop(dialog->video_rtp, AST_RTP_PROPERTY_NAT, dialog->nat_rtp); + } + + if (dialog->text_rtp) { + ast_debug(1, "Setting NAT on text RTP to %s\n", dialog->nat_rtp ? "on" : "off"); + ast_rtp_instance_set_prop(dialog->text_rtp, AST_RTP_PROPERTY_NAT, dialog->nat_rtp); + } + + if (dialog->udptl) { + ast_debug(1, "Setting NAT on UDPTL to %s\n", dialog->nat_rtp ? "on" : "off"); + ast_udptl_setnat(dialog->udptl, dialog->nat_rtp); + } +} + +/* Check and see if the requesting UA is likely to be behind a NAT. If the requesting NAT is behind NAT, set the + * Asterisk natdetected flag so that later, peers with nat=auto_* can use the value. Also, set the flags so that + * Asterisk responds identically whether or not a peer exists so as not to leak peer name information */ +void sip_dialog_check_nat(struct sip_dialog *dialog, const struct ast_sockaddr *address) +{ + if (!address || !dialog) { + return; + } + + if (ast_sockaddr_cmp_addr(&dialog->socket.address, address)) { + char *host, *socket_host; + + host = ast_strdupa(ast_sockaddr_stringify_addr(address)); + socket_host = ast_strdupa(ast_sockaddr_stringify_addr(&dialog->socket.address)); + dialog->nat_detected = TRUE; + + if (dialog->nat_auto_rport) { + dialog->nat_force_rport = TRUE; + } + + if (dialog->nat_auto_rtp) { + dialog->nat_rtp = TRUE; + } + + ast_debug(3, "NAT detected for '%s' via '%s'\n", host, socket_host); + } else { + dialog->nat_detected = FALSE; + + if (dialog->nat_auto_rport) { + dialog->nat_force_rport = FALSE; + } + + if (dialog->nat_auto_rtp) { + dialog->nat_rtp = FALSE; + } + } +} + +/* Check Via: header for hostname, port and rport request/answer */ +void sip_dialog_check_via(struct sip_dialog *dialog, struct sip_message *request) +{ + struct ast_sockaddr address; + int port; + + if (request->via_rport) { /* 'rport' query, not answer */ + dialog->rport_present = TRUE; + } + + if (!ast_strlen_zero(request->via_maddr) && + ast_sockaddr_resolve_first_af(&dialog->address, request->via_maddr, 0, AST_AF_INET)) { + ast_sockaddr_copy(&dialog->address, &dialog->socket.address); + } + + if (ast_sockaddr_resolve_first_af(&address, request->via_sent_by, 0, AST_AF_INET)) { + ast_log(LOG_WARNING, "Could not resolve socket address for '%s'\n", request->via_sent_by); + port = SIP_STANDARD_PORT; + } else if (!(port = ast_sockaddr_port(&address))) { + port = SIP_STANDARD_PORT; + ast_sockaddr_set_port(&address, port); + } + + ast_sockaddr_set_port(&dialog->address, port); + sip_dialog_check_nat(dialog, &address); + + ast_debug(1, "Via send address '%s' %s\n", + ast_sockaddr_stringify(&address), dialog->nat_force_rport ? "NAT" : "no NAT"); +} + +void sip_dialog_set_need_destroy(struct sip_dialog *dialog, const char *reason) +{ + if (dialog->destroy_scheduled) { + return; /* This is already scheduled for final destruction, let the scheduler take care of it */ + } + + if (!dialog->need_destroy) { + ast_debug(2, "Setting need-destroy on '%s' because '%s'\n", dialog->call_id, reason); + dialog->need_destroy = TRUE; + + ao2_link(sip_dialogs_need_destroy, dialog); + } +} + +/* Encapsulate setting of SIP_ALREADYGONE to be able to trace it with debugging */ +void sip_dialog_set_already_gone(struct sip_dialog *dialog) +{ + if (!dialog->already_gone) { + ast_debug(2, "Setting already-gone on '%s'\n", dialog->call_id); + dialog->already_gone = TRUE; + } +} + +/* Set the owning channel on the \ref sip_dialog object */ +void sip_dialog_set_channel(struct sip_dialog *dialog, struct ast_channel *channel) +{ + dialog->channel = channel; + + if (dialog->audio_rtp) { + ast_rtp_instance_set_channel_id(dialog->audio_rtp, + dialog->channel ? ast_channel_uniqueid(dialog->channel) : ""); + } + + if (dialog->video_rtp) { + ast_rtp_instance_set_channel_id(dialog->video_rtp, + dialog->channel ? ast_channel_uniqueid(dialog->channel) : ""); + } + + if (dialog->text_rtp) { + ast_rtp_instance_set_channel_id(dialog->text_rtp, + dialog->channel ? ast_channel_uniqueid(dialog->channel) : ""); + } +} + +/* Find out who the call is for. We use the request uri as a destination. This code assumes authentication has been + * done, so that the device (peer/user) context is already set. If the incoming uri is a SIPS: uri, we are required to + * carry this across the dialplan, so that the outbound call also is a sips: call or encrypted IAX2 call. If that's not + * available, the call should FAIL */ +int sip_dialog_get_destination(struct sip_dialog *dialog, struct sip_message *request) +{ + char *uri, *user, *domain, *from, *from_user, *pickup_exten; + struct ast_features_pickup_config *pickup_config; + + if ((pickup_config = ast_get_chan_features_pickup_config(dialog->channel))) { + /* Don't need to duplicate since channel is locked for the duration of this function */ + pickup_exten = ast_strdupa(pickup_config->pickupexten); + ao2_cleanup(pickup_config); + } else { + ast_log(LOG_ERROR, + "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n"); + pickup_exten = ""; + } + + uri = ast_strdupa(request->uri); + + if (sip_parse_uri(uri, NULL, &user, &domain, NULL, NULL)) { + ast_log(LOG_WARNING, "Invalid URI '%s'\n", request->uri); + return SIP_DESTINATION_INVALID_URI; + } + + if (ast_strlen_zero(user)) { + user = "s"; /* No extension found or the request URI, use "s" as the extension */ + } + + domain = strsep(&domain, ":"); + + /* Now find the From: caller ID and name. Why is this done in sip_dialog_get_destination? Isn't it already done? + * Needs to be checked */ + from = ast_strdupa(sip_message_find_header(request, "From")); + + if (!ast_strlen_zero(from)) { + char *from_domain; + + if (sip_parse_contact(from, NULL, &from_user, &from_domain, NULL, NULL)) { + ast_log(LOG_WARNING, "Invalid From: '%s'\n", sip_message_find_header(request, "From")); + return SIP_DESTINATION_INVALID_URI; + } + + from_domain = strsep(&from_domain, ":"); + ast_string_field_set(dialog, from_domain, from_domain); + } else { + from_user = NULL; + } + + if (ao2_container_count(sip_domains) && !sip_domain_check(domain, NULL, 0)) { + if (!sip_config.allow_external_domains && request->method & (SIP_METHOD_INVITE | SIP_METHOD_REFER)) { + ast_debug(1, "Received %s to non-local domain '%s' refusing request\n", + sip_method2str(request->method), domain); + return SIP_DESTINATION_REFUSED; + } + } + + ast_debug(1, "Looking for exten '%s' in context '%s'\n", user, dialog->peer->context); + + /* If this is a subscription we actually just need to see if a hint exists for the extension */ + if (request->method == SIP_METHOD_SUBSCRIBE) { + if (ast_get_hint(NULL, 0, NULL, 0, NULL, + S_OR(dialog->peer->subscribe_context, dialog->peer->context), user)) { + if (request == &dialog->initial_request) { + ast_string_field_set(dialog, to_user, user); + } + + return SIP_DESTINATION_EXTEN_FOUND; + } + + return SIP_DESTINATION_EXTEN_NOT_FOUND; + } + + if (ast_exists_extension(NULL, dialog->peer->context, user, 1, S_OR(dialog->caller_number, from_user)) || + !strcmp(user, pickup_exten)) { + if (request == &dialog->initial_request) { + ast_string_field_set(dialog, to_user, user); + } + + return SIP_DESTINATION_EXTEN_FOUND; + } + + if (dialog->peer->allow_overlap && + (ast_canmatch_extension(NULL, dialog->peer->context, user, 1, S_OR(dialog->caller_number, from_user)) || + !strncmp(user, pickup_exten, strlen(user)))) { + /* Overlap dialing is enabled and we need more digits to match an extension */ + return SIP_DESTINATION_EXTEN_MATCH_MORE; + } + + return SIP_DESTINATION_EXTEN_NOT_FOUND; +} + +/* Choose realm based on From header and then To header or use globally configured realm. Realm from From/To header + * should be listed among served domains in config file: domain=.. */ +void sip_dialog_set_realm(struct sip_dialog *dialog, const struct sip_message *request) +{ + if (!ast_strlen_zero(dialog->authorization_realm)) { + return; + } + + if (sip_config.domains_as_realm && ao2_container_count(sip_domains)) { + char *from, *to, *user, *domain; + + from = ast_strdupa(sip_message_find_header(request, "From")); + + if (!sip_parse_contact(from, NULL, &user, &domain, NULL, NULL)) { + domain = strsep(&domain, ":"); + + /* Check From header first */ + if (sip_domain_check(domain, NULL, 0)) { + ast_string_field_set(dialog, authorization_realm, domain); + return; + } + } + + to = ast_strdupa(sip_message_find_header(request, "To")); + + if (!sip_parse_contact(to, NULL, &user, &domain, NULL, NULL)) { + domain = strsep(&domain, ":"); + + /* Check To header */ + if (sip_domain_check(domain, NULL, 0)) { + ast_string_field_set(dialog, authorization_realm, domain); + return; + } + } + } + + /* Use default realm from config file */ + ast_string_field_set(dialog, authorization_realm, sip_config.realm); +} + +void sip_dialog_set_dsp_detect(struct sip_dialog *dialog, int enabled) +{ + if (enabled) { + int features = 0; + + if (dialog->dsp) { + return; + } + + if (dialog->peer->dtmf_mode == SIP_DTMF_MODE_INBAND) { + if (dialog->audio_rtp) { + ast_rtp_instance_dtmf_mode_set(dialog->audio_rtp, AST_RTP_DTMF_MODE_INBAND); + } + + features |= DSP_FEATURE_DIGIT_DETECT; + } + + if (dialog->peer->fax_detect & SIP_FAX_DETECT_CNG) { + features |= DSP_FEATURE_FAX_DETECT; + } + + if (!features) { + return; + } + + if (!(dialog->dsp = ast_dsp_new())) { + return; + } + + ast_dsp_set_features(dialog->dsp, features); + + if (dialog->peer->relax_dtmf) { + ast_dsp_set_digitmode(dialog->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF); + } + } else { + if (dialog->dsp) { + ast_dsp_free(dialog->dsp); + dialog->dsp = NULL; + } + } +} + +/* Using the local_networks structure built up with localnet settings apply it to their address to see if we need + * to substitute our externaddr or can get away with our internal bindaddr 'us' is always overwritten */ +void sip_dialog_set_our_address(struct sip_dialog *dialog) +{ + /* Set want_remap to non-zero if we want to remap 'our_addr' to an externally reachable IP address and port. + * This is done if: + * 1. we have a localaddr list (containing 'internal' addresses marked as 'deny', so ast_apply_ha() will return + * AST_SENSE_DENY on them, and AST_SENSE_ALLOW on 'external' ones); + * 2. externaddr is set, so we know what to use as the externally visible address; + * 3. the remote address, 'them', is external; + * 4. the address returned by ast_ouraddrfor() is 'internal' (AST_SENSE_DENY when passed to ast_apply_ha() so it + * does need to be remapped. This fourth condition is checked later */ + static int external_expires = 0; + int want_remap; + + /* Starting guess for the internal address */ + ast_sockaddr_copy(&dialog->our_address, &sip_our_address); + /* Now ask the system what would it use to talk to 'address' */ + ast_ouraddrfor(&dialog->address, &dialog->our_address); + + if (ast_sockaddr_is_ipv6(&dialog->address) && !ast_sockaddr_is_ipv4_mapped(&dialog->address)) { + if (sip_config.internal_networks && + !ast_sockaddr_isnull(&sip_config.external_address) && + !ast_sockaddr_is_any(&sip_config.udp_bind_address)) { + ast_log(LOG_WARNING, + "Address remapping activated in but we're using IPv6, which doesn't need it\n"); + } + + want_remap = FALSE; + } else { + want_remap = sip_config.internal_networks && !ast_sockaddr_isnull(&sip_config.external_address) && + ast_apply_ha(sip_config.internal_networks, &dialog->address) == AST_SENSE_ALLOW; + } + + if (want_remap && ast_apply_ha(sip_config.internal_networks, &dialog->our_address) == AST_SENSE_DENY) { + /* If we used externalhost, see if it is time to refresh the info */ + if (sip_config.external_expires && time(NULL) >= external_expires) { + if (ast_sockaddr_resolve_first_af(&sip_config.external_address, sip_config.external_host, 0, AST_AF_INET)) { + ast_log(LOG_WARNING, "Re-lookup of '%s' failed\n", sip_config.external_host); + } + + external_expires = time(NULL) + sip_config.external_expires; + } + + if (!ast_sockaddr_isnull(&sip_config.external_address)) { + ast_sockaddr_copy(&dialog->our_address, &sip_config.external_address); + + switch (dialog->socket.transport) { + case AST_TRANSPORT_TCP: + if (!sip_config.external_tcp_port && ast_sockaddr_port(&sip_config.external_address)) { + /* For consistency, default to the externaddr port */ + sip_config.external_tcp_port = ast_sockaddr_port(&sip_config.external_address); + } + + if (!sip_config.external_tcp_port) { + sip_config.external_tcp_port = ast_sockaddr_port(&sip_tcp_session.local_address); + } + + if (!sip_config.external_tcp_port) { + sip_config.external_tcp_port = SIP_STANDARD_PORT; + } + + ast_sockaddr_set_port(&dialog->our_address, sip_config.external_tcp_port); + break; + case AST_TRANSPORT_TLS: + if (!sip_config.external_tls_port) { + sip_config.external_tls_port = ast_sockaddr_port(&sip_tls_session.local_address); + } + + if (!sip_config.external_tls_port) { + sip_config.external_tls_port = SIP_STANDARD_TLS_PORT; + } + + ast_sockaddr_set_port(&dialog->our_address, sip_config.external_tls_port); + break; + case AST_TRANSPORT_UDP: + if (!ast_sockaddr_port(&sip_config.external_address)) { + ast_sockaddr_set_port(&dialog->our_address, + ast_sockaddr_port(&sip_config.udp_bind_address)); + } + + break; + default: + break; + } + } + + ast_debug(1, "Target address %s is not local, substituting external address\n", + ast_sockaddr_stringify(&dialog->address)); + } else { + /* No remapping, but we bind to a specific address, so use it */ + switch (dialog->socket.transport) { + case AST_TRANSPORT_TCP: + if (!ast_sockaddr_isnull(&sip_tcp_session.local_address)) { + if (!ast_sockaddr_is_any(&sip_tcp_session.local_address)) { + ast_sockaddr_copy(&dialog->our_address, &sip_tcp_session.local_address); + } else { + ast_sockaddr_set_port(&dialog->our_address, + ast_sockaddr_port(&sip_tcp_session.local_address)); + } + + break; + } + /* Fall through */ + case AST_TRANSPORT_TLS: + if (!ast_sockaddr_isnull(&sip_tls_session.local_address)) { + if (!ast_sockaddr_is_any(&sip_tls_session.local_address)) { + ast_sockaddr_copy(&dialog->our_address, &sip_tls_session.local_address); + } else { + ast_sockaddr_set_port(&dialog->our_address, + ast_sockaddr_port(&sip_tls_session.local_address)); + } + + break; + } + /* Fall through */ + case AST_TRANSPORT_UDP: + /* Fall through */ + default: + if (!ast_sockaddr_is_any(&sip_config.udp_bind_address)) { + ast_sockaddr_copy(&dialog->our_address, &sip_config.udp_bind_address); + } + + if (!ast_sockaddr_port(&dialog->our_address)) { + ast_sockaddr_set_port(&dialog->our_address, + ast_sockaddr_port(&sip_config.udp_bind_address)); + } + } + } + + ast_debug(3, "Setting AST_TRANSPORT_%s with address %s\n", + ast_transport2str(dialog->socket.transport), ast_sockaddr_stringify(&dialog->our_address)); +} + +/* Save contact header for 200 OK on INVITE */ +void sip_dialog_set_contact(struct sip_dialog *dialog, struct sip_message *message) +{ + char *contact = ast_strdupa(sip_message_find_header(message, "Contact")); + + contact = sip_get_uri(contact); + ast_string_field_set(dialog, contact, contact); /* Save URI for later ACKs, BYE or re-INVITE */ +} + +/* Update inuse and ringing counts for dialog and the peer */ +int sip_dialog_change_inuse(struct sip_dialog *dialog, int event) +{ + ast_debug(3, "Changing inuse for %s %s'\n", dialog->originated_call ? "outgoing" : "incoming", dialog->call_id); + + ao2_lock(dialog); + ao2_lock(dialog->peer); + + switch (event) { + case SIP_RINGING_REMOVE: + if (dialog->ringing) { + if (dialog->peer->ringing > 0) { + dialog->peer->ringing--; + } + + dialog->ringing = FALSE; + } + + break; + case SIP_INUSE_REMOVE: + /* Decrement ringing count if applicable */ + if (dialog->peer->ringing > 0) { + if (dialog->ringing) { + dialog->peer->ringing--; + dialog->ringing = FALSE; + } + } else { + dialog->peer->ringing = 0; + } + + if (dialog->peer->inuse > 0) { + if (dialog->inuse) { + dialog->peer->inuse--; + dialog->inuse = FALSE; + } + } else { + dialog->peer->inuse = 0; + } + + if (dialog->onhold) { + dialog->peer->onhold--; + dialog->onhold = SIP_ONHOLD_SENDRECV; + } + + ast_debug(2, "Call %s peer '%s' removed from call limit %d out of %d\n", + dialog->originated_call ? "to" : "from", dialog->peer->name, dialog->peer->inuse, + dialog->peer->max_calls); + break; + case SIP_RINGING_ADD: + case SIP_INUSE_ADD: + /* If call limit is active and we have reached the limit, reject the call */ + if (dialog->peer->max_calls > 0) { + if (dialog->peer->inuse >= dialog->peer->max_calls) { + ast_log(LOG_NOTICE, "Call %s %s '%s' rejected due to usage limit of %d\n", + dialog->originated_call ? "to" : "from", "peer", dialog->peer->name, + dialog->peer->max_calls); + + ao2_unlock(dialog->peer); + ao2_unlock(dialog); + + return -1; + } + } + + if (event == SIP_RINGING_ADD) { + if (!dialog->ringing) { + dialog->peer->ringing++; + dialog->ringing = TRUE; + } + } + + if (!dialog->inuse) { + dialog->peer->inuse++; + dialog->inuse = TRUE; + } + + ast_debug(2, "Call %s peer '%s' added to call limit %d out of %d\n", + dialog->originated_call ? "to" : "from", dialog->peer->name, dialog->peer->inuse, + dialog->peer->max_calls); + break; + default: + break; + } + + ao2_unlock(dialog->peer); + ao2_unlock(dialog); + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", dialog->peer->name); + return 0; +} + +/* Change hold state for a call */ +void sip_dialog_change_onhold(struct sip_dialog *dialog, int onhold) +{ + if (dialog->onhold == onhold) { + return; + } + + ast_debug(2, "Changing %s onhold for '%s'\n", + dialog->originated_call ? "incoming" : "outgoing", dialog->call_id); + + ao2_lock(dialog); + ao2_lock(dialog->peer); + + if (onhold != SIP_ONHOLD_SENDRECV) { + dialog->peer->onhold++; + } else { + dialog->peer->onhold--; + } + + dialog->onhold = onhold; + + ao2_unlock(dialog->peer); + ao2_unlock(dialog); + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", dialog->peer->name); +} + +/* Notify peer that the connected line has changed */ +void sip_dialog_update_connected_line(struct sip_dialog *dialog) +{ + if (!dialog->peer->identity_support) { + return; + } + + if (ast_channel_state(dialog->channel) == AST_STATE_UP || dialog->outgoing) { + if (dialog->allow_methods & SIP_METHOD_UPDATE) { + sip_request_send_update(dialog); + } else if (!dialog->pending_invite_cseq && + (dialog->invite_state == SIP_INVITE_CONFIRMED || dialog->invite_state == SIP_INVITE_TERMINATED)) { + sip_request_send_reinvite_with_sdp(dialog, TRUE, dialog->fax_state == SIP_FAX_ENABLED); + } else { + /* We cannot send the update yet, so we have to wait until we can */ + dialog->need_reinvite = TRUE; + } + } else { + if (ast_channel_state(dialog->channel) == AST_STATE_RING && !dialog->sent_progress) { + sip_response_send_with_identity(dialog, "180 Ringing", &dialog->initial_request); + + dialog->sent_ringing = TRUE; + } else if (ast_channel_state(dialog->channel) == AST_STATE_RINGING) { + sip_response_send_with_identity(dialog, "183 Session Progress", &dialog->initial_request); + + dialog->sent_progress = TRUE; + } else { + dialog->pending_connected_line = TRUE; + + ast_debug(1, "Unable able to send update to '%s' in %s state\n", + ast_channel_name(dialog->channel), ast_state2str(ast_channel_state(dialog->channel))); + } + } +} + +void sip_dialog_queue_connected_line(struct sip_dialog *dialog, int source) +{ + struct ast_party_connected_line connected_line; + struct ast_set_party_connected_line update; + + ast_party_connected_line_init(&connected_line); + memset(&update, 0, sizeof(update)); + + update.id.number = TRUE; + connected_line.id.number.valid = TRUE; + connected_line.id.number.str = (char *) dialog->caller_number; + connected_line.id.number.presentation = dialog->caller_presentation; + + update.id.name = TRUE; + connected_line.id.name.valid = TRUE; + connected_line.id.name.str = (char *) dialog->caller_name; + connected_line.id.name.presentation = dialog->caller_presentation; + + connected_line.id.tag = (char *) dialog->caller_tag; + connected_line.source = source; + + /* Invalidate any earlier private connected id representation */ + ast_set_party_id_all(&update.priv); + ast_channel_queue_connected_line_update(dialog->channel, &connected_line, &update); +} + +/* Send a provisional response indicating that a call was redirected */ +void sip_dialog_update_redirecting(struct sip_dialog *dialog) +{ + if (ast_channel_state(dialog->channel) == AST_STATE_UP || dialog->outgoing) { + return; + } + + sip_response_send_with_diversion(dialog, &dialog->initial_request); +} + +void sip_dialog_queue_redirecting(struct sip_dialog *dialog) +{ + struct ast_party_redirecting redirecting; + struct ast_set_party_redirecting update; + + ast_party_redirecting_init(&redirecting); + memset(&update, 0, sizeof(update)); + + if (!ast_strlen_zero(dialog->redirecting_from_name)) { + update.from.name = TRUE; + redirecting.from.name.str = (char *) dialog->redirecting_from_name; + redirecting.from.name.valid = TRUE; + } + + if (!ast_strlen_zero(dialog->redirecting_from_name)) { + update.from.number = TRUE; + redirecting.from.name.str = (char *) dialog->redirecting_from_name; + redirecting.from.name.valid = TRUE; + } + + if (!ast_strlen_zero(dialog->redirecting_to_name)) { + update.to.name = TRUE; + redirecting.to.name.str = (char *) dialog->redirecting_to_name; + redirecting.to.name.valid = TRUE; + } + + if (!ast_strlen_zero(dialog->redirecting_to_name)) { + update.to.number = TRUE; + redirecting.to.name.str = (char *) dialog->redirecting_to_name; + redirecting.to.name.valid = TRUE; + } + + if (!ast_strlen_zero(dialog->caller_tag)) { + redirecting.from.tag = (char *) dialog->caller_tag; + redirecting.to.tag = (char *) dialog->caller_tag; + } + + if (!ast_strlen_zero(dialog->redirecting_reason)) { + redirecting.reason.str = (char *) dialog->redirecting_reason; + } + + redirecting.reason.code = dialog->redirecting_code; + + /* Invalidate any earlier private redirecting id representations */ + ast_set_party_id_all(&update.priv_orig); + ast_set_party_id_all(&update.priv_from); + ast_set_party_id_all(&update.priv_to); + + ast_channel_queue_redirecting_update(dialog->channel, &redirecting, &update); +} + +/* Reply to authentication for outbound registrations. This is used for register= servers in sip.conf, SIP proxies we + * register with for receiving calls from */ +int sip_dialog_parse_authorization(struct sip_dialog *dialog, struct sip_message *response) +{ + char *www_authenticate = ast_strdupa(sip_message_find_header(response, + response->code == 401 ? "WWW-Authenticate" : "Proxy-Authenticate")); + + if (ast_strlen_zero(www_authenticate)) { + return -1; + } + + sip_parse_authorization(dialog, www_authenticate); + + /* Reset nonce count */ + if (!ast_strlen_zero(dialog->authorization_nonce) && strcmp(dialog->nonce, dialog->authorization_nonce)) { + dialog->nonce_count = 0; + } + + ast_string_field_set(dialog, nonce, dialog->authorization_nonce); + return sip_dialog_build_authorization(dialog, response->method); +} + +/* Build reply digest. Build digest challenge for authentication of registrations and calls Also used for authentication + * of BYE */ +int sip_dialog_build_authorization(struct sip_dialog *dialog, int method) +{ + char a1[256], a2[512], a1_hash[64], a2_hash[64], response[1024], response_hash[64], uri[256], cnonce[16]; + const char *user, *secret, *md5_secret; + struct ast_str *authorization; + RAII_VAR(struct sip_authentication_realm *, authentication_realm, NULL, ao2_cleanup); + + if (!ast_strlen_zero(dialog->authorization_domain)) { + snprintf(uri, sizeof(uri), "%s:%s", + dialog->socket.transport == AST_TRANSPORT_TLS ? "sips" : "sip", dialog->authorization_domain); + } else if (!ast_strlen_zero(dialog->uri)) { + ast_copy_string(uri, dialog->uri, sizeof(uri)); + } else { + snprintf(uri, sizeof(uri), "%s:%s@%s", dialog->socket.transport == AST_TRANSPORT_TLS ? "sips" : "sip", + dialog->peer->authorization_user, ast_sockaddr_stringify_host_remote(&dialog->address)); + } + + /* Check if we have peer credentials, and if not use the global ones */ + if (!(authentication_realm = ao2_find(dialog->authentication_realms, dialog->authorization_realm, OBJ_SEARCH_KEY))) { + authentication_realm = ao2_find(sip_authentication_realms, dialog->authorization_realm, OBJ_SEARCH_KEY); + } + + if (authentication_realm) { + user = authentication_realm->user; + secret = authentication_realm->secret; + md5_secret = authentication_realm->md5_secret; + + ast_debug(1, "Using realm '%s' authentication for '%s'\n", + dialog->authorization_realm, dialog->call_id); + } else { + /* No auth realm, use peer config */ + if (!ast_strlen_zero(dialog->peer->authorization_user)) { + user = dialog->peer->authorization_user; + } else { + user = dialog->peer->name; + } + + if (!ast_strlen_zero(dialog->peer->remote_secret)) { + secret = dialog->peer->remote_secret; + } else { + secret = dialog->peer->secret; + } + + md5_secret = dialog->peer->md5_secret; + } + + if (ast_strlen_zero(user)) { + /* We have no authentication */ + return -1; + } + + /* Calculate SIP digest response */ + if (!ast_strlen_zero(md5_secret)) { + ast_copy_string(a1_hash, md5_secret, sizeof(a1_hash)); + } else { + snprintf(a1, sizeof(a1), "%s:%s:%s", user, dialog->authorization_realm, secret); + ast_md5_hash(a1_hash, a1); + } + + snprintf(a2, sizeof(a2), "%s:%s", sip_method2str(method), uri); + ast_md5_hash(a2_hash, a2); + + dialog->nonce_count++; + snprintf(cnonce, sizeof(cnonce), "%08lx", (unsigned long) ast_random()); + + if (!ast_strlen_zero(dialog->authorization_qop)) { + snprintf(response, sizeof(response), "%s:%s:%08x:%s:%s:%s", + a1_hash, dialog->authorization_nonce, (unsigned int) dialog->nonce_count, cnonce, "auth", a2_hash); + } else { + snprintf(response, sizeof(response), "%s:%s:%s", a1_hash, dialog->authorization_nonce, a2_hash); + } + + ast_md5_hash(response_hash, response); + + authorization = ast_str_alloca(2048); + ast_str_set(&authorization, 0, "Digest username=\"%s\",realm=\"%s\",algorithm=MD5,uri=\"%s\",nonce=\"%s\",response=\"%s\"", + user, dialog->authorization_realm, uri, dialog->authorization_nonce, response_hash); + + /* We hard code our qop to "auth" for now */ + if (!ast_strlen_zero(dialog->authorization_qop)) { + ast_str_append(&authorization, 0, ",qop=auth,cnonce=\"%s\",nc=%08x", cnonce, dialog->nonce_count); + } + + /* Only include the opaque string if it's set */ + if (!ast_strlen_zero(dialog->authorization_opaque)) { + ast_str_append(&authorization, 0, ",opaque=\"%s\"", dialog->authorization_opaque); + } + + ast_string_field_set(dialog, authorization, ast_str_buffer(authorization)); + return 0; +} + +/* Check user authorization from peer definition */ +static int sip_dialog_check_authorization(struct sip_dialog *dialog, struct sip_peer *peer, struct sip_message *request, + int reliable) +{ + char a1[256], a2[512], a1_hash[64], a2_hash[64], response[1024], response_hash[64], *authorization; + const char *user, *nonce; + int invalid_nonce, authenticated; + + /* Always OK if no secret */ + if (ast_strlen_zero(peer->secret) && ast_strlen_zero(peer->md5_secret)) { + return SIP_AUTHORIZATION_SUCCESS; + } else if (peer->cisco_mode && request->method & (SIP_METHOD_REFER | SIP_METHOD_PUBLISH)) { + return SIP_AUTHORIZATION_SUCCESS; /* Buggy Cisco phones can't auth REFER or PUBLISH correctly */ + } + + /* Always authentiate with WWW-auth since we're NOT a proxy Using proxy-auth in a B2BUA may block proxy + * authorization in the same transaction */ + authorization = ast_strdupa(sip_message_find_header(request, "Authorization")); + + if (request->ignore && !ast_strlen_zero(dialog->nonce) && ast_strlen_zero(authorization)) { + /* This is a retransmitted invite/register/etc, don't reconstruct authentication information */ + if (!reliable) { + /* Resend message if this was NOT a reliable delivery. Otherwise the retransmission should get + * it */ + sip_response_send_with_www_authenticate(dialog, request, reliable, FALSE); + /* Schedule auto destroy in 32 seconds (according to RFC 3261) */ + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } + + return SIP_AUTHORIZATION_CHALLENGE_SENT; + } else if (ast_strlen_zero(dialog->nonce) || ast_strlen_zero(authorization)) { + /* We have no auth, so issue challenge and message authentication */ + sip_dialog_build_nonce(dialog, TRUE); /* Create nonce for challenge */ + sip_response_send_with_www_authenticate(dialog, request, reliable, FALSE); + + /* Schedule auto destroy in 32 seconds */ + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return SIP_AUTHORIZATION_CHALLENGE_SENT; + } + + sip_parse_authorization(dialog, authorization); + + /* Cisco phones use the primary line credentials for secondary lines */ + if (peer->cisco_mode && peer->line_index > 1) { + user = peer->authorization_user; + } else { + user = peer->name; + } + + /* Verify that digest username matches the username we auth as */ + if (strcmp(user, dialog->authorization_username)) { + if (request->debug) { + ast_verb(3, "Authorization username mismatch for SIP peer '%s' response has '%s'\n", + user, dialog->authorization_username); + } + + /* Oops, we're trying something here */ + return SIP_AUTHORIZATION_USERNAME_MISMATCH; + } + + /* Verify nonce from message matches our nonce, and the nonce has not already been responded to. + * If this check fails, send 401 with new nonce */ + if (strcmp(dialog->nonce, dialog->authorization_nonce) || dialog->stale_nonce) { + invalid_nonce = TRUE; + nonce = dialog->authorization_nonce; + } else { + invalid_nonce = FALSE; + nonce = dialog->nonce; + /* Now, since the nonce has a response, mark it as stale so it can't be sent or responded to again */ + dialog->stale_nonce = TRUE; + } + + /* Whoever came up with the authentication section of SIP can suck my %&#$&* for not putting an example in the + * spec of just what it is you're doing a hash on */ + if (!ast_strlen_zero(peer->md5_secret)) { + ast_copy_string(a1_hash, peer->md5_secret, sizeof(a1_hash)); + } else { + snprintf(a1, sizeof(a1), "%s:%s:%s", user, dialog->authorization_realm, peer->secret); + ast_md5_hash(a1_hash, a1); + } + + /* Compute the expected response to compare with what we received */ + snprintf(a2, sizeof(a2), "%s:%s", sip_method2str(request->method), dialog->authorization_uri); + ast_md5_hash(a2_hash, a2); + + snprintf(response, sizeof(response), "%s:%s:%s", a1_hash, nonce, a2_hash); + ast_md5_hash(response_hash, response); + + /* Lastly, check that the peer isn't the fake peer */ + authenticated = !strcmp(response_hash, dialog->authorization_response); + + if (invalid_nonce) { + if (authenticated) { + if (request->debug) { + ast_verb(3, "Valid authorization received from SIP peer '%s' but with a stale nonce\n", + peer->name); + } + + /* We got working auth token, based on stale nonce */ + sip_dialog_build_nonce(dialog, TRUE); + sip_response_send_with_www_authenticate(dialog, request, reliable, TRUE); + } else { + /* Everything was wrong, so give the device one more try with a new challenge */ + if (!request->ignore) { + if (request->debug) { + ast_verb(3, "Invalid authorization received from SIP peer '%s'\n", peer->name); + } + + sip_dialog_build_nonce(dialog, FALSE); + } else if (request->debug) { + ast_verb(3, "Duplicate authorization received from SIP peer '%s'\n", peer->name); + } + + sip_response_send_with_www_authenticate(dialog, request, reliable, FALSE); + } + + /* Schedule auto destroy in 32 seconds */ + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return SIP_AUTHORIZATION_CHALLENGE_SENT; + } + + if (authenticated) { + return SIP_AUTHORIZATION_SUCCESS; + } + + /* Ok, we have a bad username/secret pair Tell the UAS not to re-send this authentication data, because it + * will continue to fail */ + return SIP_AUTHORIZATION_SECRET_FAILED; +} + +/* Check if matching user or peer is defined, Match user on From: user name and peer on IP/port. This is used on first + * invite (not re-invites) and subscribe requests */ +int sip_dialog_handle_authorization(struct sip_dialog *dialog, struct sip_message *request, int reliable) +{ + char *from, *name, *user, *domain, *parameters; + RAII_VAR(struct sip_peer *, peer, NULL, ao2_cleanup); + int res; + + if (ast_strlen_zero(dialog->to_user)) { + char *uri, *user; + + uri = ast_strdupa(request->uri); + + if (sip_parse_uri(uri, NULL, &user, NULL, NULL, NULL)) { + ast_log(LOG_WARNING, "Invalid request URI '%s' from %s\n", + request->uri, ast_sockaddr_stringify(&dialog->socket.address)); + return SIP_AUTHORIZATION_DONT_KNOW; + } + + ast_string_field_set(dialog, to_user, user); + + if (ast_strlen_zero(dialog->our_contact)) { + sip_dialog_build_contact(dialog, request); + } + } + + from = ast_strdupa(sip_message_find_header(request, "From")); + + if (sip_parse_contact(from, &name, &user, &domain, ¶meters, NULL)) { + ast_log(LOG_WARNING, "Invalid From: header '%s' from %s\n", + sip_message_find_header(request, "From"), ast_sockaddr_stringify(&dialog->socket.address)); + return SIP_AUTHORIZATION_DONT_KNOW; + } + + ast_string_field_build(dialog, from, "%s%s%s%s%s", user, !ast_strlen_zero(user) ? "@" : "", domain, + !ast_strlen_zero(parameters) ? ";" : "", parameters); + domain = strsep(&domain, ":"); /* Remove port number */ + + if (ast_strlen_zero(user) || ast_strlen_zero(domain)) { + return SIP_AUTHORIZATION_DONT_KNOW; + } + + if (sip_config.shrink_callerid && ast_is_shrinkable_phonenumber(user)) { + ast_shrink_phone_number(user); + } + + ast_string_field_set(dialog, caller_name, name); + ast_string_field_set(dialog, caller_number, user); + + if (sip_config.match_authorization_username) { + /* This is experimental code to grab the search key from the Authorization header username instead of + * the 'From' name, if available. Do not enable this block unless you understand the side effects (if + * any!) Note, the search for "username" should be done in a more robust way. Note, at the moment we + * check both fields, though maybe we should pick one or another depending on the request? */ + char *authorization = ast_strdupa(sip_message_find_header(request, "Authorization")); + + if (ast_strlen_zero(authorization)) { + authorization = ast_strdupa(sip_message_find_header(request, "Proxy-Authorization")); + } + + if (!strncmp(authorization, "Digest ", 7)) { + char *username; + + authorization += 7; + sip_parse_parameters(authorization, ',', "username", &username, NULL); + + if (!ast_strlen_zero(username)) { + user = username; + } + } + } + + /* First find devices based on username */ + if (!(peer = sip_peer_find(user, TRUE, FALSE))) { + /* Then find devices based on IP */ + peer = sip_peer_address_find(&dialog->socket.address, dialog->socket.transport, TRUE, FALSE); + } + + if (!peer) { + ast_debug(1, "No matching peer for '%s' from '%s'\n", + user, ast_sockaddr_stringify(&dialog->socket.address)); + + /* If you don't mind, we can return 404s for devices that do not exist: username disclosure */ + if (!sip_config.always_send_unauthorized) { + return SIP_AUTHORIZATION_NOT_FOUND; + } + + /* If you do mind, we use a peer that will never authenticate. This ensures that we follow the same code + * path as regular auth: less chance for username disclosure */ + peer = ao2_bump(sip_invalid_peer); + } + + if (ast_apply_acl(peer->address_acl, &dialog->socket.address, NULL) != AST_SENSE_ALLOW) { + ast_verb(3, "Authorization for SIP peer '%s' denied by address ACL for '%s'\n", + peer->name, ast_sockaddr_stringify(&dialog->socket.address)); + return SIP_AUTHORIZATION_ACL_FAILED; + } + + if (peer != sip_invalid_peer) { + ast_debug(1, "Found peer '%s' for '%s' from %s\n", + peer->name, name, ast_sockaddr_stringify(&dialog->socket.address)); + } + + if ((res = sip_dialog_check_authorization(dialog, peer, request, reliable)) != SIP_AUTHORIZATION_SUCCESS) { + return res; + } + + ao2_cleanup(dialog->peer); /* Should be NULL */ + dialog->peer = ao2_bump(peer); + request->authenticated = TRUE; /* Disable TCP/TLS timeout */ + + sip_dialog_get_peer_config(dialog, peer); + + if (request->method == SIP_METHOD_INVITE) { + /* Destroy old channel vars and copy in new ones */ + ast_variables_destroy(dialog->channel_variables); + + if (peer->channel_variables) { + dialog->channel_variables = ast_variables_dup(peer->channel_variables); + } else { + dialog->channel_variables = NULL; + } + } + + ast_format_cap_remove_by_type(dialog->format_cap, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(dialog->format_cap, peer->format_cap, AST_MEDIA_TYPE_UNKNOWN); + + ast_format_cap_remove_by_type(dialog->joint_format_cap, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(dialog->joint_format_cap, peer->format_cap, AST_MEDIA_TYPE_UNKNOWN); + + if (ast_format_cap_count(dialog->remote_format_cap)) { + struct ast_format_cap *joint_format_cap; + + if ((joint_format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + ast_format_cap_get_compatible(dialog->joint_format_cap, + dialog->remote_format_cap, joint_format_cap); + ao2_ref(dialog->joint_format_cap, -1); + dialog->joint_format_cap = joint_format_cap; + } + } + + dialog->joint_non_format_cap = dialog->non_format_cap; + + if (sip_dialog_alloc_rtp(dialog)) { + return SIP_AUTHORIZATION_RTP_FAILED; + } + + if (dialog->peer->dtmf_mode == SIP_DTMF_MODE_RFC2833) { + dialog->non_format_cap |= AST_RTP_DTMF; + } else { + dialog->non_format_cap &= ~AST_RTP_DTMF; + } + + if (dialog->nat_detected && peer->nat_auto_rport) { + ast_sockaddr_copy(&peer->address, &dialog->socket.address); + } + + sip_dialog_set_rtp_nat(dialog); + /* 'sip_peer_build' called through sip_peer_find, is not able to check the dialog->nat_detected flag in order to + * determine if the peer is behind NAT or not when nat_auto_rport or nat_auto_comedia are set on the peer. So we + * check for that here and set the peer's address accordingly. The address should ONLY be set once we are sure + * authentication was a success. If, for example, an INVITE was sent that matched the peer name but failed the + * authentication check, the address would be updated, which is bad */ + sip_peer_set_auto_nat(peer, dialog->nat_detected); + + if (!sip_parse_identity(dialog, request)) { + if (!ast_strlen_zero(peer->caller_number)) { + char *caller_number = ast_strdupa(peer->caller_number); + + if (sip_config.shrink_callerid && ast_is_shrinkable_phonenumber(caller_number)) { + ast_shrink_phone_number(caller_number); + } + + ast_string_field_set(dialog, caller_number, caller_number); + } + + if (!ast_strlen_zero(peer->caller_name)) { + ast_string_field_set(dialog, caller_name, peer->caller_name); + } + + dialog->caller_presentation = peer->caller_presentation; + } + + return SIP_AUTHORIZATION_SUCCESS; +} + +/* Verify registration of peer. Registration is done in several steps, first a REGISTER without auth to get a challenge + * (nonce) then a second one with auth. Registration requests are only matched with peers that are marked as "dynamic" */ +int sip_dialog_handle_registration(struct sip_dialog *dialog, struct sip_message *request) +{ + char *to, *user, *domain, *contact, *headers; + int address_changed, res; + RAII_VAR(struct sip_peer *, peer, NULL, ao2_cleanup); + + to = ast_strdupa(sip_message_find_header(request, "To")); + + if (sip_parse_contact(to, NULL, &user, &domain, NULL, NULL)) { + ast_log(LOG_NOTICE, "Invalid To: header '%s' from %s\n", + sip_message_find_header(request, "To"), ast_sockaddr_stringify_addr(&dialog->socket.address)); + + sip_response_send(dialog, "416 Unsupported URI Scheme", request); + return SIP_AUTHORIZATION_DONT_KNOW; + } else { + ast_string_field_set(dialog, to_user, user); + } + + domain = strsep(&domain, ":"); + + if (ast_strlen_zero(user) || ast_strlen_zero(domain)) { + sip_response_send(dialog, "404 Not Found", request); + return SIP_AUTHORIZATION_UNKNOWN_DOMAIN; + } + + if (ao2_container_count(sip_domains) && !sip_domain_check(domain, NULL, 0)) { + ast_verb(3, "SIP registration for '%s' denied due to unknown domain '%s' from '%s'\n", + user, domain, ast_sockaddr_stringify(&dialog->socket.address)); + + if (sip_config.always_send_unauthorized) { + sip_response_send_with_fake_authorization(dialog, request); + } else { + sip_response_send(dialog, "404 Not Found", request); + } + + return SIP_AUTHORIZATION_UNKNOWN_DOMAIN; + } + + contact = ast_strdupa(sip_message_find_header(request, "Contact")); + + if (!ast_strlen_zero(contact)) { + char *expires, *keep_alive; + + if (sip_parse_contact(contact, NULL, NULL, NULL, NULL, &headers)) { + ast_log(LOG_NOTICE, "Invalid Contact: header '%s' from %s\n", + sip_message_find_header(request, "Contact"), + ast_sockaddr_stringify_addr(&dialog->socket.address)); + + sip_response_send(dialog, "416 Unsupported URI Scheme", request); + return SIP_AUTHORIZATION_DONT_KNOW; + } + + /* Cisco failover */ + sip_parse_parameters(headers, ';', "expires", &expires, "cisco-keep-alive", &keep_alive, NULL); + + if (!atoi(expires) && !ast_strlen_zero(keep_alive)) { + sip_response_send_with_date(dialog, "200 OK", request); + return SIP_AUTHORIZATION_SUCCESS; + } + } + + sip_dialog_build_contact(dialog, request); + peer = sip_peer_find(user, TRUE, FALSE); + + /* If we don't want username disclosure, use the sip_invalid_peer when a user is not found */ + if (!peer && sip_config.always_send_unauthorized) { + peer = ao2_bump(sip_invalid_peer); + } + + if (peer && ast_apply_acl(peer->address_acl, &dialog->socket.address, NULL) != AST_SENSE_ALLOW) { + ast_verb(3, "Registration for SIP peer '%s' denied by address ACL for '%s'\n", + peer->name, ast_sockaddr_stringify(&dialog->socket.address)); + res = SIP_AUTHORIZATION_ACL_FAILED; + } else if (!peer) { + ast_verb(3, "No SIP peer '%s' found for registration from '%s'\n", + user, ast_sockaddr_stringify(&dialog->socket.address)); + res = SIP_AUTHORIZATION_NOT_FOUND; + } else { + ao2_lock(peer); + + if (!peer->host_dynamic) { + ast_verb(3, "SIP peer '%s' is trying to register but it is not configured as host=dynamic\n", + peer->name); + res = SIP_AUTHORIZATION_PEER_NOT_DYNAMIC; + } else if (sip_dialog_check_transport(dialog, peer)) { + dialog->pending_bye = TRUE; + res = SIP_AUTHORIZATION_INVALID_TRANSPORT; + } else if ((res = sip_dialog_check_authorization(dialog, peer, request, SIP_SEND_UNRELIABLE)) == SIP_AUTHORIZATION_SUCCESS) { + ao2_cleanup(dialog->peer); + + dialog->peer = ao2_bump(peer); + request->authenticated = TRUE; /* Disable TCP/TLS timeout */ + + sip_dialog_get_peer_config(dialog, peer); + + /* We have a successful registration attempt with proper authentication, now, update the peer */ + if (!sip_peer_register(peer, dialog, request, &address_changed)) { + ast_string_field_set(dialog, contact, peer->contact); + + if (sip_config.realtime_update_peer && (peer->realtime || peer->realtime_cache_peer)) { + sip_realtime_update(peer); + } + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + + if (dialog->expires && peer->cisco_mode && address_changed) { + sip_response_send_with_optionsind(dialog, request); + + /* We only need to do an update if the peer address has changed */ + sip_peer_update_subscriptions(peer); + sip_peer_update_aliases(peer); + + ao2_unlock(dialog); + + sip_peer_send_bulk_update(peer); + sip_peer_send_qualify(peer, FALSE); + + ao2_lock(dialog); + } else { + sip_response_send_with_date(dialog, "200 OK", request); + + if (dialog->expires) { + ao2_unlock(dialog); + + sip_peer_send_mwi(peer, FALSE); + sip_peer_send_qualify(peer, FALSE); + + ao2_lock(dialog); + } else { + sip_peer_set_messages(peer, 0, 0, FALSE); + } + } + } + } + + ao2_unlock(peer); + } + + if (res != SIP_AUTHORIZATION_SUCCESS && res != SIP_AUTHORIZATION_CHALLENGE_SENT) { + if (res == SIP_AUTHORIZATION_USERNAME_MISMATCH || res == SIP_AUTHORIZATION_NOT_FOUND) { + if (sip_config.always_send_unauthorized) { + sip_response_send_with_fake_authorization(dialog, request); + } else { + sip_response_send(dialog, "404 Not Found", request); + } + } else if (res == SIP_AUTHORIZATION_ACL_FAILED) { + sip_response_send_with_date(dialog, "603 Decline", request); + } else { + sip_response_send_with_date(dialog, "403 Forbidden", request); + } + + if (peer && peer->endpoint && sip_config.authentication_failure_events) { + char *peer_host, *peer_port, *cause; + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + + peer_host = ast_strdupa(ast_sockaddr_stringify_addr(&dialog->socket.address)); + peer_port = ast_strdupa(ast_sockaddr_stringify_port(&dialog->socket.address)); + + switch (res) { + case SIP_AUTHORIZATION_SECRET_FAILED: + cause = "SIP_AUTHORIZATION_SECRET_FAILED"; + break; + case SIP_AUTHORIZATION_PEER_NOT_DYNAMIC: + cause = "SIP_AUTHORIZATION_PEER_NOT_DYNAMIC"; + break; + case SIP_AUTHORIZATION_INVALID_TRANSPORT: + cause = "SIP_AUTHORIZATION_INVALID_TRANSPORT"; + break; + case SIP_AUTHORIZATION_ACL_FAILED: + cause = "SIP_AUTHORIZATION_ACL_FAILED"; + break; + case SIP_AUTHORIZATION_USERNAME_MISMATCH: + cause = "SIP_AUTHORIZATION_USERNAME_MISMATCH"; + break; + default: + cause = "URI_NOT_FOUND"; + break; + } + + blob = ast_json_pack("{s: s, s: s, s: s, s: s}", + "peer_status", "Rejected", "cause", cause, "address", peer_host, "port", peer_port); + ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); + } + } + + return res; +} + +/* Determine if a correct transport is being used to contact a peer */ +static int sip_dialog_check_transport(struct sip_dialog *dialog, struct sip_peer *peer) +{ + if (peer->socket.transport == dialog->socket.transport) { + return 0; + } + + if (!(peer->transports & dialog->socket.transport)) { + ast_verb(3, "'%s' is not a valid transport for SIP peer '%s'\n", + ast_transport2str(dialog->socket.transport), peer->name); + return -1; + } + + if (peer->socket.transport & AST_TRANSPORT_TLS) { + ast_verb(3, "SIP peer '%s' has not used TLS in favor of '%s' but this is allowed\n", + peer->name, ast_transport2str(dialog->socket.transport)); + } else { + ast_debug(1, "SIP peer '%s' has connected using '%s' even though we prefer '%s'\n", + peer->name, ast_transport2str(dialog->socket.transport), + ast_transport2str(peer->socket.transport)); + } + + return 0; +} + +/* Add authentication on outbound SIP packet */ +int sip_dialog_handle_authentication(struct sip_dialog *dialog, struct sip_message *response) +{ + int res; + + if (dialog->sent_authorization) { + ast_debug(2, "Failed to authenticate %s with host '%s'\n", + sip_method2str(response->method), ast_sockaddr_stringify(&dialog->address)); + return -1; + } + + ast_debug(2, "Sending authentication for %s\n", sip_method2str(response->method)); + + if (sip_dialog_parse_authorization(dialog, response)) { + /* No way to authenticate */ + return -1; + } + + dialog->authorization_code = response->code; + dialog->sent_authorization = TRUE; + + /* Now we have a reply digest */ + switch (response->method) { + case SIP_METHOD_REGISTER: + res = sip_request_send_register(dialog, TRUE); + break; + case SIP_METHOD_INVITE: + res = sip_request_send_invite(dialog, TRUE, SIP_INIT_BRANCH, NULL); + break; + case SIP_METHOD_UPDATE: + res = sip_request_send_update(dialog); + break; + case SIP_METHOD_BYE: + res = sip_request_send_bye(dialog); + break; + case SIP_METHOD_SUBSCRIBE: + res = sip_request_send_subscribe(dialog, SIP_INIT_NONE); + break; + case SIP_METHOD_REFER: + res = sip_request_send_refer(dialog, SIP_INIT_NONE); + break; + case SIP_METHOD_NOTIFY: + res = sip_request_send_notify(dialog, SIP_INIT_NONE); + break; + case SIP_METHOD_OPTIONS: + res = sip_request_send_options(dialog); + break; + case SIP_METHOD_MESSAGE: + res = sip_request_send_message(dialog, SIP_INIT_NONE); + break; + default: + ast_log(LOG_WARNING, "Unable to authorize %s request for '%s'\n", + sip_method2str(response->method), dialog->call_id); + res = -1; + break; + } + + return res; +} + +int sip_dialog_need_reinvite(const void *data) +{ + struct sip_dialog *dialog; + struct ast_channel *channel; + + dialog = (struct sip_dialog *) data; + channel = sip_dialog_lock_with_channel(dialog); + + dialog->need_reinvite = TRUE; + dialog->need_reinvite_sched_id = -1; + + sip_dialog_check_pending(dialog); + ao2_unlock(dialog); + + if (channel) { + ast_channel_unlock(channel); + ast_channel_unref(channel); + } + + ao2_ref(dialog, -1); + return 0; +} + +/* Reset the need_reinvite flag after waiting when we get 491 on a Re-invite to avoid race conditions between Asterisk + * servers */ +void sip_dialog_start_need_reinvite(struct sip_dialog *dialog) +{ + int when; + + /* This is a re-invite that failed. Reset the flag after a while */ + /* RFC 3261, if owner of call, wait between 2.1 to 4 seconds, if not owner of call, wait 0 to 2 seconds */ + if (dialog->originated_call) { + when = 2100 + (ast_random() % 1900); + } else { + when = ast_random() % 2000; + } + + if ((dialog->need_reinvite_sched_id = ast_sched_add(sip_sched_context, when, sip_dialog_need_reinvite, + ao2_bump(dialog)) == -1)) { + ao2_ref(dialog, -1); + } + + ast_debug(2, "Reinvite race. Scheduled reinvite retry for '%s' in %dms'\n", dialog->call_id, when); +} + +/* Run by the sched thread */ +static int __sip_dialog_stop_need_reinvite(const void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->need_reinvite_sched_id, ao2_cleanup(dialog)); + ao2_ref(dialog, -1); + return 0; +} + +void sip_dialog_stop_need_reinvite(struct sip_dialog *dialog) +{ + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_stop_need_reinvite, ao2_bump(dialog)) == -1) { + ao2_ref(dialog, -1); + } +} + +/* Run by the sched thread */ +int sip_dialog_reinvite_timeout(const void *data) +{ + struct sip_dialog *dialog; + struct ast_channel *channel; + + dialog = (struct sip_dialog *) data; + channel = sip_dialog_lock_with_channel(dialog); + dialog->reinvite_sched_id = -1; + + sip_dialog_check_pending(dialog); + + if (channel) { + ast_channel_unlock(channel); + ast_channel_unref(channel); + } + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + return 0; +} + +void sip_dialog_start_reinvite(struct sip_dialog *dialog) +{ + if ((dialog->reinvite_sched_id = ast_sched_add(sip_sched_context, dialog->timer_b, + sip_dialog_reinvite_timeout, ao2_bump(dialog))) == -1) { + ao2_ref(dialog, -1); + } +} + +/* Run by the sched thread */ +static int __sip_dialog_stop_reinvite(const void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->reinvite_sched_id, ao2_cleanup(dialog)); + ao2_ref(dialog, -1); + return 0; +} + +void sip_dialog_stop_reinvite(struct sip_dialog *dialog) +{ + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_stop_reinvite, ao2_bump(dialog)) == -1) { + ao2_ref(dialog, -1); + } +} + +/* Run by the sched thread */ +static int sip_dialog_provisional_keepalive(const void *data) +{ + struct sip_dialog *dialog; + const char *status_line; + + dialog = (struct sip_dialog *) data; + + ao2_lock(dialog); + + if (!strncmp(dialog->provisional_status_line, "100", 3)) { + status_line = "183 Session Progress"; + } else { + status_line = dialog->provisional_status_line; + } + + if (dialog->invite_state < SIP_INVITE_COMPLETED) { + if (dialog->provisional_keepalive_sdp) { + dialog->provisional_keepalive_sdp = FALSE; + sip_response_send_with_sdp(dialog, status_line, &dialog->initial_request, SIP_SEND_UNRELIABLE, + FALSE, FALSE); + } else { + sip_response_send(dialog, status_line, &dialog->initial_request); + } + } else { + dialog->provisional_keepalive_sched_id = -1; + } + + ao2_unlock(dialog); + + if (dialog->provisional_keepalive_sched_id == -1) { + ao2_ref(dialog, -1); + return FALSE; + } + + return TRUE; /* Reschedule */ +} + +/* Run by the sched thread */ +static int __sip_dialog_sched_provisional_keepalive(const void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->provisional_keepalive_sched_id, ao2_cleanup(dialog)); + ao2_lock(dialog); + + if (dialog->invite_state < SIP_INVITE_COMPLETED) { + /* Provisional keepalive is still needed */ + if ((dialog->provisional_keepalive_sched_id = ast_sched_add(sip_sched_context, dialog->timer_b, + sip_dialog_provisional_keepalive, ao2_bump(dialog))) == -1) { + ao2_ref(dialog, -1); + } + } + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + return 0; +} + +void sip_dialog_sched_provisional_keepalive(struct sip_dialog *dialog, int with_sdp) +{ + dialog->provisional_keepalive_sdp = with_sdp; + + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_sched_provisional_keepalive, ao2_bump(dialog)) == -1) { + ao2_ref(dialog, -1); + } +} + +/* Run by the sched thread */ +static int __sip_dialog_cancel_provisional_keepalive(const void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->provisional_keepalive_sched_id, ao2_cleanup(dialog)); + ao2_ref(dialog, -1); + return 0; +} + +void sip_dialog_cancel_provisional_keepalive(struct sip_dialog *dialog) +{ + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_cancel_provisional_keepalive, ao2_bump(dialog)) == -1) { + ao2_ref(dialog, -1); + } +} + +void sip_extension_state_destroy(int state_id, void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + ao2_ref(dialog, -1); +} + +int sip_extension_state_event(const char *context, const char *exten, struct ast_state_cb_info *state_info, void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + ao2_lock(dialog); + + switch (state_info->exten_state) { + case AST_EXTENSION_DEACTIVATED: /* Retry after a while */ + case AST_EXTENSION_REMOVED: /* Extension is gone */ + dialog->subscribe_event = SIP_SUBSCRIBE_NONE; + sip_dialog_sched_destroy(dialog, dialog->timer_b); /* Delete subscription in 32 secs */ + + ast_debug(2, "Extension '%s@%s' %s for peer '%s'\n", dialog->to_user, dialog->peer->context, + state_info->exten_state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", + dialog->peer->name); + break; + default: + if (dialog->force_state_change) { + dialog->force_state_change = FALSE; + /* We must skip the next two checks for a queued state change or resubscribe */ + } else if ((dialog->last_exten_state == state_info->exten_state && + (~state_info->exten_state & AST_EXTENSION_RINGING)) && + (dialog->last_presence_state == state_info->presence_state && + !strcmp(dialog->last_presence_subtype, S_OR(state_info->presence_subtype, "")) && + !strcmp(dialog->last_presence_message, S_OR(state_info->presence_message, "")))) { + /* Don't notify unchanged state or unchanged early-state causing parties again */ + ao2_unlock(dialog); + return 0; + } else if (state_info->exten_state & AST_EXTENSION_RINGING) { + /* Check if another channel than last time is ringing now to be notified */ + RAII_VAR(struct ast_channel *, channel, NULL, ast_channel_cleanup); + + if ((channel = sip_find_ringing_channel(state_info->device_state_info))) { + struct timeval ringing_time = ast_channel_creationtime(channel); + + if (!ast_tvcmp(ringing_time, dialog->last_ringing_time)) { + /* We assume here that no two channels have the exact same creation time */ + ao2_unlock(dialog); + return 0; + } + + dialog->last_ringing_time = ringing_time; + } + + /* If no ringing channel was found, it doesn't necessarily indicate anything bad. Likely, a + * device state change occurred for a custom device state, which does not correspond to any + * channel. In such a case, just go ahead and pass the notification along */ + } + + /* Ref before unref because the new could be the same as the old one. Don't risk destruction! */ + if (state_info->device_state_info) { + ao2_ref(state_info->device_state_info, +1); + } + + ao2_cleanup(dialog->last_device_state_info); + + dialog->last_exten_state = state_info->exten_state; + dialog->last_device_state_info = state_info->device_state_info; + dialog->last_presence_state = state_info->presence_state; + + ast_string_field_set(dialog, last_presence_subtype, state_info->presence_subtype); + ast_string_field_set(dialog, last_presence_message, state_info->presence_message); + break; + } + + if (dialog->subscribe_event) { /* Only send state NOTIFY if we know the format */ + if (!dialog->pending_invite_cseq) { + sip_request_send_notify_with_extension_state(dialog, state_info, FALSE); + + if (dialog->last_device_state_info) { + /* We don't need the saved ref anymore, don't keep channels ref'd */ + ao2_ref(dialog->last_device_state_info, -1); + dialog->last_device_state_info = NULL; + } + } else { + /* We already have a NOTIFY sent that is not answered. Queue the state up. + * if many state changes happen meanwhile, we will only send a notification of the last one */ + dialog->queued_state_change = TRUE; + } + } + + ast_debug(2, "Extension '%s@%s' changed to '%s' for peer '%s'%s\n", + dialog->to_user, dialog->peer->context, ast_extension_state2str(state_info->exten_state), + dialog->peer->name, dialog->queued_state_change ? ", queued" : ""); + ao2_unlock(dialog); + return 0; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/dialplan_applications.c asterisk-22.6.0/channels/sip/dialplan_applications.c --- asterisk-22.6.0.orig/channels/sip/dialplan_applications.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/dialplan_applications.c 2025-10-21 18:38:17.442218359 +1300 @@ -0,0 +1,685 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/channel.h" +#include "asterisk/indications.h" +#include "asterisk/format_cache.h" +#include "asterisk/rtp_engine.h" +#include "asterisk/pbx.h" +#include "asterisk/app.h" +#include "asterisk/acl.h" +#include "asterisk/message.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/domains.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/channel_tech.h" +#include "include/dialplan_applications.h" + +/*** DOCUMENTATION + + + Add a SIP header to the outbound call. + + + + + + + Adds a header to a SIP call placed with DIAL. + Remember to use the X-header if you are adding non-standard SIP + headers, like X-Asterisk-Accountcode:. Use this with care. + Adding the wrong headers may jeopardize the SIP dialog. + Always returns 0. + + + + + Remove SIP headers previously added with SIPAddHeader + + + + + + SIPRemoveHeader() allows you to remove headers which were previously + added with SIPAddHeader(). If no parameter is supplied, all previously added + headers will be removed. If a parameter is supplied, only the matching headers + will be removed. + + same => next,SIPAddHeader(P-Asserted-Identity: <sip:foo@bar>) + same => next,SIPAddHeader(P-Preferred-Identity: <sip:bar@foo>) + + + same => next,SIPRemoveHeader() + + + same => next,SIPRemoveHeader(P-) + + + same => next,SIPRemoveHeader(P-Asserted-Identity:) + + Always returns 0. + + + + + Page a series of Cisco phones + + + + + Name of the SIP peer to page + + + Name of the second peer to page, additional peers are + specified as peer&peer2&peer3... + + + + + + + + + + + + + + + + + Using the RTP streaming API, send a request to the specified peers to + receive RTP audio. Supported codecs are G711 (mulaw and alaw), G722 and + G729a. RTP is transmitted as unicast unless the m() option is used. + + + ***/ + +/* Add a SIP header to an outbound INVITE */ +int sip_application_add_header(struct ast_channel *channel, const char *data) +{ + char name[30], *value; + int count; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "This application requires the argument: Header\n"); + return 0; + } + + ast_channel_lock(channel); + + /* Check for headers */ + for (count = 1; count <= 50; count++) { + snprintf(name, sizeof(name), "__SIP_ADD_HEADER%.2d", count); + + /* Compare without the leading underscores */ + if (!pbx_builtin_getvar_helper(channel, name + 2)) { + break; + } + } + + if (count <= 50) { + int value_len; + + value_len = strlen(data) + 1; + value = ast_alloca(value_len); + + ast_get_encoded_str(data, value, value_len); + pbx_builtin_setvar_helper(channel, name, value); + + ast_debug(1, "Adding SIP header '%s' as '%s'\n", data, name); + } else { + ast_log(LOG_WARNING, "Too many SIP headers added, max 50\n"); + } + + ast_channel_unlock(channel); + return 0; +} + +/* Remove SIP headers added previously with SipAddHeader application */ +int sip_application_remove_header(struct ast_channel *channel, const char *data) +{ + struct ast_var_t *var; + struct varshead *head; + int remove_all, data_len; + + if (!(data_len = strlen(data))) { + remove_all = TRUE; + } else { + remove_all = FALSE; + } + + ast_channel_lock(channel); + head = ast_channel_varshead(channel); + + AST_LIST_TRAVERSE_SAFE_BEGIN(head, var, entries) { + if (!strncmp(ast_var_name(var), "SIP_ADD_HEADER", 14)) { + if (remove_all || !strncasecmp(ast_var_value(var), data, data_len)) { + ast_debug(1, "Removing SIP header '%s' that was '%s'\n", + ast_var_name(var), ast_var_value(var)); + + AST_LIST_REMOVE_CURRENT(entries); + ast_var_delete(var); + } + } + } + + AST_LIST_TRAVERSE_SAFE_END; + ast_channel_unlock(channel); + return 0; +} + +enum sip_cisco_page_options { + APP_CISCO_PAGE_MULTICAST = 1 << 0, + APP_CISCO_PAGE_PORT = 1 << 1, + APP_CISCO_PAGE_VOLUME = 1 << 2, + APP_CISCO_PAGE_DISPLAY = 1 << 3, + APP_CISCO_PAGE_INCLUDE_BUSY = 1 << 4, + APP_CISCO_PAGE_OFFHOOK = 1 << 5, + APP_CISCO_PAGE_BEEP = 1 << 6, + APP_CISCO_PAGE_TIMER = 1 << 7, +}; + +enum sip_cisco_page_option_args { + APP_CISCO_PAGE_ARG_MULTICAST, + APP_CISCO_PAGE_ARG_PORT, + APP_CISCO_PAGE_ARG_VOLUME, + APP_CISCO_PAGE_ARG_DISPLAY, + APP_CISCO_PAGE_ARG_TIMER, + APP_CISCO_PAGE_ARG_ARRAY_SIZE, +}; + +AST_APP_OPTIONS(sip_application_ciscopage_options, BEGIN_OPTIONS + AST_APP_OPTION_ARG('m', APP_CISCO_PAGE_MULTICAST, APP_CISCO_PAGE_ARG_MULTICAST), + AST_APP_OPTION_ARG('p', APP_CISCO_PAGE_PORT, APP_CISCO_PAGE_ARG_PORT), + AST_APP_OPTION_ARG('v', APP_CISCO_PAGE_VOLUME, APP_CISCO_PAGE_ARG_VOLUME), + AST_APP_OPTION_ARG('d', APP_CISCO_PAGE_DISPLAY, APP_CISCO_PAGE_ARG_DISPLAY), + AST_APP_OPTION_ARG('t', APP_CISCO_PAGE_TIMER, APP_CISCO_PAGE_ARG_TIMER), + AST_APP_OPTION('b', APP_CISCO_PAGE_INCLUDE_BUSY), + AST_APP_OPTION('o', APP_CISCO_PAGE_OFFHOOK), + AST_APP_OPTION('a', APP_CISCO_PAGE_BEEP), +END_OPTIONS); + +struct sip_page_target { + struct sip_peer *peer; + struct ast_rtp_instance *rtp; + AST_LIST_ENTRY(sip_page_target) next; +}; + +/* Send direct RTP audio to Cisco phones */ +int sip_application_cisco_page(struct ast_channel *channel, const char *data) +{ + char *option_args[APP_CISCO_PAGE_ARG_ARRAY_SIZE]; + char *parse, *peer_name, *codec, display[64]; + int volume, port, include_busy, offhook, beep, timer; + struct ast_format *format; + AST_LIST_HEAD_NOLOCK(, sip_page_target) targets; + struct sip_page_target *target; + struct ast_str *content; + struct ast_rtp_instance *multicast_rtp; + struct ast_sockaddr multicast_address; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(peer_names); + AST_APP_ARG(options); + ); + struct ast_flags options; + int res; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "Cannot call SIPCiscoPage without arguments\n"); + return -1; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.peer_names)) { + ast_log(LOG_ERROR, "No peer names specified\n"); + return -1; + } + + memset(&options, 0, sizeof(options)); + + if (!ast_strlen_zero(args.options)) { + ast_app_parse_options(sip_application_ciscopage_options, &options, option_args, args.options); + } + + if (ast_test_flag(&options, APP_CISCO_PAGE_MULTICAST) && + !ast_strlen_zero(option_args[APP_CISCO_PAGE_ARG_MULTICAST])) { + if (!ast_sockaddr_parse(&multicast_address, option_args[APP_CISCO_PAGE_ARG_MULTICAST], PARSE_PORT_FORBID)) { + ast_log(LOG_ERROR, "Invalid IP address '%s'\n", option_args[APP_CISCO_PAGE_ARG_MULTICAST]); + return -1; + } + + if (!ast_sockaddr_is_ipv4_multicast(&multicast_address)) { + ast_log(LOG_ERROR, "IP address '%s' is not multicast\n", + option_args[APP_CISCO_PAGE_ARG_MULTICAST]); + return -1; + } + } else { + ast_sockaddr_setnull(&multicast_address); + } + + if (ast_test_flag(&options, APP_CISCO_PAGE_PORT) && !ast_strlen_zero(option_args[APP_CISCO_PAGE_ARG_PORT])) { + port = strtol(option_args[APP_CISCO_PAGE_ARG_PORT], NULL, 10); + + if (port < 20480 || port > 32768 || port % 2) { + ast_log(LOG_ERROR, "Invalid port '%s'\n", option_args[APP_CISCO_PAGE_ARG_PORT]); + return -1; + } + } else { + port = 20480; + } + + if (ast_test_flag(&options, APP_CISCO_PAGE_VOLUME) && + !ast_strlen_zero(option_args[APP_CISCO_PAGE_ARG_VOLUME])) { + volume = strtol(option_args[APP_CISCO_PAGE_ARG_VOLUME], NULL, 10); + + if (volume < 1 || volume > 100) { + ast_log(LOG_ERROR, "Invalid volume '%s'\n", option_args[APP_CISCO_PAGE_ARG_VOLUME]); + return -1; + } + } else { + volume = -1; + } + + if (ast_test_flag(&options, APP_CISCO_PAGE_DISPLAY) && + !ast_strlen_zero(option_args[APP_CISCO_PAGE_ARG_DISPLAY])) { + ast_xml_escape(option_args[APP_CISCO_PAGE_ARG_DISPLAY], display, sizeof(display)); + } else { + display[0] = '\0'; + } + + if (ast_test_flag(&options, APP_CISCO_PAGE_TIMER) && !ast_strlen_zero(option_args[APP_CISCO_PAGE_ARG_TIMER])) { + timer = strtol(option_args[APP_CISCO_PAGE_ARG_TIMER], NULL, 10); + + if (timer < 1 || timer > 60) { + ast_log(LOG_ERROR, "Invalid timer '%s'\n", option_args[APP_CISCO_PAGE_ARG_TIMER]); + return -1; + } + } else { + timer = 10; + } + + beep = !!ast_test_flag(&options, APP_CISCO_PAGE_BEEP); + include_busy = !!ast_test_flag(&options, APP_CISCO_PAGE_INCLUDE_BUSY); + offhook = !!ast_test_flag(&options, APP_CISCO_PAGE_OFFHOOK); + + format = ast_channel_readformat(channel); + + if (ast_format_cmp(format, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL || + ast_format_cmp(format, ast_format_alaw) == AST_FORMAT_CMP_EQUAL) { + codec = "G.711"; + } else if (ast_format_cmp(format, ast_format_g722) == AST_FORMAT_CMP_EQUAL) { + codec = "G.722"; + } else if (ast_format_cmp(format, ast_format_g729) == AST_FORMAT_CMP_EQUAL) { + codec = "G.729"; + } else { + ast_log(LOG_ERROR, "Unsupported codec format\n"); + return -1; + } + + multicast_rtp = NULL; + content = ast_str_alloca(4096); + + res = -1; + AST_LIST_HEAD_INIT_NOLOCK(&targets); + + while ((peer_name = strsep(&args.peer_names, "&"))) { + struct sip_peer *peer; + struct sip_dialog *dialog; + struct ast_rtp_instance *rtp; + struct ast_sockaddr local_address, remote_address; + + if (!(peer = sip_peer_find(peer_name, TRUE, FALSE))) { + ast_log(LOG_ERROR, "No such peer '%s'\n", peer_name); + continue; + } + + if (!peer->cisco_mode) { + ast_log(LOG_ERROR, "Peer '%s' does not have 'cisco=yes'\n", peer->name); + ao2_ref(peer, -1); + continue; + } + + if (ast_sockaddr_isnull(&peer->address)) { + ast_log(LOG_ERROR, "Peer '%s' is not registered\n", peer->name); + ao2_ref(peer, -1); + continue; + } + + if ((peer->offhook || peer->ringing || peer->inuse || peer->do_not_disturb) && !include_busy) { + ao2_ref(peer, -1); + continue; + } + + if (!ast_sockaddr_isnull(&multicast_address)) { + ast_sockaddr_copy(&local_address, &multicast_address); + rtp = NULL; + } else { + ast_ouraddrfor(&peer->address, &local_address); + ast_sockaddr_copy(&remote_address, &peer->address); + ast_sockaddr_set_port(&remote_address, port); + + if (!(rtp = ast_rtp_instance_new("asterisk", sip_sched_context, &local_address, NULL))) { + ao2_ref(peer, -1); + goto cleanup; + } + + ast_rtp_instance_set_write_format(rtp, ast_channel_readformat(channel)); + ast_rtp_instance_set_remote_address(rtp, &remote_address); + + ast_rtp_instance_set_qos(rtp, sip_config.tos_audio, sip_config.cos_audio, "SIP RTP"); + ast_rtp_instance_activate(rtp); + } + + if (!(dialog = sip_dialog_alloc(NULL, NULL, SIP_METHOD_REFER, NULL, 0))) { + ao2_ref(peer, -1); + ast_rtp_instance_destroy(rtp); + continue; + } + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + + ao2_ref(dialog, -1); + ao2_ref(peer, -1); + ast_rtp_instance_destroy(rtp); + continue; + } + + ast_str_reset(content); + + if (!ast_strlen_zero(display)) { + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n" + " notify_display\n"); + ast_str_append(&content, 0, " %s\n", display); + ast_str_append(&content, 0, " %d\n", timer); + ast_str_append(&content, 0, " 0\n" + " 1\n" + " \n" + "\n" + "\r\n"); + } + + if (beep) { + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n" + " DtZipZip\n" + " all\n" + " \n" + "\n" + "\r\n"); + } + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n" + " 0\n" + " 0\n" + " StationSequenceLast\n" + " 2\n" + " 0\n" + " 0\n" + " 0\n" + " \n" + "\n" + "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-cm+xml\r\n" + "\r\n" + "\n" + "\n"); + + if (volume != -1) { + ast_str_append(&content, 0, " \n", volume); + } else { + ast_str_append(&content, 0, " \n"); + } + + ast_str_append(&content, 0, " audio\n" + " %s\n", codec); + ast_str_append(&content, 0, " receive\n" + "
%s
\n", ast_sockaddr_stringify_addr(&local_address)); + ast_str_append(&content, 0, " %d\n", port); + ast_str_append(&content, 0, "
\n" + "
\n" + "\r\n" + "--uniqueBoundary--\r\n"); + + sip_request_send_refer_with_content(dialog, "multipart/mixed; boundary=uniqueBoundary", + ast_str_buffer(content)); + ao2_ref(dialog, -1); + + if (!(target = ast_calloc(1, sizeof(*target)))) { + ao2_ref(peer, -1); + ast_rtp_instance_destroy(rtp); + goto cleanup; + } + + if (offhook) { + ao2_lock(peer); + peer->offhook++; + ao2_unlock(peer); + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + } + + target->peer = peer; + target->rtp = rtp; + + AST_LIST_INSERT_TAIL(&targets, target, next); + } + + if (AST_LIST_EMPTY(&targets)) { + return -1; + } + + if (!ast_sockaddr_isnull(&multicast_address)) { + struct ast_sockaddr local_address; + + ast_sockaddr_set_port(&multicast_address, port); + + if (!ast_sockaddr_isnull(&sip_config.rtp_bind_address)) { + ast_sockaddr_copy(&local_address, &sip_config.rtp_bind_address); + } else { + ast_sockaddr_copy(&local_address, &sip_config.udp_bind_address); + } + + if (!(multicast_rtp = ast_rtp_instance_new("multicast", sip_sched_context, &local_address, "basic"))) { + goto cleanup; + } + + ast_rtp_instance_set_write_format(multicast_rtp, ast_channel_readformat(channel)); + ast_rtp_instance_set_remote_address(multicast_rtp, &multicast_address); + + ast_rtp_instance_set_qos(multicast_rtp, sip_config.tos_audio, sip_config.cos_audio, "SIP RTP"); + ast_rtp_instance_activate(multicast_rtp); + } + + if (ast_channel_state(channel) != AST_STATE_UP) { + if ((res = ast_answer(channel))) { + goto cleanup; + } + } + + /* Wait 500ms for phones to accept the media stream request */ + if ((res = ast_safe_sleep(channel, 500))) { + goto cleanup; + } + + for (;;) { + struct ast_frame *frame; + + if (ast_waitfor(channel, 10000) < 1) { + break; + } + + frame = ast_read(channel); + + if (!frame || (frame->frametype == AST_FRAME_CONTROL && frame->subclass.integer == AST_CONTROL_HANGUP)) { + ast_frfree(frame); + break; + } + + if (frame->frametype == AST_FRAME_VOICE) { + if (multicast_rtp) { + ast_rtp_instance_write(multicast_rtp, frame); + } else { + AST_LIST_TRAVERSE(&targets, target, next) { + ast_rtp_instance_write(target->rtp, frame); + } + } + } + + ast_frfree(frame); + } + + res = 0; + +cleanup: + while ((target = AST_LIST_REMOVE_HEAD(&targets, next))) { + struct sip_dialog *dialog; + + if ((dialog = sip_dialog_alloc(NULL, NULL, SIP_METHOD_REFER, NULL, 0))) { + if (sip_dialog_build_from_peer(dialog, target->peer)) { + sip_dialog_unlink(dialog); + ao2_ref(dialog, -1); + dialog = NULL; + } + } + + if (dialog) { + ast_str_reset(content); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n" + " 0\n" + " 0\n" + " StationSequenceLast\n" + " 2\n" + " 0\n" + " 0\n" + " 0\n" + " \n" + "\n" + "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-cm+xml\r\n" + "\r\n" + "\n" + "\n" + " \n" + "\n" + "\r\n" + "--uniqueBoundary--\r\n"); + + sip_request_send_refer_with_content(dialog, "multipart/mixed; boundary=uniqueBoundary", + ast_str_buffer(content)); + ao2_ref(dialog, -1); + } + + if (offhook) { + ao2_lock(target->peer); + target->peer->offhook--; + ao2_unlock(target->peer); + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", target->peer->name); + } + + ao2_ref(target->peer, -1); + ast_rtp_instance_destroy(target->rtp); + ast_free(target); + } + + ast_rtp_instance_destroy(multicast_rtp); + return res; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/dialplan_functions.c asterisk-22.6.0/channels/sip/dialplan_functions.c --- asterisk-22.6.0.orig/channels/sip/dialplan_functions.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/dialplan_functions.c 2025-10-21 18:38:17.443218332 +1300 @@ -0,0 +1,983 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +/*** DOCUMENTATION + + + + R/O Get the IP address of the peer. + + + R/O Get the source IP address of the peer. + + + R/O Get the source port of the peer. + + + R/O Get the URI from the From: header. + + + R/O Get the URI. + + + R/O Get the URI from the request. + + + R/O Get the useragent. + + + R/O Get the name of the peer. + + + R/O 1 if fax is offered or enabled in this channel, + otherwise 0 + + + R/O Get source RTP destination information. + This option takes one additional argument: + Argument 1: + audio Get audio destination + video Get video destination + text Get text destination + Defaults to audio if unspecified. + + + R/O Get remote RTP destination information. + This option takes one additional argument: + Argument 1: + audio Get audio destination + video Get video destination + text Get text destination + Defaults to audio if unspecified. + + + R/O Get QOS information about the RTP stream + This option takes two additional arguments: + Argument 1: + audio Get data about the audio stream + video Get data about the video stream + text Get data about the text stream + Argument 2: + local_ssrc Local SSRC (stream ID) + local_lostpackets Local lost packets + local_jitter Local calculated jitter + local_maxjitter Local calculated jitter (maximum) + local_minjitter Local calculated jitter (minimum) + local_normdevjitterLocal calculated jitter (normal deviation) + local_stdevjitter Local calculated jitter (standard deviation) + local_count Number of received packets + remote_ssrc Remote SSRC (stream ID) + remote_lostpacketsRemote lost packets + remote_jitter Remote reported jitter + remote_maxjitter Remote calculated jitter (maximum) + remote_minjitter Remote calculated jitter (minimum) + remote_normdevjitterRemote calculated jitter (normal deviation) + remote_stdevjitterRemote calculated jitter (standard deviation) + remote_count Number of transmitted packets + rtt Round trip time + maxrtt Round trip time (maximum) + minrtt Round trip time (minimum) + normdevrtt Round trip time (normal deviation) + stdevrtt Round trip time (standard deviation) + all All statistics (in a form suited to logging, + but not for parsing) + + + + + + Gets the specified SIP header from an incoming INVITE message. + + + + + If not specified, defaults to 1. + + + + Since there are several headers (such as Via) which can occur multiple + times, SIP_HEADER takes an optional second argument to specify which header with + that name to retrieve. Headers start at offset 1. + This function does not access headers from the REFER message if the call + was transferred. To obtain the REFER headers, set the dialplan variable + GET_TRANSFERRER_DATA to the prefix of the headers of the + REFER message that you need to access; for example, X- to + get all headers starting with X-. The variable must be set + before a call to the application that starts the channel that may eventually + transfer back into the dialplan, and must be inherited by that channel, so prefix + it with the _ or __ when setting (or + set it in the pre-dial handler executed on the new channel). To get all headers + of the REFER message, set the value to *. Headers + are returned in the form of a dialplan hash TRANSFER_DATA, and can be accessed + with the functions HASHKEYS(TRANSFER_DATA) and, e. g., + HASH(TRANSFER_DATA,X-That-Special-Header). + Please also note that contents of the SDP (an attachment to the + SIP request) can't be accessed with this function. + + + SIP_HEADERS + + + + + Gets the list of SIP header names from an incoming INVITE message. + + + + If specified, only the headers matching the given prefix are returned. + + + + Returns a comma-separated list of header names (without values) from the + INVITE message that originated the current channel. Multiple headers with the + same name are included in the list only once. The returned list can be iterated + over using the functions POP() and SIP_HEADER(). + For example, ${SIP_HEADERS(Co)} might return + Contact,Content-Length,Content-Type. As a practical example, + you may use ${SIP_HEADERS(X-)} to enumerate optional extended + headers. + This function does not access headers from the incoming SIP REFER message; + see the documentation of the function SIP_HEADER for how to access them. + Please observe that contents of the SDP (an attachment to the + SIP request) can't be accessed with this function. + + + SIP_HEADER + POP + + + + + Gets SIP peer information. + + + + + + + The IP address. + + + The port number. + + + The configured mailbox. + + + The configured context. + + + The epoch time of the next expire. + + + Is it dynamic? (yes/no). + + + The configured Caller ID name. + + + The configured Caller ID number. + + + The configured Callgroup. + + + The configured Pickupgroup. + + + The configured Named Callgroup. + + + The configured Named Pickupgroup. + + + The configured codecs. + + + Status (if qualify=yes). + + + Maximum number of calls. + + + Number of answered calls to signal as busy. + + + Number of answered calls + + + Number of ringing calls + + + Number of onhold calls + + + Default language for peer. + + + Account code for this peer. + + + Current user agent header used by peer. + + + The value used for SIP loop prevention in outbound requests + + + The channel variable named x configured + for this peer. + + + Preferred codec index number x + (beginning with zero). + + + The vmexten for this peer. + + + Is DoNotDisturb set on this peer (yes/no). + + + The call forwarding extension for this peer. + + + Is HuntGroup login set on this peer (yes/no). + + + The Call-ID of the REGISTER dialog. + + + The device name of the peer when ciscosupport is enabled + + + The line index of the peer when ciscosuppot is enabled + + + + + + Returns information about a SIP peer. + + + + + Deprecated alias for SIP_PEER function. + + + + + SIP_PEER + + + + + Checks if domain is a local domain. + + + + + + This function checks if the domain in the argument is configured + as a local SIP domain that this Asterisk server is configured to handle. + Returns the domain name if it is locally handled, otherwise an empty string. + Check the domain= configuration in sip.conf. + + + + + Deprecated alias for SIP_CHECK_DOMAIN function. + + + + + SIP_CHECK_DOMAIN + + + ***/ + +#include "asterisk.h" +#include "asterisk/channel.h" +#include "asterisk/indications.h" +#include "asterisk/rtp_engine.h" +#include "asterisk/pbx.h" +#include "asterisk/astdb.h" +#include "asterisk/acl.h" +#include "asterisk/app.h" +#include "asterisk/message.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/domains.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/config.h" +#include "include/channel_tech.h" +#include "include/dialplan_functions.h" +#include "include/fax.h" + +int sip_function_channel_read(struct ast_channel *channel, const char *function, char *data, char *buf, size_t buf_len) +{ + struct sip_dialog *dialog; + char *parse; + int res; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(option); + AST_APP_ARG(type); + AST_APP_ARG(field); + ); + + /* Check for zero arguments */ + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "Cannot call %s without arguments\n", function); + return -1; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + /* Sanity check */ + if (ast_channel_tech(channel) != &sip_channel_tech) { + ast_log(LOG_ERROR, "Cannot call %s on a non-SIP channel\n", function); + return 0; + } + + if (!(dialog = ast_channel_tech_pvt(channel))) { + return -1; + } + + res = 0; + memset(buf, 0, buf_len); + + if (!strcasecmp(args.option, "peerip") || !strcasecmp(args.option, "peeraddr")) { + if (strcasecmp(args.option, "peeraddr")) { + ast_log(LOG_WARNING, "Item '%s' is deprecated use '%s' instead\n", args.option, "peeraddr"); + } + + ast_copy_string(buf, + !ast_sockaddr_isnull(&dialog->address) ? ast_sockaddr_stringify_addr(&dialog->address) : "", + buf_len); + } else if (!strcasecmp(args.option, "recvip") || !strcasecmp(args.option, "recvaddr")) { + if (strcasecmp(args.option, "recvaddr")) { + ast_log(LOG_WARNING, "Item '%s' is deprecated use '%s' instead\n", args.option, "recvaddr"); + } + + ast_copy_string(buf, + !ast_sockaddr_isnull(&dialog->socket.address) ? ast_sockaddr_stringify_addr(&dialog->socket.address) : "", + buf_len); + } else if (!strcasecmp(args.option, "recvport")) { + ast_copy_string(buf, + !ast_sockaddr_isnull(&dialog->socket.address) ? ast_sockaddr_stringify_port(&dialog->socket.address) : "", + buf_len); + } else if (!strcasecmp(args.option, "from")) { + ast_copy_string(buf, dialog->from, buf_len); + } else if (!strcasecmp(args.option, "uri")) { + ast_copy_string(buf, dialog->uri, buf_len); + } else if (!strcasecmp(args.option, "ruri") || !strcasecmp(args.option, "requesturi")) { + if (strcasecmp(args.option, "requesturi")) { + ast_log(LOG_WARNING, "Item '%s' is deprecated use '%s' instead\n", args.option, "requesturi"); + } + + if (ast_strlen_zero(dialog->initial_request.uri)) { + return -1; + } + + ast_copy_string(buf, dialog->initial_request.uri, buf_len); + } else if (!strcasecmp(args.option, "useragent")) { + ast_copy_string(buf, dialog->useragent, buf_len); + } else if (!strcasecmp(args.option, "peername")) { + ast_copy_string(buf, dialog->peer->name, buf_len); + } else if (!strcasecmp(args.option, "t38passthrough") || !strcasecmp(args.option, "fax")) { + ast_copy_string(buf, dialog->fax_state == SIP_FAX_DISABLED ? "0" : "1", buf_len); + } else if (!strcasecmp(args.option, "rtpsource")) { + struct ast_sockaddr local_address; + struct ast_rtp_instance *rtp; + + if (ast_strlen_zero(args.type)) + args.type = "audio"; + + if (!strcasecmp(args.type, "audio")) { + rtp = dialog->audio_rtp; + } else if (!strcasecmp(args.type, "video")) { + rtp = dialog->video_rtp; + } else if (!strcasecmp(args.type, "text")) { + rtp = dialog->text_rtp; + } else { + return -1; + } + + /* Return 0 to suppress a console warning message */ + if (!rtp) { + return 0; + } + + ast_rtp_instance_get_local_address(rtp, &local_address); + + if (ast_sockaddr_isnull(&local_address)) { + struct ast_sockaddr remote_address; + + ast_rtp_instance_get_remote_address(rtp, &remote_address); + ast_ouraddrfor(&remote_address, &local_address); + } + + snprintf(buf, buf_len, "%s", ast_sockaddr_stringify(&local_address)); + } else if (!strcasecmp(args.option, "rtpdest") || !strcasecmp(args.option, "rtpdestination")) { + struct ast_sockaddr remote_address; + struct ast_rtp_instance *rtp; + + if (strcasecmp(args.option, "rtpdestination")) { + ast_log(LOG_WARNING, "Item '%s' is deprecated use '%s' instead\n", args.option, "rtpdestination"); + } + + if (ast_strlen_zero(args.type)) { + args.type = "audio"; + } + + if (!strcasecmp(args.type, "audio")) { + rtp = dialog->audio_rtp; + } else if (!strcasecmp(args.type, "video")) { + rtp = dialog->video_rtp; + } else if (!strcasecmp(args.type, "text")) { + rtp = dialog->text_rtp; + } else { + ast_log(LOG_WARNING, "Unrecognized argument '%s' to %s\n", data, function); + return -1; + } + + /* Return 0 to suppress a console warning message */ + if (!rtp) { + return 0; + } + + ast_rtp_instance_get_remote_address(rtp, &remote_address); + snprintf(buf, buf_len, "%s", ast_sockaddr_stringify(&remote_address)); + } else if (!strcasecmp(args.option, "rtpqos")) { + struct ast_rtp_instance *rtp; + + if (ast_strlen_zero(args.type)) { + args.type = "audio"; + } + + if (!strcasecmp(args.type, "audio")) { + rtp = dialog->audio_rtp; + } else if (!strcasecmp(args.type, "video")) { + rtp = dialog->video_rtp; + } else if (!strcasecmp(args.type, "text")) { + rtp = dialog->text_rtp; + } else { + ast_log(LOG_WARNING, "Unrecognized argument '%s' to %s\n", data, function); + return -1; + } + + if (ast_strlen_zero(args.field) || !strcasecmp(args.field, "all")) { + char quality[AST_MAX_USER_FIELD]; + + if (!ast_rtp_instance_get_quality(rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, + quality, sizeof(quality))) { + return -1; + } + + ast_copy_string(buf, quality, buf_len); + return res; + } else { + struct ast_rtp_instance_stats stats; + + if (ast_rtp_instance_get_stats(rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) { + return -1; + } + + if (!strcasecmp(args.field, "txcount")) { + snprintf(buf, buf_len, "%d", stats.txcount); + } else if (!strcasecmp(args.field, "rxcount")) { + snprintf(buf, buf_len, "%d", stats.rxcount); + } else if (!strcasecmp(args.field, "txjitter")) { + snprintf(buf, buf_len, "%f", stats.txjitter); + } else if (!strcasecmp(args.field, "rxjitter")) { + snprintf(buf, buf_len, "%f", stats.rxjitter); + } else if (!strcasecmp(args.field, "remote_maxjitter")) { + snprintf(buf, buf_len, "%f", stats.remote_maxjitter); + } else if (!strcasecmp(args.field, "remote_minjitter")) { + snprintf(buf, buf_len, "%f", stats.remote_minjitter); + } else if (!strcasecmp(args.field, "remote_normdevjitter")) { + snprintf(buf, buf_len, "%f", stats.remote_normdevjitter); + } else if (!strcasecmp(args.field, "remote_stdevjitter")) { + snprintf(buf, buf_len, "%f", stats.remote_stdevjitter); + } else if (!strcasecmp(args.field, "local_maxjitter")) { + snprintf(buf, buf_len, "%f", stats.local_maxjitter); + } else if (!strcasecmp(args.field, "local_minjitter")) { + snprintf(buf, buf_len, "%f", stats.local_minjitter); + } else if (!strcasecmp(args.field, "local_normdevjitter")) { + snprintf(buf, buf_len, "%f", stats.local_normdevjitter); + } else if (!strcasecmp(args.field, "local_stdevjitter")) { + snprintf(buf, buf_len, "%f", stats.local_stdevjitter); + } else if (!strcasecmp(args.field, "txploss")) { + snprintf(buf, buf_len, "%d", stats.txploss); + } else if (!strcasecmp(args.field, "rxploss")) { + snprintf(buf, buf_len, "%d", stats.rxploss); + } else if (!strcasecmp(args.field, "remote_maxrxploss")) { + snprintf(buf, buf_len, "%f", stats.remote_maxrxploss); + } else if (!strcasecmp(args.field, "remote_minrxploss")) { + snprintf(buf, buf_len, "%f", stats.remote_minrxploss); + } else if (!strcasecmp(args.field, "remote_normdevrxploss")) { + snprintf(buf, buf_len, "%f", stats.remote_normdevrxploss); + } else if (!strcasecmp(args.field, "remote_stdevrxploss")) { + snprintf(buf, buf_len, "%f", stats.remote_stdevrxploss); + } else if (!strcasecmp(args.field, "local_maxrxploss")) { + snprintf(buf, buf_len, "%f", stats.local_maxrxploss); + } else if (!strcasecmp(args.field, "local_minrxploss")) { + snprintf(buf, buf_len, "%f", stats.local_minrxploss); + } else if (!strcasecmp(args.field, "local_normdevrxploss")) { + snprintf(buf, buf_len, "%f", stats.local_normdevrxploss); + } else if (!strcasecmp(args.field, "local_stdevrxploss")) { + snprintf(buf, buf_len, "%f", stats.local_stdevrxploss); + } else if (!strcasecmp(args.field, "rtt")) { + snprintf(buf, buf_len, "%f", stats.rtt); + } else if (!strcasecmp(args.field, "maxrtt")) { + snprintf(buf, buf_len, "%f", stats.maxrtt); + } else if (!strcasecmp(args.field, "minrtt")) { + snprintf(buf, buf_len, "%f", stats.minrtt); + } else if (!strcasecmp(args.field, "normdevrtt")) { + snprintf(buf, buf_len, "%f", stats.normdevrtt); + } else if (!strcasecmp(args.field, "stdevrtt")) { + snprintf(buf, buf_len, "%f", stats.stdevrtt); + } else if (!strcasecmp(args.field, "local_ssrc")) { + snprintf(buf, buf_len, "%d", stats.local_ssrc); + } else if (!strcasecmp(args.field, "remote_ssrc")) { + snprintf(buf, buf_len, "%d", stats.remote_ssrc); + } else { + ast_log(LOG_WARNING, "Unrecognized argument '%s' to %s\n", data, function); + res = -1; + } + } + } else if (!strcasecmp(args.option, "secure_signaling")) { + snprintf(buf, buf_len, "%s", dialog->socket.transport == AST_TRANSPORT_TLS ? "1" : "0"); + } else if (!strcasecmp(args.option, "secure_media")) { + snprintf(buf, buf_len, "%s", dialog->secure_audio_rtp ? "1" : "0"); + } else { + ast_log(LOG_WARNING, "Unknown option '%s'\n", args.option); + res = -1; + } + + return res; +} + +struct ast_custom_function sip_function_headers = { + .name = "SIP_HEADERS", + .read2 = sip_function_headers_read, +}; + +/* Read unique list of SIP headers (dialplan function) */ +int sip_function_headers_read(struct ast_channel *channel, const char *function, char *data, struct ast_str **buf, + ssize_t max_len) +{ + struct sip_dialog *dialog; + char *parse; + int i, pattern_len; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(pattern); + ); + + if (!channel) { + return -1; + } + + ast_channel_lock(channel); + + if (ast_channel_tech(channel) != &sip_channel_tech) { + ast_log(LOG_WARNING, "This function can only be used on SIP channels\n"); + ast_channel_unlock(channel); + + return -1; + } + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_channel_unlock(channel); + return -1; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (!args.pattern || strcmp(args.pattern, "*") == 0) { + args.pattern = ""; + } + + pattern_len = strlen(args.pattern); + + for (i = 0; i < dialog->initial_request.header_count; i++) { + const char *name = dialog->initial_request.headers[i].name; + + if (!strncasecmp(name, args.pattern, pattern_len)) { + if (!ast_in_delimited_string(name, ast_str_buffer(*buf), ',')) { + if (ast_str_strlen(*buf)) { + ast_str_append(buf, max_len, ","); + } + + ast_str_append(buf, max_len, "%s", name); + } + } + } + + ast_channel_unlock(channel); + return 0; +} + +struct ast_custom_function sip_function_header = { + .name = "SIP_HEADER", + .read2 = sip_function_header_read, +}; + +/* Read SIP header (dialplan function) */ +int sip_function_header_read(struct ast_channel *channel, const char *function, char *data, struct ast_str **buf, + ssize_t max_len) +{ + struct sip_dialog *dialog; + const char *header; + char *parse; + int iter, i, count; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(header); + AST_APP_ARG(count); + ); + + if (!channel) { + ast_log(LOG_WARNING, "No channel was provided to %s function\n", function); + return -1; + } + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "This function requires a header name\n"); + return -1; + } + + ast_channel_lock(channel); + + if (ast_channel_tech(channel) != &sip_channel_tech) { + ast_log(LOG_WARNING, "This function can only be used on SIP channels\n"); + ast_channel_unlock(channel); + return -1; + } + + parse = ast_strdup(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (!args.count || sscanf(args.count, "%30d", &count) != 1 || count < 1) { + count = 1; + } + + /* If there is no private structure, this channel is no longer alive */ + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_channel_unlock(channel); + return -1; + } + + header = NULL; + iter = 0; + + for (i = 0; i < count; i++) { + header = sip_message_next_header(&dialog->initial_request, args.header, &iter); + } + + if (ast_strlen_zero(header)) { + ast_channel_unlock(channel); + return -1; + } + + ast_str_set(buf, max_len, "%s", header); + ast_channel_unlock(channel); + return 0; +} + +struct ast_custom_function sip_function_peer = { + .name = "SIP_PEER", + .read2 = sip_function_peer_read, + .write = sip_function_peer_write +}; + +struct ast_custom_function sip_function_peer_deprecated = { + .name = "SIPPEER", + .read2 = sip_function_peer_read, + .write = sip_function_peer_write +}; + +/* ${SIP_PEER()} Dialplan function - reads peer data */ +int sip_function_peer_read(struct ast_channel *channel, const char *function, char *data, struct ast_str **buf, + ssize_t max_len) +{ + struct sip_peer *peer; + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(peer_name); + AST_APP_ARG(option); + ); + + if (strcmp(function, "SIP_PEER")) { + ast_log(LOG_WARNING, "Function '%s' is deprecated use '%s' instead\n", function, "SIP_PEER"); + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (!(peer = sip_peer_find(args.peer_name, TRUE, FALSE))) { + return -1; + } + + if (!strcasecmp(args.option, "ip") || !strcasecmp(args.option, "addr")) { + if (strcasecmp(args.option, "addr")) { + ast_log(LOG_WARNING, "Item '%s' is deprecated use '%s' instead\n", args.option, "addr"); + } + + ast_str_set(buf, max_len, "%s", ast_sockaddr_stringify_addr(&peer->address)); + } else if (!strcasecmp(args.option, "port")) { + ast_str_set(buf, max_len, "%d", ast_sockaddr_port(&peer->address)); + } else if (!strcasecmp(args.option, "status")) { + ast_str_set(buf, max_len, "%s", sip_peer_status2str(peer)); + } else if (!strcasecmp(args.option, "language")) { + ast_str_set(buf, max_len, "%s", peer->language); + } else if (!strcasecmp(args.option, "limit") || !strcasecmp(args.option, "maxcalls")) { + if (strcasecmp(args.option, "maxcalls")) { + ast_log(LOG_WARNING, "Item '%s' is deprecated use '%s' instead\n", args.option, "maxcalls"); + } + + ast_str_set(buf, max_len, "%d", peer->max_calls); + } else if (!strcasecmp(args.option, "busylevel")) { + ast_str_set(buf, max_len, "%d", peer->busy_level); + } else if (!strcasecmp(args.option, "curcalls") || !strcasecmp(args.option, "inuse")) { + if (strcasecmp(args.option, "inuse")) { + ast_log(LOG_WARNING, "Item '%s' is deprecated use '%s' instead\n", args.option, "inuse"); + } + + ast_str_set(buf, max_len, "%d", peer->inuse); + } else if (!strcasecmp(args.option, "ringing")) { + ast_str_set(buf, max_len, "%d", peer->ringing); + } else if (!strcasecmp(args.option, "onhold")) { + ast_str_set(buf, max_len, "%d", peer->onhold); + } else if (!strcasecmp(args.option, "maxforwards")) { + ast_str_set(buf, max_len, "%d", peer->max_forwards); + } else if (!strcasecmp(args.option, "accountcode")) { + ast_str_set(buf, max_len, "%s", peer->accountcode); + } else if (!strcasecmp(args.option, "callgroup")) { + ast_print_group(ast_str_buffer(*buf), max_len, peer->callgroup); + } else if (!strcasecmp(args.option, "pickupgroup")) { + ast_print_group(ast_str_buffer(*buf), max_len, peer->pickupgroup); + } else if (!strcasecmp(args.option, "namedcallgroup")) { + ast_print_namedgroups(buf, peer->named_callgroups); + } else if (!strcasecmp(args.option, "namedpickupgroup")) { + ast_print_namedgroups(buf, peer->named_pickupgroups); + } else if (!strcasecmp(args.option, "useragent")) { + ast_str_set(buf, max_len, "%s", peer->useragent); + } else if (!strcasecmp(args.option, "mailbox")) { + sip_peer_get_mailboxes(peer, buf); + } else if (!strcasecmp(args.option, "context")) { + ast_str_set(buf, max_len, "%s", peer->context); + } else if (!strcasecmp(args.option, "expire") || !strcasecmp(args.option, "expires")) { + if (strcasecmp(args.option, "expires")) { + ast_log(LOG_WARNING, "Item '%s' is deprecated use '%s' instead\n", args.option, "expires"); + } + + ast_str_set(buf, max_len, "%ld", ast_sched_when(sip_sched_context, peer->register_expires_sched_id)); + } else if (!strcasecmp(args.option, "dynamic")) { + ast_str_set(buf, max_len, "%s", peer->host_dynamic ? "yes" : "no"); + } else if (!strcasecmp(args.option, "callerid_name")) { + ast_str_set(buf, max_len, "%s", peer->caller_name); + } else if (!strcasecmp(args.option, "callerid_num") || !strcasecmp(args.option, "callerid_number")) { + if (strcasecmp(args.option, "callerid_number")) { + ast_log(LOG_WARNING, "Item '%s' is deprecated use '%s' instead\n", args.option, "callerid_number"); + } + + ast_str_set(buf, max_len, "%s", peer->caller_number); + } else if (!strcasecmp(args.option, "codecs")) { + ast_format_cap_get_names(peer->format_cap, buf); + } else if (!strcasecmp(args.option, "encryption")) { + ast_str_set(buf, max_len, "%s", peer->secure_media ? "yes" : "no"); + } else if (!strncasecmp(args.option, "chanvar[", 8) || !strncasecmp(args.option, "variable[", 9)) { + char *name; + const char *value; + + if (strncasecmp(args.option, "variable[", 9)) { + ast_log(LOG_WARNING, "Item '%s' is deprecated use '%s' instead\n", "chanvar", "variable"); + } + + name = strchr(args.option, '[') + 1; + name = strsep(&name, "]"); + + if ((value = ast_variable_find_in_list(peer->channel_variables, name))) { + ast_str_set(buf, max_len, "%s", value); + } + } else if (!strncasecmp(args.option, "codec[", 6)) { + char *codec; + struct ast_format *format; + + codec = strchr(args.option, '[') + 1; + codec = strsep(&codec, "]"); + + if ((format = ast_format_cap_get_format(peer->format_cap, atoi(codec)))) { + ast_str_set(buf, max_len, "%s", ast_format_get_name(format)); + ao2_ref(format, -1); + } else { + buf[0] = '\0'; + } + } else if (!strcasecmp(args.option, "vmexten") || !strcasecmp(args.option, "mwiexten")) { + ast_str_set(buf, max_len, "%s", peer->mwi_exten); + } else if (!strcasecmp(args.option, "donotdisturb")) { + ast_str_set(buf, max_len, "%s", peer->do_not_disturb ? "yes" : "no"); + } else if (!strcasecmp(args.option, "callforward")) { + ast_str_set(buf, max_len, "%s", peer->call_forward); + } else if (!strcasecmp(args.option, "huntgroup")) { + ast_str_set(buf, max_len, "%s", peer->hunt_group ? "yes" : "no"); + } else if (!strcasecmp(args.option, "regcallid") || !strcasecmp(args.option, "register_callid")) { + if (strcasecmp(args.option, "register_callid")) { + ast_log(LOG_WARNING, "Item '%s' is deprecated use '%s' instead\n", args.option, + "register_callid"); + } + + ast_str_set(buf, max_len, "%s", peer->register_call_id); + } else if (!strcasecmp(args.option, "ciscodevicename") || !strcasecmp(args.option, "devicename")) { + if (strcasecmp(args.option, "devicename")) { + ast_log(LOG_WARNING, "Item '%s' is deprecated use '%s' instead\n", args.option, "devicename"); + } + + ast_str_set(buf, max_len, "%s", peer->device_name); + } else if (!strcasecmp(args.option, "ciscolineindex") || !strcasecmp(args.option, "lineindex")) { + if (strcasecmp(args.option, "lineindex")) { + ast_log(LOG_WARNING, "Item '%s' is deprecated use '%s' instead\n", args.option, "lineindex"); + } + + ast_str_set(buf, max_len, "%d", peer->line_index); + } else { + ast_log(LOG_WARNING, "Unknown option '%s'\n", args.option); + } + + ao2_ref(peer, -1); + return 0; +} + +int sip_function_peer_write(struct ast_channel *channel, const char *function, char *data, const char *value) +{ + struct sip_peer *peer; + struct sip_alias *alias; + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(peer_name); + AST_APP_ARG(option); + ); + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (!(peer = sip_peer_find(args.peer_name, TRUE, FALSE))) { + return -1; + } + + if (!strcasecmp(args.option, "donotdisturb")) { + peer->do_not_disturb = ast_true(value); + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", peer->name); + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (alias->peer) { + alias->peer->do_not_disturb = peer->do_not_disturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", alias->peer->name); + } + } + + if (!peer->realtime) { + ast_db_put("SIP/DoNotDisturb", peer->name, peer->do_not_disturb ? "yes" : "no"); + } else if (sip_config.realtime_update_peer && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name, + "donotdisturb", peer->do_not_disturb ? "yes" : "no", SENTINEL); + + } + + sip_peer_send_do_not_disturb(peer); + } else if (!strcasecmp(args.option, "huntgroup")) { + peer->hunt_group = ast_true(value); + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (alias->peer) { + alias->peer->hunt_group = peer->hunt_group; + } + } + + if (!peer->realtime) { + ast_db_put("SIP/HuntGroup", peer->name, peer->hunt_group ? "yes" : "no"); + } else if (sip_config.realtime_update_peer && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name, + "huntgroup", peer->hunt_group ? "yes" : "no", SENTINEL); + } + + sip_peer_send_hunt_group(peer); + } else if (!strcasecmp(args.option, "callforward")) { + ast_string_field_set(peer, call_forward, value); + + if (!peer->realtime) { + if (ast_strlen_zero(peer->call_forward)) { + ast_db_del("SIP/CallForward", peer->name); + } else { + ast_db_put("SIP/CallForward", peer->name, peer->call_forward); + } + } else if (sip_config.realtime_update_peer && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name, "callforward", peer->call_forward, SENTINEL); + } + + sip_peer_send_call_forward(peer); + } + + ao2_ref(peer, -1); + return 0; +} + +struct ast_custom_function sip_function_check_domain = { + .name = "SIP_CHECK_DOMAIN", + .read2 = sip_function_check_domain_read, +}; + +struct ast_custom_function sip_function_check_domain_deprecated = { + .name = "CHECKSIPDOMAIN", + .read2 = sip_function_check_domain_read, +}; + +/* Dial plan function to check if domain is local */ +int sip_function_check_domain_read(struct ast_channel *channel, const char *function, char *data, struct ast_str **buf, + ssize_t max_len) +{ + if (strcmp(function, "SIP_CHECK_DOMAIN")) { + ast_log(LOG_WARNING, "Function '%s' is deprecated use '%s' instead\n", function, "SIP_CHECK_DOMAIN"); + } + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "%s function requires a domain name\n", function); + return -1; + } + + ast_str_set(buf, max_len, "%s", sip_domain_check(data, NULL, 0) ? data : ""); + return 0; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/domains.c asterisk-22.6.0/channels/sip/domains.c --- asterisk-22.6.0.orig/channels/sip/domains.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/domains.c 2025-10-21 18:38:17.445218279 +1300 @@ -0,0 +1,129 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/channel.h" + +#include "include/sip.h" +#include "include/domains.h" + +static void sip_domain_destroy(void *data); + +/* The SIP domain list */ +struct ao2_container *sip_domains = NULL; + +int sip_domain_cmp(void *data, void *arg, int flags) +{ + struct sip_domain *domain; + const char *name; + + domain = (struct sip_domain *) data; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + struct sip_domain *domain = (struct sip_domain *) arg; + + name = domain->name; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + name = (const char *) arg; + } else { + return 0; + } + + if (!strcmp(domain->name, name)) { + return CMP_MATCH | CMP_STOP; + } + + return 0; +} + +/* Add SIP domain to list of domains we are responsible for */ +int sip_domain_build(const char *config, int lineno) +{ + struct sip_domain *domain; + char *name, *context; + + name = ast_strdupa(config); + + if ((context = strchr(name, ','))) { + *context++ = '\0'; + } + + if (ast_strlen_zero(name)) { + ast_log(LOG_WARNING, "Format for domain is domain[,context] at line %d\n", lineno); + return -1; + } + + if ((domain = ao2_find(sip_domains, name, OBJ_SEARCH_KEY))) { + ao2_ref(domain, -1); + return 0; + } + + if (!(domain = ao2_alloc(sizeof(*domain), sip_domain_destroy))) { + return -1; + } + + if (ast_string_field_init(domain, 128)) { + ao2_ref(domain, -1); + return -1; + } + + ast_string_field_set(domain, name, name); + ast_string_field_set(domain, context, context); + + ao2_link(sip_domains, domain); + ao2_ref(domain, -1); + + ast_debug(3, "Added local domain '%s'\n", name); + return 0; +} + +static void sip_domain_destroy(void *data) +{ + struct sip_domain *domain = (struct sip_domain *) data; + + ast_debug(3, "Destroying local domain '%s'\n", domain->name); + ast_string_field_free_memory(domain); +} + +/* Check if domain part of uri is local to our server */ +int sip_domain_check(const char *name, char *context, size_t context_len) +{ + struct sip_domain *domain = ao2_find(sip_domains, name, OBJ_SEARCH_KEY); + + if (!domain) { + return FALSE; + } + + if (context) { + ast_copy_string(context, domain->context, context_len); + } + + ao2_ref(domain, -1); + return TRUE; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/events.c asterisk-22.6.0/channels/sip/events.c --- asterisk-22.6.0.orig/channels/sip/events.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/events.c 2025-10-21 18:38:17.446218252 +1300 @@ -0,0 +1,141 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/sched.h" +#include "asterisk/channel.h" +#include "asterisk/indications.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_endpoints.h" +#include "asterisk/stasis_system.h" +#include "asterisk/acl.h" +#include "asterisk/security_events.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/proxy.h" +#include "include/registrations.h" +#include "include/mwi_subscriptions.h" +#include "include/config.h" +#include "include/events.h" + +static int __sip_network_change_event(const void *data); +static void sip_network_change_event(void *data, struct stasis_subscription *subscription, + struct stasis_message *message); +static void sip_acl_change_event(void *data, struct stasis_subscription *subscription, struct stasis_message *message); + +static int sip_network_change_sched_id = -1; +static struct stasis_subscription *sip_network_change_subscription; /* Subscription for network change events */ +static struct stasis_subscription *sip_acl_change_subscription; /* Subscription for named ACL system change events */ + +/* Event callback which indicates we're fully booted */ +void sip_startup_event(void *data, struct stasis_subscription *subscription, struct stasis_message *message) +{ + struct ast_json_payload *payload; + const char *type; + + if (stasis_message_type(message) != ast_manager_get_generic_type()) { + return; + } + + payload = stasis_message_data(message); + type = ast_json_string_get(ast_json_object_get(payload->json, "type")); + + if (strcmp(type, "FullyBooted")) { + return; + } + + sip_module_notice(); + stasis_unsubscribe(subscription); +} + +/* Run by the sched thread */ +static int __sip_network_change_event(const void *data) +{ + sip_network_change_sched_id = -1; + + sip_registration_send_all(); + sip_mwi_subscription_send_all(); + return 0; +} + +static void sip_network_change_event(void *data, struct stasis_subscription *subscription, + struct stasis_message *message) +{ + /* This callback is only concerned with network change messages from the system topic */ + if (stasis_message_type(message) != ast_network_change_type()) { + return; + } + + if (sip_network_change_sched_id == -1) { + ast_verb(3, "Renewing all SIP registrations in response to a network change event\n"); + + sip_network_change_sched_id = ast_sched_add(sip_sched_context, 1000, __sip_network_change_event, NULL); + } +} + +void sip_network_change_subscribe(void) +{ + if (!sip_network_change_subscription) { + sip_network_change_subscription = stasis_subscribe(ast_system_topic(), sip_network_change_event, NULL); + + stasis_subscription_accept_message_type(sip_network_change_subscription, ast_network_change_type()); + stasis_subscription_set_filter(sip_network_change_subscription, STASIS_SUBSCRIPTION_FILTER_SELECTIVE); + } +} + +void sip_network_change_unsubscribe(void) +{ + sip_network_change_subscription = stasis_unsubscribe_and_join(sip_network_change_subscription); +} + +static void sip_acl_change_event(void *data, struct stasis_subscription *subscription, struct stasis_message *message) +{ + if (stasis_message_type(message) != ast_named_acl_change_type()) { + return; + } + + ast_verb(3, "Reloading chan_sip in response to ACL change event\n"); + sip_config_reload(CHANNEL_ACL_RELOAD); +} + +void sip_acl_change_subscribe(void) +{ + if (!sip_acl_change_subscription) { + sip_acl_change_subscription = stasis_subscribe(ast_security_topic(), sip_acl_change_event, NULL); + + stasis_subscription_accept_message_type(sip_acl_change_subscription, ast_named_acl_change_type()); + stasis_subscription_set_filter(sip_acl_change_subscription, STASIS_SUBSCRIPTION_FILTER_SELECTIVE); + } +} + +void sip_acl_change_unsubscribe(void) +{ + sip_acl_change_subscription = stasis_unsubscribe_and_join(sip_acl_change_subscription); +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/fax.c asterisk-22.6.0/channels/sip/fax.c --- asterisk-22.6.0.orig/channels/sip/fax.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/fax.c 2025-10-21 18:38:17.448218199 +1300 @@ -0,0 +1,312 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/strings.h" +#include "asterisk/indications.h" +#include "asterisk/pbx.h" +#include "asterisk/udptl.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/fax.h" + +static int sip_fax_abort(const void *data); +static int __sip_fax_start_abort(const void *data); +static void sip_fax_stop_abort(struct sip_dialog *dialog); +static int __sip_fax_stop_abort(const void *data); + +/* Create and initialize UDPTL for the specified dialog */ +int sip_fax_alloc(struct sip_dialog *dialog) +{ + if (!dialog->peer->fax_support) { + return -1; + } + + /* If we've already initialized T38, don't take any further action */ + if (dialog->udptl) { + return 0; + } + + /* T38 can be supported by this dialog, create it and set the derived properties */ + if (!(dialog->udptl = ast_udptl_new_with_bindaddr(sip_sched_context, sip_io_context, 0, + &sip_config.udp_bind_address))) { + ast_log(AST_LOG_WARNING, "UDPTL creation failed, disabling T38 for this dialog\n"); + return -1; + } + + ast_udptl_set_error_correction_scheme(dialog->udptl, dialog->peer->udptl_error_correction); + ast_udptl_set_far_max_datagram(dialog->udptl, dialog->peer->fax_max_datagram); + + if (dialog->channel) { + ast_channel_set_fd(dialog->channel, SIP_UDPTL_FD, ast_udptl_fd(dialog->udptl)); + } + + ast_udptl_setqos(dialog->udptl, sip_config.tos_audio, sip_config.cos_audio); + + ast_debug(1, "Setting NAT on UDPTL to %s\n", dialog->nat_rtp ? "on" : "off"); + ast_udptl_setnat(dialog->udptl, dialog->nat_rtp); + + return 0; +} + +/* Helper function which updates T.38 capability information and triggers a reinvite */ +int sip_fax_update(struct sip_dialog *dialog, const struct ast_control_t38_parameters *parameters) +{ + if (!dialog->udptl) { + return -1; + } + + switch (parameters->request_response) { + case AST_T38_NEGOTIATED: + case AST_T38_REQUEST_NEGOTIATE: /* Request T38 */ + /* Negotiation can not take place without a valid max_ifp value */ + if (!parameters->max_ifp) { + if (dialog->fax_state == SIP_FAX_REMOTE_REINVITE) { + sip_fax_stop_abort(dialog); + sip_response_send_reliable(dialog, "488 Not Acceptable Here", &dialog->initial_request); + } + + sip_fax_set_state(dialog, SIP_FAX_REJECTED); + break; + } else if (dialog->fax_state == SIP_FAX_REMOTE_REINVITE) { + sip_fax_stop_abort(dialog); + + dialog->fax_config = *parameters; + + /* Modify our parameters to conform to the peer's parameters, based on the rules in the ITU T.38 + * recommendation */ + if (!dialog->fax_remote_config.fill_bit_removal) { + dialog->fax_config.fill_bit_removal = FALSE; + } + + if (!dialog->fax_remote_config.transcoding_mmr) { + dialog->fax_config.transcoding_mmr = FALSE; + } + + if (!dialog->fax_remote_config.transcoding_jbig) { + dialog->fax_config.transcoding_jbig = FALSE; + } + + dialog->fax_config.version = MIN(dialog->fax_config.version, dialog->fax_remote_config.version); + dialog->fax_config.rate_management = dialog->fax_remote_config.rate_management; + + ast_udptl_set_local_max_ifp(dialog->udptl, dialog->fax_config.max_ifp); + sip_fax_set_state(dialog, SIP_FAX_ENABLED); + + sip_response_send_with_sdp(dialog, "200 OK", &dialog->initial_request, SIP_SEND_CRITICAL, + FALSE, FALSE); + } else if (dialog->fax_state != SIP_FAX_ENABLED || + (dialog->fax_state == SIP_FAX_ENABLED && + parameters->request_response == AST_T38_REQUEST_NEGOTIATE)) { + dialog->fax_config = *parameters; + + ast_udptl_set_local_max_ifp(dialog->udptl, dialog->fax_config.max_ifp); + sip_fax_set_state(dialog, SIP_FAX_LOCAL_REINVITE); + + if (!dialog->pending_invite_cseq) { + sip_request_send_reinvite_with_sdp(dialog, FALSE, TRUE); + } else if (!dialog->pending_bye) { + dialog->need_reinvite = TRUE; + } + } + + break; + case AST_T38_TERMINATED: + case AST_T38_REFUSED: + case AST_T38_REQUEST_TERMINATE: /* Shutdown T38 */ + if (dialog->fax_state == SIP_FAX_REMOTE_REINVITE) { + sip_fax_stop_abort(dialog); + sip_fax_set_state(dialog, SIP_FAX_REJECTED); + + sip_response_send_reliable(dialog, "488 Not Acceptable Here", &dialog->initial_request); + } else if (dialog->fax_state == SIP_FAX_ENABLED) { + sip_fax_set_state(dialog, SIP_FAX_DISABLED); + sip_request_send_reinvite_with_sdp(dialog, FALSE, FALSE); + } + + break; + case AST_T38_REQUEST_PARMS: /* Application wants remote's parameters re-sent */ + if (dialog->fax_state == SIP_FAX_REMOTE_REINVITE) { + struct ast_control_t38_parameters parameters = dialog->fax_remote_config; + + sip_fax_stop_abort(dialog); + + parameters.max_ifp = ast_udptl_get_far_max_ifp(dialog->udptl); + parameters.request_response = AST_T38_REQUEST_NEGOTIATE; + + if (dialog->channel) { + ast_queue_control_data(dialog->channel, AST_CONTROL_T38_PARAMETERS, + ¶meters, sizeof(parameters)); + } + + /* We need to return a positive value here, so that applications that send this request can + * determine conclusively whether it was accepted or not... older versions of chan_sip would + * just silently accept it and return zero */ + return AST_T38_REQUEST_PARMS; + } + + break; + default: + return -1; + } + + return 0; +} + +/* Change the T38 state on a SIP dialog */ +void sip_fax_set_state(struct sip_dialog *dialog, int fax_state) +{ + struct ast_control_t38_parameters parameters; + + /* Don't bother changing if we are already in the state wanted or if no channel was provided we can't send off a + * control frame */ + if (dialog->fax_state == fax_state || !dialog->channel) { + return; + } + + ast_debug(2, "T38 state changed to %u on channel %s\n", + fax_state, dialog->channel ? ast_channel_name(dialog->channel) : ""); + + memset(¶meters, 0, sizeof(parameters)); + + /* Given the state requested and old state determine what control frame we want to queue up */ + switch (fax_state) { + case SIP_FAX_ENABLED: + case SIP_FAX_REMOTE_REINVITE: + parameters = dialog->fax_remote_config; + parameters.max_ifp = ast_udptl_get_far_max_ifp(dialog->udptl); + + if (fax_state == SIP_FAX_ENABLED) { + parameters.request_response = AST_T38_NEGOTIATED; + } else if (fax_state == SIP_FAX_REMOTE_REINVITE) { + parameters.request_response = AST_T38_REQUEST_NEGOTIATE; + } + + ast_udptl_set_tag(dialog->udptl, "%s", ast_channel_name(dialog->channel)); + break; + case SIP_FAX_REJECTED: + case SIP_FAX_DISABLED: + if (dialog->fax_state == SIP_FAX_ENABLED) { + parameters.request_response = AST_T38_TERMINATED; + } else if (dialog->fax_state == SIP_FAX_LOCAL_REINVITE) { + parameters.request_response = AST_T38_REFUSED; + } + + break; + case SIP_FAX_LOCAL_REINVITE: + /* Wait until we get a peer response before responding to local reinvite */ + break; + } + + dialog->fax_state = fax_state; + + /* Woot we got a message, create a control frame and send it on! */ + if (parameters.request_response) { + ast_queue_control_data(dialog->channel, AST_CONTROL_T38_PARAMETERS, ¶meters, sizeof(parameters)); + } +} + +/* Called to deny a T38 reinvite if the core does not respond to our request Run by the sched thread */ +static int sip_fax_abort(const void *data) +{ + struct sip_dialog *dialog; + struct ast_channel *channel; + + dialog = (struct sip_dialog *) data; + dialog->fax_abort_sched_id = -1; + + channel = sip_dialog_lock_with_channel(dialog); + + /* An application may have taken ownership of the T.38 negotiation on the channel while we were waiting to grab + * the lock. If it did, the T.38 state will have been changed. This is our indication that we do *not* want to + * abort the negotiation process */ + if (dialog->fax_state == SIP_FAX_REMOTE_REINVITE) { + /* Still waiting for a response on timeout so reject the offer */ + sip_fax_set_state(dialog, SIP_FAX_REJECTED); + sip_response_send_reliable(dialog, "488 Not Acceptable Here", &dialog->initial_request); + } + + if (channel) { + ast_channel_unlock(channel); + ast_channel_unref(channel); + } + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + return 0; +} + +/* Run by the sched thread */ +static int __sip_fax_stop_abort(const void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->fax_abort_sched_id, ao2_cleanup(dialog)); + ao2_ref(dialog, -1); + return 0; +} + +static void sip_fax_stop_abort(struct sip_dialog *dialog) +{ + if (ast_sched_add(sip_sched_context, 0, __sip_fax_stop_abort, ao2_bump(dialog)) == -1) { + ao2_ref(dialog, -1); + } +} + +/* Run by the sched thread */ +static int __sip_fax_start_abort(const void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->fax_abort_sched_id, ao2_cleanup(dialog)); + + if ((dialog->fax_abort_sched_id = ast_sched_add(sip_sched_context, 5000, sip_fax_abort, ao2_bump(dialog)) == -1)) { + ao2_ref(dialog, -1); + } + + ao2_ref(dialog, -1); + return 0; +} + +void sip_fax_start_abort(struct sip_dialog *dialog) +{ + if (ast_sched_add(sip_sched_context, 0, __sip_fax_start_abort, ao2_bump(dialog)) == -1) { + ao2_ref(dialog, -1); + } +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/handlers.c asterisk-22.6.0/channels/sip/handlers.c --- asterisk-22.6.0.orig/channels/sip/handlers.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/handlers.c 2025-10-21 18:38:17.451218119 +1300 @@ -0,0 +1,4267 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/module.h" +#include "asterisk/logger.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/utils.h" +#include "asterisk/xml.h" +#include "asterisk/sched.h" +#include "asterisk/bridge.h" +#include "asterisk/callerid.h" +#include "asterisk/causes.h" +#include "asterisk/indications.h" +#include "asterisk/cdr.h" +#include "asterisk/sdp_srtp.h" +#include "asterisk/pbx.h" +#include "asterisk/devicestate.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_endpoints.h" +#include "asterisk/stasis_system.h" +#include "asterisk/features_config.h" +#include "asterisk/astdb.h" +#include "asterisk/message.h" +#include "asterisk/pickup.h" +#include "asterisk/mwi.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/domains.h" +#include "include/registrations.h" +#include "include/mwi_subscriptions.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/sdp.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/channel_tech.h" +#include "include/handlers.h" +#include "include/security_events.h" +#include "include/transfer.h" +#include "include/remotecc.h" +#include "include/pickup.h" +#include "include/parking.h" +#include "include/conference.h" +#include "include/recording.h" +#include "include/callback.h" +#include "include/fax.h" + +static int sip_handle_request(struct sip_dialog *dialog, struct sip_message *request, int *recount, int *no_unlock); +static int sip_handle_request_invite(struct sip_dialog *dialog, struct sip_message *request, int *recount, + int *no_unlock); +static int sip_handle_request_ack(struct sip_dialog *dialog, struct sip_message *request); +static int sip_handle_request_update(struct sip_dialog *dialog, struct sip_message *request); +static int sip_handle_request_cancel(struct sip_dialog *dialog, struct sip_message *request); +static int sip_handle_request_bye(struct sip_dialog *dialog, struct sip_message *request); +static int sip_handle_request_subscribe(struct sip_dialog *dialog, struct sip_message *request); +static int sip_handle_request_notify(struct sip_dialog *dialog, struct sip_message *request); +static int sip_handle_request_refer(struct sip_dialog *dialog, struct sip_message *request, int *no_unlock); +static int sip_handle_request_register(struct sip_dialog *dialog, struct sip_message *request); +static int sip_handle_request_options(struct sip_dialog *dialog, struct sip_message *request); +static int sip_handle_request_publish(struct sip_dialog *dialog, struct sip_message *request); +static int sip_handle_request_info(struct sip_dialog *dialog, struct sip_message *request); +static int sip_handle_request_message(struct sip_dialog *dialog, struct sip_message *request); + +static int sip_handle_invite_replaces(struct sip_dialog *dialog, struct sip_message *request, int *no_unlock, + struct sip_dialog *transfer_dialog, struct ast_channel *transfer_channel); +static int sip_handle_subscribe_feature_event(struct sip_dialog *dialog, struct sip_message *request, int *feature); +static int sip_handle_notify_dialog(struct sip_dialog *dialog, struct sip_message *request); +static int sip_handle_publish_presence(struct sip_dialog *dialog, struct sip_message *request); +static int sip_handle_refer_remotecc(struct sip_dialog *dialog, struct sip_message *request); + +static void sip_handle_response(struct sip_dialog *dialog, struct sip_message *response); +static void sip_handle_response_invite(struct sip_dialog *dialog, struct sip_message *response); +static void sip_handle_response_ack(struct sip_dialog *dialog, struct sip_message *response); +static void sip_handle_response_update(struct sip_dialog *dialog, struct sip_message *response); +static void sip_handle_response_cancel(struct sip_dialog *dialog, struct sip_message *response); +static void sip_handle_response_bye(struct sip_dialog *dialog, struct sip_message *response); +static void sip_handle_response_subscribe(struct sip_dialog *dialog, struct sip_message *response); +static void sip_handle_response_notify(struct sip_dialog *dialog, struct sip_message *response); +static void sip_handle_response_refer(struct sip_dialog *dialog, struct sip_message *response); +static void sip_handle_response_register(struct sip_dialog *dialog, struct sip_message *response); +static void sip_handle_response_options(struct sip_dialog *dialog, struct sip_message *response); +static void sip_handle_response_publish(struct sip_dialog *dialog, struct sip_message *response); +static void sip_handle_response_info(struct sip_dialog *dialog, struct sip_message *response); +static void sip_handle_response_message(struct sip_dialog *dialog, struct sip_message *response); + +/* Handle incoming SIP message - request or response. This is used for all transports (udp, tcp and tls) */ +int sip_handle_incoming(struct sip_socket *socket, char *data) +{ + struct sip_dialog *dialog; + struct ast_channel *channel; + struct sip_message message; + int recount, no_unlock, authenticated; + const char *useragent; + + /* Empty line, probably a NAT keepalive */ + if (ast_strlen_zero(data = ast_skip_blanks(data))) { + return FALSE; + } + + memset(&message, 0, sizeof(message)); + + if (sip_debug && + (ast_sockaddr_isnull(&sip_debug_address) || + !ast_sockaddr_cmp_addr(&sip_debug_address, &socket->address))) { + /* Set the debug flag early on packet level */ + message.debug = TRUE; + } + + if (message.debug) { + ast_verbose("\n<--- SIP read from %s://%s --->\n%s\n<------------->\n", + ast_transport2str(socket->transport), ast_sockaddr_stringify(&socket->address), + data); + } + + /* If the first line doesn't start or end with SIP/2.0 we don't even respond */ + if (sip_message_parse(&message, data)) { + return FALSE; + } + + ast_mutex_lock(&sip_netsock_lock); + + /* Find the active SIP dialog or create a new one, this function will also send back error responses if it + * cannot find the relevant dialog or there was some other error */ + if (!(dialog = sip_message_find_dialog(&message, socket))) { /* Returns dialog with a ref only. _NOT_ locked */ + sip_message_destroy(&message); + ast_mutex_unlock(&sip_netsock_lock); + return FALSE; + } + + sip_socket_copy(&dialog->socket, socket); + + if (dialog->logger_callid) { + ast_callid_threadassoc_add(dialog->logger_callid); + } + + sip_parse_allow(dialog, &message); /* Find out what methods they allow */ + sip_parse_supported(dialog, &message); /* Find out what options they support */ + + useragent = sip_message_find_header(&message, "User-Agent"); /* Save useragent of the client */ + + if (!ast_strlen_zero(useragent) && strcmp(dialog->useragent, useragent)) { + ast_string_field_set(dialog, useragent, useragent); + } + + /* If we have a channel then this request has been authenticated */ + if (dialog->channel) { + message.authenticated = TRUE; + } + + /* Lock both the dialog and the channel if a channel is present. This will not fail */ + channel = sip_dialog_lock_with_channel(dialog); + recount = FALSE; + no_unlock = FALSE; + + if (!message.response) { + sip_handle_request(dialog, &message, &recount, &no_unlock); + } else { + sip_handle_response(dialog, &message); + } + + authenticated = message.authenticated; + sip_message_destroy(&message); + ast_mutex_unlock(&sip_netsock_lock); + + if (recount) { + ast_update_use_count(); + } + + if (dialog->channel && !no_unlock) { + ast_channel_unlock(dialog->channel); + } + + if (channel) { + ast_channel_unref(channel); + } + + ao2_unlock(dialog); + + if (dialog->logger_callid) { + ast_callid_threadassoc_remove(); + } + + ao2_ref(dialog, -1); /* Dialog is gone after the return */ + return authenticated; +} + +/* Handle incoming SIP requests (methods) called with dialog and dialog->channel locked */ +static int sip_handle_request(struct sip_dialog *dialog, struct sip_message *request, int *recount, int *no_unlock) +{ + int res; + + /* New SIP request coming in */ + ast_debug(4, "Received '%s', outgoing method is '%s'\n", + sip_method2str(request->method), sip_method2str(dialog->method)); + + if (dialog->incoming_cseq && dialog->incoming_cseq > request->cseq) { + if (dialog->pending_invite_cseq && request->cseq == dialog->pending_invite_cseq && + request->method & (SIP_METHOD_ACK | SIP_METHOD_CANCEL)) { + ast_debug(2, "Received CANCEL or ACK on INVITE with transactions in between\n"); + } else { + ast_debug(1, "Ignoring too old message CSeq %u (expecting CSeq greater than %u)\n", + request->cseq, dialog->incoming_cseq); + + if (request->method == SIP_METHOD_INVITE) { + /* Respond according to RFC 3261 14.2 with Retry-After betwewn 0 and 10 */ + sip_response_send_with_retry_after(dialog, request, (unsigned int) (ast_random() % 10) + 1); + } else if (request->method != SIP_METHOD_ACK) { + /* We must respond according to RFC 3261 sec 12.2 */ + sip_response_send(dialog, "500 Internal Server Error", request); + } + + return -1; + } + } else if (dialog->incoming_cseq && request->cseq == dialog->incoming_cseq && + request->method != SIP_METHOD_ACK && (request->method != SIP_METHOD_CANCEL || dialog->already_gone)) { + /* Ignore means "don't do anything with it" but still have to respond appropriately. We do this if we + * receive a repeat of the last sequence number */ + request->ignore = TRUE; + + ast_debug(3, "Ignoring %s '%s' because of retransmit (CSeq %u, our CSeq %u)\n", + sip_method2str(request->method), request->call_id, request->cseq, dialog->incoming_cseq); + } + + dialog->method = request->method; /* Set which SIP method they are using */ + + /* RFC 3261 section 9. "CANCEL has no effect on a request to which a UAS has already given a final response." */ + if (!dialog->pending_invite_cseq && request->method == SIP_METHOD_CANCEL) { + sip_response_send(dialog, "481 Call/Transaction Does Not Exist", request); + return -1; + } + + if (request->cseq > dialog->incoming_cseq) { + /* Next should follow monotonically (but not necessarily incrementally) */ + dialog->incoming_cseq = request->cseq; + } + + /* Find their tag if we haven't got it */ + if (ast_strlen_zero(dialog->remote_tag)) { + ast_string_field_set(dialog, remote_tag, request->from_tag); + } + + /* If this is a request packet without a from tag, it's not correct according to RFC 3261. + * Check if this a new request in a new dialog with a to-tag already attached to it, RFC 3261 - section 12.2 + * - and we don't want to mess with recovery */ + if (ast_strlen_zero(dialog->initial_request.uri) && !ast_strlen_zero(request->to_tag)) { + /* If this is a first request and it got a to-tag, it is not for us */ + if (!request->ignore && request->method == SIP_METHOD_INVITE) { + /* Just because we think this is a dialog-starting INVITE with a to-tag doesn't mean it + * actually is. It could be a reinvite for an established, but unknown dialog. In such a case, + * we need to change our tag to the incoming INVITE's to-tag so that they will recognize the + * 481 we send and so that we will properly match their incoming ACK */ + ast_string_field_set(dialog, local_tag, request->to_tag); + dialog->pending_invite_cseq = dialog->incoming_cseq; + + /* Will cease to exist after ACK */ + sip_response_send_reliable(dialog, "481 Call/Transaction Does Not Exist", request); + return -1; + } else if (request->method != SIP_METHOD_ACK) { + sip_response_send(dialog, "481 Call/Transaction Does Not Exist", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + + /* Otherwise, this is an ACK. It will always have a to-tag */ + } + + /* Handle various incoming SIP methods in requests */ + switch (request->method) { + case SIP_METHOD_OPTIONS: + res = sip_handle_request_options(dialog, request); + break; + case SIP_METHOD_INVITE: + res = sip_handle_request_invite(dialog, request, recount, no_unlock); + break; + case SIP_METHOD_ACK: + res = sip_handle_request_ack(dialog, request); + break; + case SIP_METHOD_UPDATE: + res = sip_handle_request_update(dialog, request); + break; + case SIP_METHOD_BYE: + res = sip_handle_request_bye(dialog, request); + break; + case SIP_METHOD_CANCEL: + res = sip_handle_request_cancel(dialog, request); + break; + case SIP_METHOD_REGISTER: + res = sip_handle_request_register(dialog, request); + break; + case SIP_METHOD_REFER: + res = sip_handle_request_refer(dialog, request, no_unlock); + break; + case SIP_METHOD_SUBSCRIBE: + res = sip_handle_request_subscribe(dialog, request); + break; + case SIP_METHOD_NOTIFY: + res = sip_handle_request_notify(dialog, request); + break; + case SIP_METHOD_PUBLISH: + res = sip_handle_request_publish(dialog, request); + break; + case SIP_METHOD_MESSAGE: + res = sip_handle_request_message(dialog, request); + break; + case SIP_METHOD_INFO: + res = sip_handle_request_info(dialog, request); + break; + default: + res = -1; /* Unknown methods are handled in sip_message_find_dialog */ + break; + } + + return res; +} + +/* Handle incoming INVITE request. If the INVITE has a Replaces header, it is part of an attended transfer. If so, we do + * not go through the dial plan but try to find the active call and masquerade into it */ +static int sip_handle_request_invite(struct sip_dialog *dialog, struct sip_message *request, int *recount, + int *no_unlock) +{ + int reinvite; + char *replaces, *call_id, pickup_exten[AST_MAX_EXTENSION], pickup_context[AST_MAX_CONTEXT]; + RAII_VAR(struct sip_dialog *, transfer_dialog, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, transfer_channel, NULL, ao2_cleanup); + + /* If there are any options required that we do not support, then send a 420 with only those unsupported + * options listed */ + if (sip_parse_require(dialog, request)) { + ast_verb(3, "SIP call from '%s' rejected due to an unsupported option '%s'\n", + sip_message_find_header(request, "From"), dialog->unsupported_options); + + dialog->invite_state = SIP_INVITE_COMPLETED; + sip_response_send_with_unsupported(dialog, request, dialog->unsupported_options); + + if (!dialog->last_invite_cseq) { + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } + + return -1; + } + + /* Check if this is a loop */ + if (dialog->outgoing && dialog->invite_state != SIP_INVITE_TERMINATED && + dialog->invite_state != SIP_INVITE_CONFIRMED && dialog->channel && + ast_channel_state(dialog->channel) != AST_STATE_UP) { + /* This is a call to ourself. Send ourselves an error code and stop processing immediately, as SIP + * really has no good mechanism for being able to call yourself. If pedantic is on, we need to check the + * tags. If they're different, this is in fact a forked call through a SIP proxy somewhere */ + if (!sip_cmp_uri(dialog->initial_request.uri, request->uri)) { + dialog->invite_state = SIP_INVITE_COMPLETED; + sip_response_send(dialog, "482 Loop Detected", request); + + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } else { + /* This is a spiral. What we need to do is to just change the outgoing INVITE so that it now + * routes to the new Request URI. Since we created the INVITE ourselves that should be all we + * need to do. This needs to be reviewed. You don't change the request URI really, you route + * the packet correctly instead */ + char *uri, *user; + + ast_debug(2, "Potential spiral detected, original RURI was '%s' new RURI is '%s'\n", + dialog->initial_request.uri, request->uri); + + sip_response_send(dialog, "100 Trying", request); + uri = ast_strdupa(request->uri); + + if (sip_parse_uri(uri, NULL, &user, NULL, NULL, NULL)) { + ast_debug(1, "Unable to parse URI '%s'\n", request->uri); + user = NULL; + } + + ast_string_field_set(dialog, remote_tag, NULL); + + /* Treat this as if there were a call forward instead.. */ + ast_channel_call_forward_set(dialog->channel, user); + ast_queue_control(dialog->channel, AST_CONTROL_BUSY); + } + + return -1; + } + + if (!request->ignore && dialog->pending_invite_cseq) { + if (!dialog->outgoing && + (dialog->invite_state == SIP_INVITE_COMPLETED || dialog->invite_state == SIP_INVITE_TERMINATED)) { + /* What do these circumstances mean? We have received an INVITE for an "incoming" dialog for + * which we have sent a final response. We have not yet received an ACK, though (which is why + * dialog->pending_invite_cseq is non-zero). + * We also know that the INVITE is not a retransmission, because otherwise the "ignore" flag + * would be set. This means that either we are receiving a reinvite for a terminated dialog, + * or we are receiving an INVITE with credentials based on one we challenged earlier. + * The action to take in either case is to treat the INVITE as though it contains an implicit + * ACK for the previous transaction. Calling sip_packet_ack will take care of this by clearing + * dialog->pending_invite_cseq and removing the response from the previous transaction from the + *list of outstanding packets */ + sip_packet_ack(dialog, SIP_METHOD_INVITE, dialog->pending_invite_cseq, TRUE); + } else { + /* We already have a pending invite. Sorry. You are on hold */ + dialog->glare_invite_cseq = request->cseq; + + sip_response_send_reliable(dialog, "491 Request Pending", request); + sip_dialog_check_via(dialog, request); + + ast_debug(1, "Received INVITE on '%s' where we already have pending INVITE, deferring that\n", + dialog->call_id); + return -1; /* Don't destroy dialog here */ + } + } + + /* These will be set if we are doing an INVITE with Replaces */ + call_id = NULL; + pickup_exten[0] = '\0'; + pickup_context[0] = '\0'; + + replaces = ast_strdupa(sip_message_find_header(request, "Replaces")); + + /* We have a Replaces header, either a transfer or call pickup */ + if (!ast_strlen_zero(replaces)) { + char *from_tag, *to_tag; + int error; + + if (dialog->channel) { + ast_debug(3, "INVITE with Replaces: on '%s' but we already have channel, refusing action\n", + dialog->call_id); + + /* The best way to not accept the transfer */ + sip_response_send_reliable(dialog, "400 Bad Request", request); + sip_dialog_check_via(dialog, request); + sip_message_copy(&dialog->initial_request, request); + + /* Do not destroy existing call */ + return -1; + } + + call_id = strsep(&replaces, ";"); + sip_parse_parameters(replaces, ';', "from-tag", &from_tag, "to-tag", &to_tag, NULL); + + ast_debug(4, "Invite/replaces: Will use Call-ID: '%s' From: tag='%s' To: tag='%s'\n", + call_id, from_tag, to_tag); + + /* Try to find call that we are replacing. If we have a Replaces header, we need to cancel that call + * if we succeed with this call. First we cheat a little and look for a magic call-id from phones that + * support dialog-info+xml so we can do technology independent pickup. */ + error = FALSE; + + if (!strncmp(call_id, "pickup-", 7)) { + RAII_VAR(struct sip_dialog *, pickup_dialog, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, pickup_channel, NULL, ao2_cleanup); + + call_id += 7; /* Worst case we are looking at \0 */ + + if (!(pickup_dialog = sip_dialog_find_with_channel(call_id, to_tag, from_tag, &pickup_channel))) { + ast_debug(1, "Unable to find dialog '%s'\n", call_id); + + sip_response_send_reliable(dialog, "481 Call/Transaction Does Not Exist", request); + error = TRUE; + } else { + ao2_lock(pickup_dialog); + ast_debug(1, "Trying to pick-up '%s@%s'\n", + pickup_dialog->to_user, pickup_dialog->peer->context); + + ast_copy_string(pickup_exten, pickup_dialog->to_user, sizeof(pickup_exten)); + ast_copy_string(pickup_context, pickup_dialog->peer->context, sizeof(pickup_context)); + + ao2_unlock(pickup_dialog); + } + } + + if (!error && ast_strlen_zero(pickup_exten) && + !(transfer_dialog = sip_dialog_find_with_channel(call_id, to_tag, from_tag, &transfer_channel))) { + ast_debug(1, "Supervised transfer attempted on '%s' tried to replace non-existent call\n", + call_id); + + sip_response_send_reliable(dialog, "481 Call/Transaction Does Not Exist", request); + error = TRUE; + } + + /* The matched call is the call from the transferer to Asterisk. We want to bridge the bridged part of + * the call to the incoming invite, thus taking over the refered call */ + if (transfer_dialog == dialog) { + ast_debug(1, "INVITE with Replaces: into it's own call '%s'\n", dialog->call_id); + + /* The best way to not accept the transfer */ + sip_response_send_reliable(dialog, "400 Bad Request", request); + error = TRUE; + } + + if (!error && ast_strlen_zero(pickup_exten) && !transfer_channel) { + /* Oops, someting wrong anyway, no owner, no call */ + ast_debug(1, "Supervised transfer attempted to replace non-existing call '%s'\n", call_id); + + /* Check for better return code */ + sip_response_send_reliable(dialog, "481 Call/Transaction Does Not Exist", request); + error = TRUE; + } + + /* Check the DOWN state as well because some SIP devices do not give 180 ringing when they can just give + * 183 session progress instead. same fix the one in ast_can_pickup */ + if (!error && ast_strlen_zero(pickup_exten) && + ast_channel_state(transfer_channel) != AST_STATE_RINGING && + ast_channel_state(transfer_channel) != AST_STATE_RING && + ast_channel_state(transfer_channel) != AST_STATE_UP && + ast_channel_state(transfer_channel) != AST_STATE_DOWN) { + ast_debug(1, "Supervised transfer attempted to replace non-ringing or active call '%s'\n", + call_id); + + sip_response_send_reliable(dialog, "603 Decline", request); + error = TRUE; + } + + if (error) { /* Give up this dialog */ + dialog->invite_state = SIP_INVITE_COMPLETED; + sip_dialog_sched_destroy(dialog, dialog->timer_b); + + sip_dialog_check_via(dialog, request); + sip_message_copy(&dialog->initial_request, request); + return -1; + } + } + + /* Check if this is an INVITE that sets up a new dialog or a re-invite in an existing dialog */ + if (!request->ignore) { + /* This also counts as a pending invite */ + dialog->pending_invite_cseq = request->cseq; + + sip_dialog_cancel_destroy(dialog); + sip_dialog_check_via(dialog, request); + + ast_debug(1, "Set initial INVITE request for '%s'\n", dialog->call_id); + + sip_message_copy(&dialog->initial_request, request); /* Save this INVITE as the transaction basis */ + /* Parse new contact both for existing (re-invite) and new calls */ + sip_dialog_set_contact(dialog, request); + + if (!dialog->channel) { /* Not a re-invite */ + ast_debug(1, "Using INVITE request as basis request for '%s'\n", dialog->call_id); + } else { /* Re-invite on existing call */ + dialog->outgoing = FALSE; /* This is now an inbound dialog */ + + if (sip_parse_identity(dialog, request)) { + sip_dialog_queue_connected_line(dialog, AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER); + } + + /* Handle SDP here if we already have an owner */ + if (sip_sdp_find(request)) { + if (sip_sdp_parse(dialog, request, SIP_SDP_FAX_INITIATE, TRUE)) { + if (!ast_strlen_zero(sip_message_find_header(request, "Content-Encoding"))) { + /* Asterisk does not yet support any Content-Encoding methods. Always + * attempt to process the sdp, but return a 415 if a Content-Encoding + * header was present after processing failed */ + sip_response_send_reliable(dialog, "415 Unsupported Media Type", request); + } else { + sip_response_send_reliable(dialog, "488 Not Acceptable Here", request); + } + + if (!dialog->last_invite_cseq) { + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } + + return -1; + } + + ast_queue_control(dialog->channel, AST_CONTROL_SRCUPDATE); + } else { + ast_format_cap_remove_by_type(dialog->joint_format_cap, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(dialog->joint_format_cap, + dialog->format_cap, AST_MEDIA_TYPE_UNKNOWN); + + /* Some devices signal they want to be put off hold by sending a re-invite *without* an + * SDP, which is supposed to mean "Go back to your state" and since they put us on remote + * hold, we go back to off hold */ + if (dialog->onhold) { + ast_queue_unhold(dialog->channel); + ast_queue_frame(dialog->channel, &ast_null_frame); /* Activate a re-invite */ + + sip_dialog_change_onhold(dialog, SIP_ONHOLD_SENDRECV); + } + } + } + } else if (request->debug) { + ast_debug(1, "Ignoring INVITE request '%s'\n", request->call_id); + } + + reinvite = FALSE; + + if (!dialog->last_invite_cseq && !request->ignore && !dialog->channel) { + char *contact; + int res; + + /* This is a new invite. Handle authentication if this is our first invite */ + res = sip_dialog_handle_authorization(dialog, request, SIP_SEND_RELIABLE); + + if (res == SIP_AUTHORIZATION_CHALLENGE_SENT) { + /* Needs to restart in another INVITE transaction */ + dialog->invite_state = SIP_INVITE_COMPLETED; + return 0; + } else if (res != SIP_AUTHORIZATION_SUCCESS) { + /* Something failed in authentication */ + dialog->invite_state = SIP_INVITE_COMPLETED; + sip_response_send_with_authorization_failure(dialog, request, res, SIP_SEND_RELIABLE); + + sip_dialog_sched_destroy(dialog, dialog->timer_b); + sip_security_event(dialog, request, res); + return 0; + } + + /* We have a successful authentication, process the SDP portion if there is one */ + if (sip_sdp_find(request)) { + if (sip_sdp_parse(dialog, request, SIP_SDP_FAX_INITIATE, TRUE)) { + ast_debug(1, "No compatible codecs for this call\n"); + dialog->invite_state = SIP_INVITE_COMPLETED; + + /* Asterisk does not yet support any Content-Encoding methods. Always attempt to + * process the sdp, but return a 415 if a Content-Encoding header was present after + * processing fails */ + if (!ast_strlen_zero(sip_message_find_header(request, "Content-Encoding"))) { + sip_response_send_reliable(dialog, "415 Unsupported Media Type", request); + } else { + /* Unacceptable codecs */ + sip_response_send_reliable(dialog, "488 Not Acceptable Here", request); + } + + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + } else { + /* No SDP in invite, call control session */ + ast_format_cap_remove_by_type(dialog->joint_format_cap, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(dialog->joint_format_cap, + dialog->format_cap, AST_MEDIA_TYPE_UNKNOWN); + + ast_debug(2, "No SDP in INVITE, third party call control\n"); + } + + /* Check number of concurrent calls -vs- incoming limit HERE */ + ast_debug(1, "Checking max calls for peer '%s'\n", dialog->peer->name); + + if (sip_dialog_change_inuse(dialog, SIP_INUSE_ADD)) { + ast_verb(3, "Call from SIP peer '%s' rejected because maximum limit of %d has been reached\n", + dialog->peer->name, dialog->peer->max_calls); + + dialog->invite_state = SIP_INVITE_COMPLETED; + sip_response_send_reliable(dialog, "480 Temporarily Unavailable", request); + + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return SIP_AUTHORIZATION_SESSION_LIMIT; + } + + contact = ast_strdupa(sip_message_find_header(request, "Contact")); + contact = sip_get_uri(contact); + + if (!ast_strlen_zero(contact)) { + char *uri = strsep(&contact, ";"); + + ast_string_field_set(dialog, uri, uri); + } + + sip_dialog_build_contact(dialog, request); /* Build our contact header */ + res = sip_dialog_get_destination(dialog, request); /* Get destination right away */ + + /* No matching extension found */ + if (ast_strlen_zero(call_id) && res != SIP_DESTINATION_EXTEN_FOUND) { + dialog->invite_state = SIP_INVITE_COMPLETED; + + switch (res) { + case SIP_DESTINATION_INVALID_URI: + sip_response_send_reliable(dialog, "416 Unsupported URI Scheme", request); + break; + case SIP_DESTINATION_EXTEN_MATCH_MORE: + if (dialog->peer->allow_overlap == SIP_ALLOW_OVERLAP_INVITE) { + sip_response_send_reliable(dialog, "484 Address Incomplete", request); + break; + } + + /* We would have to implement collecting more digits in chan_sip for any other schemes + * of overlap dialing. For SIP_ALLOW_OVERLAP_DTMF it is better to do this in the + * dialplan using the Incomplete application rather than having the channel driver do + * it */ + + /* Fall through */ + case SIP_DESTINATION_EXTEN_NOT_FOUND: + sip_response_send_reliable(dialog, "404 Not Found", request); + ast_verb(3, "Call from SIP peer '%s' to '%s' rejected because extension not found in context '%s'\n", + dialog->peer->name, dialog->to_user, dialog->peer->context); + + sip_security_event(dialog, request, SIP_AUTHORIZATION_NOT_FOUND); + break; + case SIP_DESTINATION_REFUSED: + default: + sip_response_send_reliable(dialog, "403 Forbidden", request); + } + + sip_dialog_change_inuse(dialog, SIP_INUSE_REMOVE); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + + /* If no extension was specified, use the s one Basically for calling to IP/Host name only */ + if (ast_strlen_zero(dialog->to_user)) { + ast_string_field_set(dialog, to_user, "s"); + } + + /* Initialize our tag */ + sip_dialog_build_local_tag(dialog); + + if (sip_session_timer_handle_invite(dialog, request, reinvite)) { + dialog->invite_state = SIP_INVITE_COMPLETED; + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + + /* First invitation - create the channel. Allocation failures are handled below */ + sip_dialog_alloc_channel(dialog, AST_STATE_DOWN, S_OR(dialog->peer->name, NULL), NULL, NULL, + dialog->logger_callid); + *recount = TRUE; + + /* Save Record-Route for any later requests we make on this dialogue */ + sip_dialog_build_route(dialog, request, FALSE); + + if (dialog->channel) { + sip_parse_diversion(dialog, request, FALSE); + sip_set_redirecting(dialog); + } + } else { + ast_debug(2, "Recevied re-transmit of INVITE for '%s'\n", dialog->call_id); + + if (!request->ignore) { + reinvite = TRUE; + } + + if (sip_session_timer_handle_invite(dialog, request, reinvite)) { + dialog->invite_state = SIP_INVITE_COMPLETED; + + if (!dialog->last_invite_cseq) { + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } + + return -1; + } + + sip_parse_diversion(dialog, request, FALSE); + + if (dialog->channel) { + sip_set_redirecting(dialog); + } + } + + if (reinvite && dialog->session_timer_active) { + sip_session_timer_restart(dialog); + } + + if (!request->ignore) { + dialog->last_invite_cseq = request->cseq; + } + + /* Attended transfer or call pickup - we're the target */ + if (dialog->channel && !ast_strlen_zero(call_id)) { + if (!ast_strlen_zero(pickup_exten)) { + dialog->invite_state = SIP_INVITE_PROCEEDING; + /* Let the caller know we're giving it a shot */ + sip_response_send(dialog, "100 Trying", request); + + ast_setstate(dialog->channel, AST_STATE_RING); + ast_channel_unlock(dialog->channel); + + *no_unlock = TRUE; + + /* Since dialog->channel is unlocked, we need to go ahead and unlock dialog for both magic + * pickup and ast_hangup. Both of these functions will attempt to lock dialog->channel again, + * which can cause a deadlock if we already hold a lock on dialog. + * Locking order is, channel then dialog. Dead lock avoidance must be used if called the other + * way around */ + ao2_unlock(dialog); + sip_pickup_exten(dialog->channel, pickup_exten, pickup_context); + + /* Now we're either masqueraded or we failed to pickup, in either case we.. */ + ast_hangup(dialog->channel); + ao2_lock(dialog); /* Dialog is expected to remain locked on return, so re-lock it */ + return -1; + } else { + /* Go and take over the target call */ + ast_debug(2, "Sending '%s' to the INVITE replaces handler\n", dialog->call_id); + return sip_handle_invite_replaces(dialog, request, no_unlock, transfer_dialog, transfer_channel); + } + } + + if (dialog->channel) { /* We have a call either a new call or an old one (RE-INVITE) */ + enum ast_channel_state state; + struct ast_features_pickup_config *pickup_config; + int pickup; + + state = ast_channel_state(dialog->channel); + + if (state != AST_STATE_UP && reinvite && + (dialog->invite_state == SIP_INVITE_TERMINATED || dialog->invite_state == SIP_INVITE_CONFIRMED)) { + /* If these conditions are true, and the channel is still in the 'ringing' state, then this + * likely means that we have a situation where the initial INVITE transaction has completed + * *but* the channel's state has not yet been changed to UP. The reason this could happen is + * if the reinvite is received on the SIP socket prior to an application calling ast_read on + * this channel to read the answer frame we earlier queued on it. In this case, the reinvite + * is completely legitimate so we need to handle this the same as if the channel were already + * UP. Thus we are purposely falling through to the AST_STATE_UP case */ + state = AST_STATE_UP; + } + + switch (state) { + case AST_STATE_DOWN: + ast_debug(2, "Channel '%s' is still DOWN, sending trying\n", ast_channel_name(dialog->channel)); + + dialog->invite_state = SIP_INVITE_PROCEEDING; + sip_response_send_provisional(dialog, "100 Trying", request, FALSE); + + ast_setstate(dialog->channel, AST_STATE_RING); + + if ((pickup_config = ast_get_chan_features_pickup_config(dialog->channel))) { + pickup = !strcmp(dialog->to_user, pickup_config->pickupexten); + ao2_ref(pickup_config, -1); + } else { + pickup = FALSE; + ast_log(LOG_ERROR, "Unable to retrieve pickup extension for '%s'\n", + ast_channel_name(dialog->channel)); + } + + if (pickup) { + if (!sip_pickup_call(dialog->channel)) { /* Pickup in call group */ + return 0; + } + + dialog->invite_state = SIP_INVITE_COMPLETED; + sip_response_send_reliable(dialog, "480 Temporarily Unavailable", request); + + ast_debug(1, "Failed to start pickup on '%s'\n", ast_channel_name(dialog->channel)); + } else { + enum ast_pbx_result pbx_res; + + dialog->invite_state = SIP_INVITE_COMPLETED; + + /* Call to extension start PBX */ + if ((pbx_res = ast_pbx_start(dialog->channel)) == AST_PBX_SUCCESS) { + return 0; + } + + if (pbx_res == AST_PBX_CALL_LIMIT) { + sip_response_send_reliable(dialog, "480 Temporarily Unavailable", request); + ast_verb(3, "Failed to start PBX on '%s' because call limit is reached\n", + ast_channel_name(dialog->channel)); + } else { + sip_response_send_reliable(dialog, "503 Service Unavailable", request); + ast_log(LOG_ERROR, "Failed to start PBX on '%s'\n", + ast_channel_name(dialog->channel)); + } + } + + sip_dialog_set_already_gone(dialog); + ast_channel_hangupcause_set(dialog->channel, AST_CAUSE_FAILURE); + + /* Unlock locks so ast_hangup can do its magic */ + ast_channel_unlock(dialog->channel); + *no_unlock = TRUE; + + ao2_unlock(dialog); + ast_hangup(dialog->channel); + ao2_lock(dialog); + break; + case AST_STATE_RING: + dialog->invite_state = SIP_INVITE_PROCEEDING; + sip_response_send_provisional(dialog, "100 Trying", request, FALSE); + break; + case AST_STATE_RINGING: + dialog->invite_state = SIP_INVITE_PROCEEDING; + sip_response_send_provisional(dialog, "180 Ringing", request, FALSE); + break; + case AST_STATE_UP: + dialog->invite_state = SIP_INVITE_COMPLETED; + sip_response_send(dialog, "100 Trying", request); + + if (dialog->fax_state == SIP_FAX_REMOTE_REINVITE) { + sip_fax_start_abort(dialog); + } else if (dialog->fax_state == SIP_FAX_ENABLED) { + dialog->established = TRUE; + + sip_response_send_with_sdp(dialog, "200 OK", request, + (reinvite ? SIP_SEND_RELIABLE : (request->ignore ? SIP_SEND_UNRELIABLE : SIP_SEND_CRITICAL)), + FALSE, FALSE); + } else { + /* If this is not a re-invite or something to ignore - it's critical */ + if (dialog->secure_audio_rtp && !ast_test_flag(dialog->secure_audio_rtp, AST_SRTP_CRYPTO_OFFER_OK)) { + ast_verb(3, "SIP call '%s' does not support encryption\n", + ast_channel_name(dialog->channel)); + sip_response_send_reliable(dialog, "488 Not Acceptable Here", request); + } else { + dialog->established = TRUE; + + sip_response_send_with_sdp(dialog, "200 OK", request, + (reinvite ? SIP_SEND_RELIABLE : (request->ignore ? SIP_SEND_UNRELIABLE : SIP_SEND_CRITICAL)), + dialog->sdp_changed ? FALSE : TRUE, FALSE); + ast_queue_control(dialog->channel, AST_CONTROL_UPDATE_RTP_PEER); + } + } + + break; + default: + dialog->invite_state = SIP_INVITE_PROCEEDING; + sip_response_send(dialog, "100 Trying", request); + break; + } + } else if (!request->ignore && dialog->auto_destroy_sched_id == -1) { + dialog->invite_state = SIP_INVITE_COMPLETED; + + if (!ast_format_cap_count(dialog->joint_format_cap)) { + sip_response_send_reliable(dialog, "488 Not Acceptable Here", request); + } else { + ast_debug(1, "Unable to create/find channel for this INVITE\n"); + sip_response_send_reliable(dialog, "503 Service Unavailable", request); + } + + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } + + return 0; +} + +static int sip_handle_request_ack(struct sip_dialog *dialog, struct sip_message *request) +{ + /* Make sure we don't ignore this */ + if (request->cseq == dialog->pending_invite_cseq) { + dialog->invite_state = SIP_INVITE_TERMINATED; + dialog->pending_invite_cseq = 0; + + sip_packet_ack(dialog, SIP_METHOD_INVITE, request->cseq, TRUE); + + if (dialog->channel && sip_sdp_find(request)) { + if (sip_sdp_parse(dialog, request, SIP_SDP_FAX_IGNORE, FALSE)) { + return -1; + } + + if (dialog->direct_media & SIP_DIRECT_MEDIA_NO_NAT) { + ast_queue_control(dialog->channel, AST_CONTROL_UPDATE_RTP_PEER); + } + } + + sip_dialog_sched_check_pending(dialog); + } else if (request->cseq == dialog->glare_invite_cseq) { + /* Handle ACK for the 491 pending sent for glare invite */ + dialog->glare_invite_cseq = 0; + sip_packet_ack(dialog, SIP_METHOD_INVITE, request->cseq, TRUE); + } + + if (!dialog->last_invite_cseq && ast_strlen_zero(dialog->nonce)) { + sip_dialog_set_need_destroy(dialog, "unmatched ACK"); + return -1; + } + + return 0; +} + +/* Bare-bones support for SIP UPDATE. This is not even close to being RFC 3311-compliant. We don't advertise that we + * support the UPDATE method, so no one should ever try sending us an UPDATE anyway. However, Asterisk can send an + * UPDATE to change connected line information, so we need to be prepared to handle this. Actually updating the media + * session may be some future work */ +static int sip_handle_request_update(struct sip_dialog *dialog, struct sip_message *request) +{ + if (!dialog->channel) { + sip_response_send(dialog, "503 Service Unavailable", request); + return -1; + } + + if (sip_parse_identity(dialog, request)) { + sip_dialog_queue_connected_line(dialog, AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER); + } + + sip_response_send(dialog, "200 OK", request); + return 0; +} + +/* Handle incoming CANCEL request */ +static int sip_handle_request_cancel(struct sip_dialog *dialog, struct sip_message *request) +{ + struct sip_packet *packet; + + sip_dialog_check_via(dialog, request); + sip_dialog_set_already_gone(dialog); + + if (dialog->channel && ast_channel_state(dialog->channel) == AST_STATE_UP) { + /* This call is up, cancel is ignored, we need a bye */ + sip_response_send(dialog, "200 OK", request); + ast_debug(1, "Received CANCEL on an answered call, ignoring\n"); + return 0; + } + + sip_parse_reason(dialog, request); + + /* At this point, we could have cancelled the invite at the same time as the other side sends a CANCEL. Our + * final reply with error code might not have been received by the other side before the CANCEL was sent, so + * let's just give up retransmissions and waiting for ACK on our error code. The call is hanging up any way */ + if (dialog->invite_state == SIP_INVITE_TERMINATED || dialog->invite_state == SIP_INVITE_COMPLETED) { + sip_packet_pretend_ack(dialog); + } + + if (dialog->invite_state != SIP_INVITE_TERMINATED) { + dialog->invite_state = SIP_INVITE_CANCELLED; + } + + if (dialog->inuse || dialog->onhold) { + sip_dialog_change_inuse(dialog, SIP_INUSE_REMOVE); + } + + sip_dialog_stop_rtp(dialog); /* Immediately stop RTP, VRTP and UDPTL as applicable */ + + if (dialog->channel) { + sip_queue_hangup_cause(dialog, ast_channel_hangupcause(dialog->channel)); + } else { + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } + + if (ast_strlen_zero(dialog->initial_request.uri)) { + sip_response_send(dialog, "481 Call Leg/Transaction Does Not Exist", request); + return -1; + } + + /* If the CANCEL we are receiving is a retransmission, and we already have scheduled a reliable 487, then we + * don't want to schedule another one on top of the previous one. As odd as this may sound, we can't rely on the + * previously-transmitted "reliable" response in this situation. What if we've sent all of our reliable + * responses already and now all of a sudden, we get this second CANCEL? + * The only way to do this correctly is to cancel our previously-scheduled reliably-transmitted response and + * send a new one in its place.*/ + AST_LIST_TRAVERSE_SAFE_BEGIN(&dialog->packet_queue, packet, next) { + if (packet->cseq == dialog->last_invite_cseq && packet->code == 487) { + /* Unlink and destroy the packet object */ + AST_LIST_REMOVE_CURRENT(next); + + sip_dialog_cancel_resend(packet); + ao2_ref(packet, -1); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + /* Cisco phones fail to include the To tag in the ACK response */ + if (dialog->peer->cisco_mode) { + sip_response_send(dialog, "487 Request Terminated", &dialog->initial_request); + } else { + sip_response_send_reliable(dialog, "487 Request Terminated", &dialog->initial_request); + } + + sip_response_send(dialog, "200 OK", request); + return 0; +} + +/* Handle incoming BYE request */ +static int sip_handle_request_bye(struct sip_dialog *dialog, struct sip_message *request) +{ + /* If we have an INCOMING invite that we haven't answered, terminate that transaction */ + if (dialog->pending_invite_cseq && !dialog->outgoing && !request->ignore) { + sip_response_send_reliable(dialog, "487 Request Terminated", &dialog->initial_request); + } + + sip_packet_pretend_ack(dialog); + sip_message_copy(&dialog->initial_request, request); + + ast_debug(1, "Set initial BYE request for '%s'\n", dialog->call_id); + + sip_dialog_check_via(dialog, request); + sip_dialog_set_already_gone(dialog); + + /* Get RTCP quality before end of call */ + if (dialog->channel) { + char field[AST_MAX_USER_FIELD], *quality; + + sip_queue_hangup_cause(dialog, ast_channel_hangupcause(dialog->channel)); + + if (dialog->audio_rtp) { + RAII_VAR(struct ast_channel *, channel, NULL, ast_channel_cleanup); + RAII_VAR(struct ast_channel *, bridge_channel, NULL, ast_channel_cleanup); + RAII_VAR(struct ast_channel *, relocked_channel, NULL, ast_channel_cleanup); + + /* Grab a reference to dialog->channel to prevent it from going away */ + channel = ast_channel_ref(dialog->channel); + + /* Established locking order here is bridge, channel, dialog and the bridge and channel will + * be locked during ast_rtp_instance_set_stats_vars */ + ast_channel_unlock(channel); + ao2_unlock(dialog); + + ast_rtp_instance_set_stats_vars(channel, dialog->audio_rtp); + + if ((bridge_channel = ast_channel_bridge_peer(channel))) { + ast_channel_lock(bridge_channel); + + if (ast_channel_tech(bridge_channel) == &sip_channel_tech) { + struct sip_dialog *bridge_dialog = ast_channel_tech_pvt(bridge_channel); + + if (bridge_dialog) { + ao2_ref(bridge_dialog, +1); + ao2_lock(bridge_dialog); + + if (bridge_dialog->audio_rtp) { + struct ast_rtp_instance *audio_rtp = bridge_dialog->audio_rtp; + + ao2_ref(audio_rtp, +1); + ast_channel_unlock(bridge_channel); + ao2_unlock(bridge_dialog); + + ast_rtp_instance_set_stats_vars(bridge_channel, audio_rtp); + + ao2_ref(audio_rtp, -1); + ast_channel_lock(bridge_channel); + ao2_lock(bridge_dialog); + } + + ao2_unlock(bridge_dialog); + ao2_ref(bridge_dialog, -1); + } + } + + ast_channel_unlock(bridge_channel); + } + + if (!(relocked_channel = sip_dialog_lock_with_channel(dialog))) { + ast_debug(3, "Unable to reaquire owner channel lock, channel is gone\n"); + return -1; + } + } + + if (dialog->video_rtp && (quality = ast_rtp_instance_get_quality(dialog->video_rtp, + AST_RTP_INSTANCE_STAT_FIELD_QUALITY, field, sizeof(field)))) { + pbx_builtin_setvar_helper(dialog->channel, "RTPVIDEOQOS", quality); + } + + if (dialog->text_rtp && (quality = ast_rtp_instance_get_quality(dialog->text_rtp, + AST_RTP_INSTANCE_STAT_FIELD_QUALITY, field, sizeof(field)))) { + pbx_builtin_setvar_helper(dialog->channel, "RTPTEXTQOS", quality); + } + } + + sip_dialog_stop_rtp(dialog); /* Immediately stop RTP, VRTP and UDPTL as applicable */ + sip_session_timer_stop(dialog); /* Stop Session-Timer */ + sip_parse_reason(dialog, request); + + dialog->invite_state = SIP_INVITE_TERMINATED; + dialog->established = FALSE; + + if (!dialog->destroy_scheduled) { + sip_dialog_sched_destroy(dialog, dialog->timer_b); + dialog->destroy_scheduled = TRUE; + } + + /* If there are any options required that we do not support, then send a 420 with only those unsupported + * options listed */ + if (sip_parse_require(dialog, request)) { + sip_response_send_with_unsupported(dialog, request, dialog->unsupported_options); + ast_debug(1, "Received BYE with unsupported required extension '%s'\n", dialog->unsupported_options); + } else { + sip_response_send(dialog, "200 OK", request); + } + + sip_parse_rtp_stats(dialog, request); + /* Destroy any pending invites so we won't try to do another scheduled reINVITE */ + sip_dialog_stop_need_reinvite(dialog); + return 0; +} + +/* Handle incoming SUBSCRIBE request */ +static int sip_handle_request_subscribe(struct sip_dialog *dialog, struct sip_message *request) +{ + const char *event, *expires; + char *accept; + int resubscribe; + + resubscribe = dialog->subscribe_event && !request->ignore; + + if (!ast_strlen_zero(dialog->initial_request.uri)) { + /* We already have a dialog */ + if (dialog->initial_request.method != SIP_METHOD_SUBSCRIBE) { + /* This is a SUBSCRIBE within another SIP dialog, which we do not support For transfers, this + * could happen, but since we haven't seen it happening, let us just refuse this */ + sip_response_send(dialog, "403 Forbidden", request); + + /* Do not destroy session, since we will break the call if we do */ + ast_debug(1, "Received a SUBSCRIBE on '%s' within the context of an existing call, can't handle that'\n", + dialog->call_id); + return 0; + } else if (request->debug) { + if (resubscribe) { + ast_debug(1, "Received re-SUBSCRIBE '%s' on existing dialog\n", dialog->call_id); + } else { + ast_debug(1, "Received new SUBSCRIBE '%s' possibly with auth or retransmission\n", + dialog->call_id); + } + } + } + + if (!request->ignore && !resubscribe) { /* Set up dialog, new subscription */ + /* Check to see if a tag was provided, if so this is actually a resubscription of a dialog we no longer + * know about */ + if (!ast_strlen_zero(request->to_tag)) { + ast_debug(1, "Received resubscription for a dialog we no longer know about. Telling remote side to subscribe again\n"); + + sip_response_send(dialog, "481 Call Leg/Transaction Does Not Exist", request); + sip_dialog_set_need_destroy(dialog, "subscription does not exist"); + return -1; + } + + /* Use this as the basis */ + if (request->debug) { + ast_verb(3, "Creating new subscription\n"); + } + + sip_message_copy(&dialog->initial_request, request); + ast_debug(4, "Set initial SUBSCRIBE request for '%s'\n", dialog->call_id); + + sip_dialog_check_via(dialog, request); + sip_dialog_build_route(dialog, request, FALSE); + } else if (request->debug && request->ignore) { + ast_verb(3, "Ignoring this SUBSCRIBE request\n"); + } + + event = sip_message_find_header(request, "Event"); + + if (ast_strlen_zero(event)) { + sip_response_send(dialog, "489 Bad Event", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + + /* Handle authentication if we're new and not a retransmission. We can't just use if !request->ignore, because + * then we'll end up sending a 200 OK if someone retransmits without sending auth */ + if (!dialog->subscribe_event || resubscribe) { + int res = sip_dialog_handle_authorization(dialog, request, SIP_SEND_UNRELIABLE); + + /* If an authentication response was sent, we are done here */ + if (res == SIP_AUTHORIZATION_CHALLENGE_SENT) { + return 0; + } else if (res != SIP_AUTHORIZATION_SUCCESS) { + sip_response_send_with_authorization_failure(dialog, request, res, SIP_SEND_UNRELIABLE); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return 0; + } + } + + /* Check if this device is allowed to subscribe at all */ + if (!dialog->peer->allow_subscribe) { + sip_response_send(dialog, "403 Forbidden", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + + /* Get full contact header - this needs to be used as a request URI in NOTIFY's */ + sip_dialog_set_contact(dialog, request); + sip_dialog_build_contact(dialog, request); + + /* Initialize tag for new subscriptions */ + if (ast_strlen_zero(dialog->local_tag)) { + sip_dialog_build_local_tag(dialog); + } + + expires = sip_message_find_header(request, "Expires"); + + if (ast_strlen_zero(expires)) { + dialog->expires = sip_config.default_expires; + } else { + dialog->expires = atoi(expires); + + /* Check if the requested expires is within the approved limits from sip.conf */ + if (dialog->expires > sip_config.subscribe_max_expires) { + dialog->expires = sip_config.subscribe_max_expires; + } else if (dialog->expires && dialog->expires < sip_config.subscribe_min_expires) { + sip_response_send_with_min_expires(dialog, request, sip_config.subscribe_min_expires); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + } + + if (!request->ignore) { + dialog->last_invite_cseq = request->cseq; + } + + accept = ast_strdupa(sip_message_find_header(request, "Accept")); + accept = strsep(&accept, ","); + + if (!strcmp(event, "presence") || !strcmp(event, "dialog")) { /* Presence, RFC 3842. Dialog, RFC 4235 */ + struct ast_state_cb_info state_info; + RAII_VAR(struct ao2_container *, device_state_info, NULL, ao2_cleanup); + RAII_VAR(char *, subtype, NULL, ast_free_ptr); + RAII_VAR(char *, message, NULL, ast_free_ptr); + int res, subscribe_event; + + /* Get destination right away */ + res = sip_dialog_get_destination(dialog, &dialog->initial_request); + + if (res != SIP_DESTINATION_EXTEN_FOUND) { + if (res == SIP_DESTINATION_INVALID_URI) { + sip_response_send(dialog, "416 Unsupported URI Scheme", request); + } else { + sip_response_send(dialog, "404 Not Found", request); + } + + sip_dialog_set_need_destroy(dialog, "subscription exten not found"); + return -1; + } + + subscribe_event = SIP_SUBSCRIBE_NONE; + + if (!ast_strlen_zero(accept)) { + if (!strcmp(event, "dialog")) { + if (!strcmp(accept, "application/dialog-info+xml")) { + subscribe_event = SIP_SUBSCRIBE_DIALOG; + } + } else if (!strcmp(event, "presence")) { + /* Cisco phones ask for cpim-pidf+xml but actually want pidf+xml, duh */ + if (!strcmp(accept, "application/pidf+xml") || !strcmp(accept, "application/cpim-pidf+xml")) { + subscribe_event = SIP_SUBSCRIBE_PRESENCE; + } + } + } + + /* If dialog->subscribe_event is non-zero, then accept is not obligatory; according to RFC3265 + * section 3.1.3, at least. so, we'll just let it ride, keeping the value from a previous subscription, + * and not abort the subscription */ + if (ast_strlen_zero(accept)) { + if (!dialog->subscribe_event) { + ast_debug(1, "No Accept header\n"); + + sip_response_send(dialog, "489 Bad Event", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + } else if (!subscribe_event) { + ast_debug(1, "Unrecognized format for %s event '%s'\n", event, accept); + + /* Can't find event+format that we know about */ + sip_response_send(dialog, "489 Bad Event", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } else { + dialog->subscribe_event = subscribe_event; + } + + if (dialog->expires && !resubscribe) { + /* Add subscription for extension state from the PBX core */ + if (dialog->extension_state_id != -1) { + ast_extension_state_del(dialog->extension_state_id, sip_extension_state_event); + } + + if ((dialog->extension_state_id = ast_extension_state_add_destroy_extended(S_OR(dialog->peer->subscribe_context, dialog->peer->context), + dialog->to_user, sip_extension_state_event, sip_extension_state_destroy, ao2_bump(dialog))) == -1) { + ao2_ref(dialog, -1); + } + } + + ao2_unlock(dialog); + + state_info.reason = 0; + state_info.exten_state = ast_extension_state_extended(NULL, + S_OR(dialog->peer->subscribe_context, dialog->peer->context), dialog->to_user, &device_state_info); + state_info.device_state_info = device_state_info; + + state_info.presence_state = ast_hint_presence_state(NULL, + S_OR(dialog->peer->subscribe_context, dialog->peer->context), dialog->to_user, &subtype, &message); + state_info.presence_subtype = subtype; + state_info.presence_message = message; + + ao2_lock(dialog); + + if (state_info.exten_state == -1) { + if (dialog->expires) { + ast_debug(1, "Received SUBSCRIBE for extension '%s@%s' from '%s' but there is no hint for that extension\n", + dialog->to_user, S_OR(dialog->peer->subscribe_context, dialog->peer->context), + ast_sockaddr_stringify(&dialog->address)); + } + + sip_response_send(dialog, "404 Not Found", request); + sip_dialog_set_need_destroy(dialog, "no hint for subscription"); + return -1; + } + + dialog->established = TRUE; + dialog->force_state_change = TRUE; + + if (state_info.exten_state & AST_EXTENSION_RINGING) { + RAII_VAR(struct ast_channel *, channel, NULL, ast_channel_cleanup); + + /* Save last_ringing_time if this state really contains a ringing channel because + * sip_extension_state_event() doesn't do it if forced. If there is no channel, this likely + * indicates that the ringing indication is due to a custom device state. These do not have + * associated channels */ + if ((channel = sip_find_ringing_channel(state_info.device_state_info))) { + dialog->last_ringing_time = ast_channel_creationtime(channel); + } + } + + ast_debug(2, "%s extension state subscription %s@%s for %s\n", + dialog->expires ? "Adding" : "Removing", dialog->to_user, dialog->peer->context, + dialog->peer->name); + + sip_response_send(dialog, "200 OK", request); + sip_extension_state_event(S_OR(dialog->peer->subscribe_context, dialog->peer->context), dialog->to_user, + &state_info, dialog); + } else if (!strcmp(event, "message-summary")) { + if (strcmp(accept, "application/simple-message-summary")) { + ast_debug(1, "Received message-summary subscribe with unknown format '%s'\n", accept); + + /* Format requested that we do not support */ + sip_response_send(dialog, "406 Not Acceptable", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + + /* Looks like they actually want a mailbox status */ + if (AST_LIST_EMPTY(&dialog->peer->mailboxes)) { + sip_response_send(dialog, "404 Not Found", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + + dialog->established = TRUE; + dialog->subscribe_event = SIP_SUBSCRIBE_MESSAGE_SUMMARY; + + if (dialog->peer->subscribe_mwi_only) { + ao2_unlock(dialog); + sip_peer_update_mailboxes(dialog->peer); + ao2_lock(dialog); + } + + if (dialog->peer->mwi_dialog != dialog) { /* Destroy old dialog if this is a new one */ + /* We only allow one subscription per peer */ + if (dialog->peer->mwi_dialog) { + sip_dialog_unlink(dialog->peer->mwi_dialog); + ao2_ref(dialog->peer->mwi_dialog, -1); + } + + dialog->peer->mwi_dialog = ao2_bump(dialog); + } + + ast_debug(2, "%s mwi event subscription for %s\n", dialog->expires ? "Adding" : "Removing", + dialog->peer->name); + sip_response_send(dialog, "200 OK", request); + + ao2_unlock(dialog); + sip_peer_send_mwi(dialog->peer, FALSE); + ao2_lock(dialog); + } else if (!strcmp(event, "as-feature-event")) { + int feature_event = SIP_FEATURE_NONE; + + if (sip_handle_subscribe_feature_event(dialog, request, &feature_event)) { + sip_response_send(dialog, "489 Bad Event", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + + dialog->established = TRUE; + dialog->subscribe_event = SIP_SUBSCRIBE_FEATURE_EVENTS; + + if (dialog->peer->feature_events_dialog != dialog) { /* Destroy old dialog if this is a new one */ + /* We only allow one subscription per peer */ + if (dialog->peer->feature_events_dialog) { + sip_dialog_unlink(dialog->peer->feature_events_dialog); + ao2_ref(dialog->peer->feature_events_dialog, -1); + } + + dialog->peer->feature_events_dialog = ao2_bump(dialog); + } + + ast_debug(2, "%s feature event subscription for %s\n", dialog->expires ? "Adding" : "Removing", + dialog->peer->name); + + if (feature_event != SIP_FEATURE_BULK_UPDATE) { + sip_response_send_with_feature_event(dialog, request, feature_event); + } + + if (feature_event == SIP_FEATURE_BULK_UPDATE) { + sip_peer_send_bulk_update(dialog->peer); + } else if (feature_event == SIP_FEATURE_DO_NOT_DISTURB) { + sip_peer_send_do_not_disturb(dialog->peer); + } else if (feature_event == SIP_FEATURE_CALL_FORWARD) { + sip_peer_send_call_forward(dialog->peer); + } + } else { /* At this point, Asterisk does not understand the specified event */ + ast_debug(2, "Received subscribe for unknown event '%s'\n", event); + + sip_response_send(dialog, "489 Bad Event", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + + if (dialog->expires) { + /* Set timer for destruction of subscription at expiration */ + sip_dialog_sched_destroy(dialog, (dialog->expires + 10) * 1000); + } else { + sip_dialog_set_need_destroy(dialog, "forcing expiration"); + } + + return 0; +} + +/* Handle incoming notifications. Mostly created to return proper answers on notifications on outbound REFER's */ +static int sip_handle_request_notify(struct sip_dialog *dialog, struct sip_message *request) +{ + char *event; + + event = ast_strdupa(sip_message_find_header(request, "Event")); + event = strsep(&event, ";"); /* Remove ;id= for NOTIFY response to a REFER */ + + ast_debug(2, "Received NOTIFY event '%s'\n", event); + + if (!strcmp(event, "refer")) { + /* Handle REFER notifications */ + const char *content_type; + char *content, *sipfrag; + int code; + + content_type = sip_message_find_content_type(request); + + if (!strcmp(content_type, "application/x-cisco-remotecc-response+xml")) { + sip_response_send(dialog, "200 OK", request); + sip_dialog_set_need_destroy(dialog, "remotecc response"); + return 0; + } + + /* Check the content type */ + if (strncasecmp(content_type, "message/sipfrag", 15)) { + /* We need a sipfrag */ + sip_response_send(dialog, "400 Bad Request", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + + /* Get the text of the attachment */ + if (ast_strlen_zero(content = sip_message_get_content(request, 0, request->content_count))) { + sip_response_send(dialog, "400 Bad Request", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + + /* Fragment should be "SIP/2.0 " */ + sipfrag = ast_skip_blanks(content); + + /* 200 OK, 301 Moved Permanently, 302 Moved Temporarily: Transfer succeeded */ + /* 503 Service Unavailable, 603 Decline: Transfer failed */ + if (strncmp(sipfrag, "SIP/2.0", 6) || sscanf(sipfrag + 6, "%30d", &code) != 1) { + ast_debug(1, "Error parsing sipfrag '%s' in NOTIFY in response to REFER\n", sipfrag); + code = 0; + } + + /* Ignore provisional responses */ + if (dialog->channel && code >= 200) { + enum ast_control_transfer transfer; + + if (code == 200 || code == 301 || code == 302) { + transfer = AST_TRANSFER_SUCCESS; + } else { + transfer = AST_TRANSFER_FAILED; + ast_debug(1, "Transfer failed. Sorry. Nothing further to do with this call\n"); + } + + ast_queue_control_data(dialog->channel, AST_CONTROL_TRANSFER, &transfer, sizeof(transfer)); + } + + /* Confirm that we received this packet */ + sip_response_send(dialog, "200 OK", request); + } else if (!strcmp(event, "dialog")) { + /* Cisco phone on/off-hook notifications */ + if (sip_handle_notify_dialog(dialog, request)) { + sip_response_send(dialog, "489 Bad Event", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + + sip_dialog_set_need_destroy(dialog, "forcing expiration"); + return 0; + } else if (!strcmp(event, "message-summary")) { + char *mailbox, *context, *voice_message; + int i, old_messages, new_messages; + + mailbox = NULL; + + if (dialog->mwi_subscription) { + mailbox = ast_strdupa(dialog->mwi_subscription->mailbox); + context = ast_strdupa(dialog->mwi_subscription->context); + } else { + RAII_VAR(struct sip_peer *, peer, NULL, ao2_cleanup); + + if ((peer = sip_peer_address_find(&dialog->socket.address, dialog->socket.transport, TRUE, FALSE))) { + mailbox = ast_strdupa(peer->unsolicited_mailbox); + + if ((context = strchr(mailbox, '@'))) { + *context++ = '\0'; + } else { + context = "default"; + } + } + } + + voice_message = NULL; + + for (i = 0; i < request->content_count; i++) { + if (!strncmp(request->content[i], "Voice-Message:", 14)) { + voice_message = ast_strdupa(ast_skip_blanks(request->content[i] + 14)); + break; + } + } + + if (!ast_strlen_zero(mailbox) && !ast_strlen_zero(voice_message) && + sscanf(voice_message, "%30d/%30d", &new_messages, &old_messages) == 2) { + if (dialog->mwi_subscription) { + dialog->mwi_subscription->new_messages = new_messages; + dialog->mwi_subscription->old_messages = old_messages; + } + + ast_publish_mwi_state(mailbox, context, new_messages, old_messages); + sip_response_send(dialog, "200 OK", request); + } else { + sip_response_send(dialog, "489 Bad Event", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + } else { + /* We don't understand this event */ + sip_response_send(dialog, "489 Bad Event", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + + if (!dialog->last_invite_cseq) { + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } + + return 0; +} + +/* Handle incoming REFER request */ +static int sip_handle_request_refer(struct sip_dialog *dialog, struct sip_message *request, int *no_unlock) +{ + char *refer_to_user, *refer_to_context, replaces[256]; + const char *content_type; + struct sip_transfer_blind_data transfer_data; + enum ast_transfer_result transfer_res; + RAII_VAR(struct ast_channel *, channel, NULL, ast_channel_cleanup); + int res; + + if (!strcasecmp(sip_message_find_header(request, "Refer-To"), "")) { + sip_response_send(dialog, "202 Accepted", request); + + sip_dialog_set_already_gone(dialog); + sip_dialog_set_need_destroy(dialog, "token registration"); + return 0; + } + + /* Could be multipart, so find the content type of that part */ + content_type = sip_message_find_content_type(request); + + /* Cisco phone remotecc and failover */ + if (!strcmp(content_type, "application/x-cisco-alarm+xml") || + !strcmp(content_type, "application/x-cisco-remotecc-response+xml")) { + sip_response_send(dialog, "202 Accepted", request); + + if (!dialog->channel) { + sip_dialog_set_already_gone(dialog); + sip_dialog_set_need_destroy(dialog, "alarm/remotecc device notificaton"); + } + + return 0; + } else if (!strcmp(content_type, "application/x-cisco-remotecc-request+xml")) { + res = sip_dialog_handle_authorization(dialog, request, SIP_SEND_UNRELIABLE); + + /* If an authentication response was sent, we are done here */ + if (res == SIP_AUTHORIZATION_CHALLENGE_SENT) { + return 0; + } else if (res != SIP_AUTHORIZATION_SUCCESS) { + sip_response_send_reliable(dialog, "403 Forbidden", request); + sip_dialog_set_need_destroy(dialog, "authentication failed"); + return 0; + } + + if ((res = sip_handle_refer_remotecc(dialog, request))) { + sip_response_send(dialog, "603 Decline", request); + } + + if (!dialog->channel) { + sip_dialog_set_already_gone(dialog); + sip_dialog_set_need_destroy(dialog, "remotecc request"); + } + + return res; + } + + ast_debug(1, "Call '%s' received a transfer REFER from %s\n", + dialog->call_id, dialog->outgoing ? "callee" : "caller"); + + if (!dialog->channel) { + /* This is a REFER outside of an existing SIP dialog. We can't handle that, so decline it */ + ast_debug(3, "Call '%s' declined REFER, outside of dialog\n", dialog->call_id); + sip_response_send(dialog, "603 Decline", request); + + if (!request->ignore) { + sip_dialog_set_already_gone(dialog); + sip_dialog_set_need_destroy(dialog, "outside of dialog"); + + } + + return -1; + } + + /* Check if transfer is allowed from this device, if not then decline */ + if (!dialog->peer->allow_transfer) { + sip_response_send(dialog, "603 Decline", request); /* Do not destroy SIP session */ + return -1; + } + + /* Check if we already have a pending REFER */ + if (!request->ignore && dialog->transferring_call) { + sip_response_send(dialog, "491 Request Pending", request); + return -1; + } + + dialog->refer_state = SIP_REFER_SENT; + + if ((res = sip_parse_refer_to(dialog, request)) != SIP_REFER_EXTEN_FOUND) { + switch (res) { + case SIP_REFER_MISSING_HEADER: /* Syntax error */ + ast_debug(1, "Missing Refer-To: header\n"); + sip_response_send(dialog, "400 Bad Request", request); + break; + case SIP_REFER_INVALID_URI: + ast_debug(1, "Transfer to non-SIP URI denied\n"); + sip_response_send(dialog, "603 Decline", request); + break; + case SIP_REFER_EXTEN_NOT_FOUND: + default: + /* Refer-to extension not found, fake a failed transfer */ + ast_debug(1, "Transfer to bad extension '%s'\n", dialog->refer_to_user); + sip_response_send(dialog, "202 Accepted", request); + sip_request_send_notify_with_sipfrag(dialog, request->cseq, "404 Not Found"); + break; + } + + return -1; + } + + /* If we do not support SIP domains, all transfers are local or we don't bother with SIP domains or domain is + * local, so this transfer is local */ + if (!ao2_container_count(sip_domains) || + (sip_config.allow_external_domains && sip_domain_check(dialog->refer_to_domain, NULL, 0))) { + ast_debug(3, "This transfer is local '%s'\n", dialog->refer_to_domain); + dialog->local_transfer = TRUE; + } else { + ast_debug(3, "This transfer is remote '%s'\n", dialog->refer_to_domain); + } + + /* Is this a repeat of a current request? Ignore it */ + if (request->ignore) { + return 0; + } + + /* Get the transferer's channel */ + channel = ast_channel_ref(dialog->channel); + dialog->transferring_call = TRUE; + + ast_debug(3, "%s transfer channel '%s'\n", + dialog->attended_transfer ? "Attended" : "Blind", ast_channel_name(channel)); + /* From here on failures will be indicated with NOTIFY requests */ + sip_response_send(dialog, "202 Accepted", request); + + /* Attended transfer: Find all call legs and bridge transferee with target */ + if (dialog->attended_transfer) { + /* Both dialog and dialog->channel _MUST_ be locked while calling sip_transfer_attended */ + res = sip_transfer_attended(dialog, channel, request->cseq, no_unlock); + + /* Transfer attended here can abort if the target is not a local */ + if (res || dialog->refer_state == SIP_REFER_SUCCESS) { + dialog->transferring_call = FALSE; + return res; + } + + /* Fall through for remote transfers that we did not find locally */ + ast_debug(4, "Attended transfer, still not our call. Generating INVITE with replaces\n"); + } + + memset(&transfer_data, 0, sizeof(transfer_data)); + transfer_data.domain = ast_strdupa(dialog->refer_to_domain); + transfer_data.referred_by = ast_strdupa(dialog->referred_by); + + sip_parse_diversion(dialog, request, FALSE); + + if (!ast_strlen_zero(dialog->redirecting_from_name)) { + transfer_data.from_name = ast_strdupa(dialog->redirecting_from_name); + } + + if (!ast_strlen_zero(dialog->redirecting_from_number)) { + transfer_data.from_number = ast_strdupa(dialog->redirecting_from_number); + } + + if (!ast_strlen_zero(dialog->redirecting_to_name)) { + transfer_data.to_name = ast_strdupa(dialog->redirecting_to_name); + } + + if (!ast_strlen_zero(dialog->redirecting_to_number)) { + transfer_data.to_number = ast_strdupa(dialog->redirecting_to_number); + } + + if (!ast_strlen_zero(dialog->caller_tag)) { + transfer_data.tag = ast_strdupa(dialog->caller_tag); + } + + transfer_data.code = dialog->redirecting_code; + + if (!ast_strlen_zero(dialog->redirecting_reason)) { + transfer_data.reason = ast_strdupa(dialog->redirecting_reason); + } + + if (!ast_strlen_zero(dialog->replaces_call_id)) { + snprintf(replaces, sizeof(replaces), "%s%s%s%s%s", dialog->replaces_call_id, + !ast_strlen_zero(dialog->replaces_from_tag) ? ";from-tag=" : "", dialog->replaces_from_tag, + !ast_strlen_zero(dialog->replaces_to_tag) ? ";to-tag=" : "", dialog->replaces_to_tag); + transfer_data.replaces = ast_strdupa(replaces); + } else { + transfer_data.replaces = NULL; + } + + dialog->defer_bye_on_transfer = TRUE; + + if (!*no_unlock) { + ast_channel_unlock(dialog->channel); + *no_unlock = TRUE; + } + + /* Copy data we can not safely access after letting the dialog lock go */ + refer_to_user = ast_strdupa(dialog->refer_to_user); + refer_to_context = ast_strdupa(dialog->refer_to_context); + + ao2_unlock(dialog); + transfer_res = ast_bridge_transfer_blind(TRUE, channel, refer_to_user, refer_to_context, sip_transfer_blind, + &transfer_data); + ao2_lock(dialog); + + switch (transfer_res) { + case AST_BRIDGE_TRANSFER_SUCCESS: + dialog->refer_state = SIP_REFER_SUCCESS; + sip_request_send_notify_with_sipfrag(dialog, request->cseq, "200 OK"); + break; + case AST_BRIDGE_TRANSFER_FAIL: + dialog->refer_state = SIP_REFER_FAILED; + dialog->defer_bye_on_transfer = FALSE; + sip_request_send_notify_with_sipfrag(dialog, request->cseq, "500 Internal Server Error"); + break; + case AST_BRIDGE_TRANSFER_INVALID: + dialog->refer_state = SIP_REFER_FAILED; + dialog->defer_bye_on_transfer = FALSE; + sip_request_send_notify_with_sipfrag(dialog, request->cseq, "503 Service Unavailable"); + break; + case AST_BRIDGE_TRANSFER_NOT_PERMITTED: + dialog->refer_state = SIP_REFER_FAILED; + dialog->defer_bye_on_transfer = FALSE; + sip_request_send_notify_with_sipfrag(dialog, request->cseq, "403 Forbidden"); + break; + default: + break; + } + + dialog->transferring_call = FALSE; + return transfer_res == AST_BRIDGE_TRANSFER_SUCCESS ? 0 : -1; +} + +/* Handle incoming REGISTER request */ +static int sip_handle_request_register(struct sip_dialog *dialog, struct sip_message *request) +{ + int res; + + /* If this is not the intial request, and the initial request isn't a REGISTER, something screwy happened, + * so bail */ + if (!ast_strlen_zero(dialog->initial_request.uri) && dialog->initial_request.method != SIP_METHOD_REGISTER) { + ast_debug(3, "Ignoring spurious REGISTER for '%s'\n", dialog->call_id); + return -1; + } + + /* Use this as the basis */ + ast_debug(4, "Set initital REGISTER request for '%s'\n", dialog->call_id); + + sip_message_copy(&dialog->initial_request, request); + sip_dialog_check_via(dialog, request); + + res = sip_dialog_handle_registration(dialog, request); + + if (res != SIP_AUTHORIZATION_SUCCESS && res != SIP_AUTHORIZATION_CHALLENGE_SENT) { + const char *reason; + + switch (res) { + case SIP_AUTHORIZATION_SECRET_FAILED: + reason = "Wrong password"; + break; + case SIP_AUTHORIZATION_USERNAME_MISMATCH: + reason = "Username mismatch"; + break; + case SIP_AUTHORIZATION_NOT_FOUND: + reason = "No matching peer found"; + break; + case SIP_AUTHORIZATION_UNKNOWN_DOMAIN: + reason = "Not a local domain"; + break; + case SIP_AUTHORIZATION_PEER_NOT_DYNAMIC: + reason = "Peer is not supposed to register"; + break; + case SIP_AUTHORIZATION_ACL_FAILED: + reason = "Device does not match ACL"; + break; + case SIP_AUTHORIZATION_INVALID_TRANSPORT: + reason = "Device not configured to use this transport type"; + break; + case SIP_AUTHORIZATION_RTP_FAILED: + reason = "RTP initialization failed"; + break; + default: + reason = "Unknown failure"; + break; + } + + ast_verb(3, "SIP registration for peer '%s' from '%s' failed because '%s'\n", + sip_message_find_header(request, "To"), ast_sockaddr_stringify(&dialog->socket.address), + reason); + } + + if (res == SIP_AUTHORIZATION_SUCCESS && dialog->expires) { + sip_dialog_sched_destroy(dialog, (dialog->expires + 10) * 1000); + } else if (res != SIP_AUTHORIZATION_CHALLENGE_SENT) { + /* Destroy the session, but keep us around for just a bit in case they don't get our 200 OK */ + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } + + sip_security_event(dialog, request, res); + return 0; +} + +/* Handle incoming OPTIONS request. An OPTIONS request should be answered like an INVITE from the same UA, including + * SDP */ +static int sip_handle_request_options(struct sip_dialog *dialog, struct sip_message *request) +{ + const char *status_line; + int res; + + if (dialog->last_invite_cseq) { + /* If this is a request in an active dialog, just confirm that the dialog exists */ + sip_response_send_with_accept(dialog, "200 OK", request); + return 0; + } + + if (sip_config.authenticate_options) { + /* Do authentication if this OPTIONS request began the dialog */ + sip_message_copy(&dialog->initial_request, request); + + res = sip_dialog_handle_authorization(dialog, request, SIP_SEND_UNRELIABLE); + + if (res == SIP_AUTHORIZATION_CHALLENGE_SENT) { + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return 0; + } else if (res != SIP_AUTHORIZATION_SUCCESS) { /* Something failed in authentication */ + sip_response_send_with_authorization_failure(dialog, request, res, SIP_SEND_UNRELIABLE); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return 0; + } + + /* Must go through authentication before getting here */ + res = sip_dialog_get_destination(dialog, request); + } else { + /* No peer, so just say we found it */ + res = SIP_DESTINATION_EXTEN_FOUND; + } + + sip_dialog_build_contact(dialog, request); + + if (ast_shutting_down()) { + /* Not taking any new calls at this time. Likely a server availability OPTIONS poll */ + status_line = "503 Service Unavailable"; + } else if (res == SIP_DESTINATION_EXTEN_FOUND) { + status_line = "200 OK"; + } else if (res == SIP_DESTINATION_INVALID_URI) { + status_line = "416 Unsupported URI Scheme"; + } else { + status_line = "404 Not Found"; + } + + sip_response_send_with_accept(dialog, status_line, request); + + /* Destroy if this OPTIONS was the opening request, but not if it's in the middle of a normal call flow */ + if (ast_strlen_zero(dialog->initial_request.uri)) { + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } + + return 0; +} + +static int sip_handle_request_publish(struct sip_dialog *dialog, struct sip_message *request) +{ + static unsigned int next_etag = 1; + const char *event, *expires; + int res; + + event = sip_message_find_header(request, "Event"); + + if (ast_strlen_zero(event)) { + sip_response_send(dialog, "489 Bad Event", request); + sip_dialog_set_need_destroy(dialog, "missing Event: header"); + return -1; + } + + res = sip_dialog_handle_authorization(dialog, request, SIP_SEND_UNRELIABLE); + + if (res == SIP_AUTHORIZATION_CHALLENGE_SENT) { + dialog->last_invite_cseq = request->cseq; + return 0; + } else if (res != SIP_AUTHORIZATION_SUCCESS) { + sip_response_send_with_authorization_failure(dialog, request, res, SIP_SEND_UNRELIABLE); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + + ast_string_field_set(dialog, remote_tag, NULL); + return 0; + } + + if (dialog->last_invite_cseq) { + /* We need to stop retransmitting the 401 */ + sip_packet_ack(dialog, SIP_METHOD_PUBLISH, dialog->last_invite_cseq, TRUE); + } + + expires = sip_message_find_header(request, "Expires"); + + if (ast_strlen_zero(expires)) { + dialog->expires = sip_config.default_expires; + } else { + dialog->expires = atoi(expires); + + if (dialog->expires > sip_config.register_max_expires) { + dialog->expires = sip_config.register_max_expires; + } else if (dialog->expires < sip_config.register_min_expires && dialog->expires > 0) { + sip_response_send_with_min_expires(dialog, request, sip_config.register_min_expires); + sip_dialog_set_need_destroy(dialog, "interval too small"); + return -1; + } + } + + if (!strcmp(event, "presence")) { + res = sip_handle_publish_presence(dialog, request); + } else { + sip_response_send(dialog, "400 Unknown Event", request); + sip_dialog_set_need_destroy(dialog, "forcing expiration"); + return -1; + } + + if (!res) { + char etag[64]; + + ast_copy_string(etag, sip_message_find_header(request, "SIP-If-Match"), sizeof(etag)); + + if (ast_strlen_zero(etag)) { + snprintf(etag, sizeof(etag), "%d", ast_atomic_fetchadd_int((int *) &next_etag, +1)); + } + + sip_response_send_with_etag(dialog, request, etag); + } + + sip_dialog_set_need_destroy(dialog, "forcing expiration"); + return 0; +} + +/* Receive SIP INFO Message */ +static int sip_handle_request_info(struct sip_dialog *dialog, struct sip_message *request) +{ + const char *content_type; + + if (request->ignore) { + sip_response_send(dialog, "200 OK", request); + return 0; + } + + content_type = sip_message_find_header(request, "Content-Type"); + + /* Need to check the media/type */ + if (!strcmp(content_type, "application/media_control+xml")) { + /* Eh, we'll just assume it's a fast picture update for now */ + if (dialog->channel) { + ast_queue_control(dialog->channel, AST_CONTROL_VIDUPDATE); + } + + sip_response_send(dialog, "200 OK", request); + return 0; + } + + /* Other type of INFO message, not really understood by Asterisk */ + ast_debug(1, "Unable to parse INFO message from '%s'\n", dialog->call_id); + sip_response_send(dialog, "415 Unsupported Media Type", request); + return -1; +} + +/* Handle incoming MESSAGE request. We only handle messages within current calls currently */ +static int sip_handle_request_message(struct sip_dialog *dialog, struct sip_message *request) +{ + char *content, *from, *contact, *name, *user, *domain; + const char *content_type, *status_line; + struct ast_msg *msg; + int i, res; + + if (request->ignore) { + sip_response_send(dialog, "202 Accepted", request); + return 0; + } + + content_type = sip_message_find_header(request, "Content-Type"); + + if (strncmp(content_type, "text/plain", 10)) { /* No text/plain attachment */ + sip_response_send(dialog, "415 Unsupported Media Type", request); /* Good enough, or? */ + + if (!dialog->channel) { + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } + + return -1; + } + + if (!(content = sip_message_get_content(request, 0, request->content_count))) { + ast_debug(1, "No content body on MESSAGE for '%s'\n", dialog->call_id); + sip_response_send(dialog, "500 Internal Server Error", request); + + if (!dialog->channel) { + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } + + return -1; + } + + /* Strip trailing line feeds from message body. (sip_message_get_content may add a trailing linefeed and we + * don't need any at the end) */ + ast_trim_blanks(content); + + if (dialog->channel) { + struct ast_frame frame; + + memset(&frame, 0, sizeof(frame)); + + frame.frametype = AST_FRAME_TEXT; + frame.subclass.integer = 0; + frame.offset = 0; + frame.data.ptr = content; + frame.datalen = strlen(content) + 1; + + ast_queue_frame(dialog->channel, &frame); + sip_response_send(dialog, "202 Accepted", request); /* We respond 202 accepted */ + return 0; + } + + /* At this point MESSAGE is outside of a call */ + if (!sip_config.allow_message) { + /* Message outside of a call, we do not support that */ + ast_debug(1, "MESSAGE outside of a call administratively disabled\n"); + + sip_response_send(dialog, "405 Method Not Allowed", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + + sip_message_copy(&dialog->initial_request, request); + res = sip_dialog_handle_authorization(dialog, request, SIP_SEND_UNRELIABLE); + + if (res == SIP_AUTHORIZATION_CHALLENGE_SENT) { + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return 0; + } else if (res != SIP_AUTHORIZATION_SUCCESS) { /* Something failed in authentication */ + sip_response_send_with_authorization_failure(dialog, request, res, SIP_SEND_UNRELIABLE); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return 0; + } + + switch (sip_dialog_get_destination(dialog, request)) { + case SIP_DESTINATION_REFUSED: + /* Okay to send 403 since this is after auth processing */ + sip_response_send(dialog, "403 Forbidden", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + case SIP_DESTINATION_INVALID_URI: + sip_response_send(dialog, "416 Unsupported URI Scheme", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + default: + /* We may have something other than dialplan who wants the message, so defer further error handling + * for now */ + break; + } + + if (!(msg = ast_msg_alloc())) { + sip_response_send(dialog, "500 Internal Server Error", request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return -1; + } + + res = ast_msg_set_tech(msg, "%s", "SIP"); + from = ast_strdupa(sip_message_find_header(request, "From")); + + if (sip_parse_contact(from, &name, &user, &domain, NULL, NULL)) { + return -1; + } + + if (!ast_strlen_zero(name)) { + char quoted_name[128]; + + ast_escape_quoted(name, quoted_name, sizeof(quoted_name)); + res |= ast_msg_set_from(msg, "\"%s\" <%s%s%s>", + quoted_name, user, !ast_strlen_zero(user) ? "@" : "", domain); + } else { + res |= ast_msg_set_from(msg, "<%s%s%s>", user, !ast_strlen_zero(user) ? "@" : "", domain); + } + + res |= ast_msg_set_to(msg, "%s", request->uri); + res |= ast_msg_set_body(msg, "%s", content); + res |= ast_msg_set_context(msg, "%s", S_OR(dialog->peer->message_context, dialog->peer->context)); + res |= ast_msg_set_var(msg, "SIP_RECEIVED_ADDR", ast_sockaddr_stringify(&dialog->socket.address)); + + if (!ast_strlen_zero(dialog->peer->name)) { + res |= ast_msg_set_endpoint(msg, "%s", dialog->peer->name); + res |= ast_msg_set_var(msg, "SIP_PEERNAME", dialog->peer->name); + } + + contact = ast_strdupa(sip_message_find_header(request, "Contact")); + res |= ast_msg_set_var(msg, "SIP_CONTACT", sip_get_uri(contact)); + res |= ast_msg_set_exten(msg, "%s", dialog->to_user); + + for (i = 0; i < request->header_count && !res; i++) { + res |= ast_msg_set_var(msg, request->headers[i].name, request->headers[i].value); + } + + if (res) { + ast_msg_destroy(msg); + status_line = "500 Internal Server Error"; + } else if (ast_msg_has_destination(msg)) { + ast_msg_queue(msg); + status_line = "202 Accepted"; + } else { + ast_msg_destroy(msg); + status_line = "404 Not Found"; + } + + sip_response_send(dialog, status_line, request); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return 0; +} + +/* Handle the transfer part of INVITE with a replaces: header, This is used for call-pickup and for attended transfers + * initiated remote endpoints (i.e. a REFER received on a remote server). dialog and dialog->channel are locked upon + * entering this function. If the call pickup or attended transfer is successful, then dialog->channel will be unlocked + * upon exiting this function. This is communicated to the caller through the no_unlock parameter */ +static int sip_handle_invite_replaces(struct sip_dialog *dialog, struct sip_message *request, int *no_unlock, + struct sip_dialog *transfer_dialog, struct ast_channel *transfer_channel) +{ + struct ast_channel *channel; + struct ast_bridge *bridge; + + if (request->ignore) { + return 0; + } + + /* Get a ref to ensure the channel cannot go away on us */ + channel = ast_channel_ref(dialog->channel); + + /* Fake call progress */ + sip_response_send(dialog, "100 Trying", request); + ast_setstate(channel, AST_STATE_RING); + + ast_debug(4, "Invite/Replaces: preparing to replace '%s' with '%s'\n", + ast_channel_name(transfer_channel), ast_channel_name(channel)); + *no_unlock = TRUE; + + ast_channel_unlock(channel); + ao2_unlock(dialog); + + ast_raw_answer(channel); + + if ((bridge = ast_bridge_transfer_acquire_bridge(transfer_channel))) { + /* We have two refs of the channel. One is held in channel and the other is notionally represented + * by dialog->channel. The impart is "stealing" the dialog->channel ref on success so the bridging + * system can have control of when the channel is hung up */ + if (ast_bridge_impart(bridge, channel, transfer_channel, NULL, AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) { + ast_hangup(channel); + } + + ao2_ref(bridge, -1); + } else { + int picked_up; + + ast_channel_lock(transfer_channel); + picked_up = ast_can_pickup(transfer_channel) && !ast_do_pickup(channel, transfer_channel); + ast_channel_unlock(transfer_channel); + + if (!picked_up) { + ast_channel_move(transfer_channel, channel); + } + + ast_hangup(channel); + } + + ast_channel_unref(channel); + ao2_lock(dialog); + return 0; +} + +/* Handle incoming feature event SUBSCRIBE body */ +static int sip_handle_subscribe_feature_event(struct sip_dialog *dialog, struct sip_message *request, + int *feature_event) +{ + const char *content_type; + char *content; + RAII_VAR(struct ast_xml_doc *, xml_document, NULL, ast_xml_close); + + if (!atoi(sip_message_find_header(request, "Content-Length"))) { + /* Peer is subscribing to the current DoNotDisturb and CallForward state */ + *feature_event = SIP_FEATURE_BULK_UPDATE; + return 0; + } + + content_type = sip_message_find_header(request, "Content-Type"); + + if (strcmp(content_type, "application/x-as-feature-event+xml")) { + ast_debug(2, "Content-Type: is not x-as-feature-event+xml\n"); + return -1; + } + + if (!(content = sip_message_get_content(request, 0, request->content_count - 1))) { + ast_debug(2, "Unable to get content\n"); + return -1; + } + + if (!(xml_document = ast_xml_read_memory(content, strlen(content)))) { + ast_debug(2, "Unable to parse XML\n"); + return -1; + } + + if (!strcmp(ast_xml_node_get_name(ast_xml_get_root(xml_document)), "SetDoNotDisturb")) { + int do_not_disturb; + struct ast_xml_node *set_do_not_disturb, *do_not_disturb_on; + RAII_VAR(const char *, text, NULL, ast_xml_free_text); + + set_do_not_disturb = ast_xml_get_root(xml_document); + + if (!(set_do_not_disturb = ast_xml_node_get_children(set_do_not_disturb))) { + ast_debug(2, "No elements within SetDoNotDisturb"); + return -1; + } + + if (!(do_not_disturb_on = ast_xml_find_element(set_do_not_disturb, "doNotDisturbOn", NULL, NULL))) { + ast_debug(2, "Missing doNotDisturbOn"); + return -1; + } + + text = ast_xml_get_text(do_not_disturb_on); + + if (!strcmp(text, "true")) { + do_not_disturb = TRUE; + } else if (!strcmp(text, "false")) { + do_not_disturb = FALSE; + } else { + ast_debug(2, "Invalid doNotDisturbOn '%s'\n", text); + return -1; + } + + if (dialog->peer->do_not_disturb != do_not_disturb) { + dialog->peer->do_not_disturb = do_not_disturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", dialog->peer->name); + + if (!dialog->peer->realtime) { + ast_db_put("SIP/DoNotDisturb", dialog->peer->name, + dialog->peer->do_not_disturb ? "yes" : "no"); + } else if (sip_config.realtime_update_peer && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", dialog->peer->name, + "donotdisturb", dialog->peer->do_not_disturb ? "yes" : "no", SENTINEL); + } + } + + *feature_event = SIP_FEATURE_DO_NOT_DISTURB; + } else if (!strcmp(ast_xml_node_get_name(ast_xml_get_root(xml_document)), "SetForwarding")) { + struct ast_xml_node *set_forwarding, *forwarding_type, *activate_forward, *forward_dn; + RAII_VAR(const char *, text, NULL, ast_xml_free_text); + char call_forward[AST_MAX_EXTENSION]; + + set_forwarding = ast_xml_get_root(xml_document); + + if (!(set_forwarding = ast_xml_node_get_children(set_forwarding))) { + ast_debug(2, "No elements within SetForwarding"); + return -1; + } + + if (!(forwarding_type = ast_xml_find_element(set_forwarding, "forwardingType", NULL, NULL))) { + ast_debug(2, "Missing forwardingType\n"); + return -1; + } + + text = ast_xml_get_text(forwarding_type); + + if (strcmp(text, "forwardImmediate")) { + ast_debug(2, "forwardingType not supported '%s'\n", text); + return -1; + } + + if (!(activate_forward = ast_xml_find_element(set_forwarding, "activateForward", NULL, NULL))) { + ast_debug(2, "Missing activateForward"); + return -1; + } + + ast_xml_free_text(text); + text = ast_xml_get_text(activate_forward); + + if (!strcmp(text, "true")) { + if (!(forward_dn = ast_xml_find_element(set_forwarding, "forwardDN", NULL, NULL))) { + ast_debug(2, "Missing forwardDN\n"); + return -1; + } + + ast_xml_free_text(text); + text = ast_xml_get_text(forward_dn); + + ast_copy_string(call_forward, text, sizeof(call_forward)); + } else if (!strcmp(text, "false")) { + call_forward[0] = '\0'; + } else { + ast_debug(2, "Invalid activateForward '%s'\n", text); + return -1; + } + + if (strcmp(dialog->peer->call_forward, call_forward)) { + ast_string_field_set(dialog->peer, call_forward, call_forward); + + if (!dialog->peer->realtime) { + if (ast_strlen_zero(dialog->peer->call_forward)) { + ast_db_del("SIP/CallForward", dialog->peer->name); + } else { + ast_db_put("SIP/CallForward", dialog->peer->name, dialog->peer->call_forward); + } + } else if (sip_config.realtime_update_peer && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", dialog->peer->name, + "callforward", dialog->peer->call_forward, SENTINEL); + } + } + + *feature_event = SIP_FEATURE_CALL_FORWARD; + } else { + ast_debug(2, "Not SetDoNotDisturb or SetForwarding '%s'\n", + ast_xml_node_get_name(ast_xml_get_root(xml_document))); + return -1; + } + + return 0; +} + +/* Handle dialog notifications */ +static int sip_handle_notify_dialog(struct sip_dialog *dialog, struct sip_message *request) +{ + struct sip_peer *peer; + const char *content_type; + char *content, *uri, *user; + RAII_VAR(struct ast_xml_doc *, xml_document, NULL, ast_xml_close); + struct ast_xml_node *dialog_info; + RAII_VAR(const char *, entity, NULL, ast_xml_free_attr); + int offhook; + + content_type = sip_message_find_header(request, "Content-Type"); + + if (strcmp(content_type, "application/dialog-info+xml")) { + ast_debug(2, "Content-Type: is not application/dialog-info+xml\n"); + return -1; + } + + if (!(content = sip_message_get_content(request, 0, request->content_count - 1))) { + ast_debug(2, "Unable to get content\n"); + return -1; + } + + if (!(xml_document = ast_xml_read_memory(content, strlen(content)))) { + ast_debug(2, "Unable to parse XML\n"); + return -1; + } + + dialog_info = ast_xml_get_root(xml_document); + + if (strcasecmp(ast_xml_node_get_name(dialog_info), "dialog-info")) { + ast_debug(2, "Missing dialog-info\n"); + return -1; + } + + /* We have to use the entity attribute on dialog-info instead of the NOTIFY URI because some models of + * Cisco phones only put the first character of the peer name in the From/To/URI */ + if (!(entity = ast_xml_get_attribute(dialog_info, "entity"))) { + ast_debug(2, "Missing entity"); + return -1; + } + + uri = ast_strdupa(entity); + + if (sip_parse_uri(uri, NULL, &user, NULL, NULL, NULL)) { + return -1; + } + + if (!(dialog_info = ast_xml_node_get_children(dialog_info))) { + ast_debug(2, "No elements within dialog-info\n"); + return -1; + } else { + struct ast_xml_node *dialog, *state; + RAII_VAR(const char *, text, NULL, ast_xml_free_text); + + if (!(dialog = ast_xml_find_element(dialog_info, "dialog", NULL, NULL))) { + ast_debug(2, "Missing dialog\n"); + return -1; + } + + if (!(dialog = ast_xml_node_get_children(dialog))) { + ast_debug(2, "No elements within dialog\n"); + return -1; + } + + if (!(state = ast_xml_find_element(dialog, "state", NULL, NULL))) { + ast_debug(2, "Missing state\n"); + return -1; + } + + text = ast_xml_get_text(state); + + if (!strcasecmp(text, "trying")) { + offhook = TRUE; + } else if (!strcasecmp(text, "terminated")) { + offhook = FALSE; + } else { + ast_debug(2, "Invalid state '%s'\n", text); + return -1; + } + } + + if (!(peer = sip_peer_find(user, TRUE, TRUE))) { + ast_debug(2, "Unknown peer '%s'\n", user); + return -1; + } + + if (peer->socket.transport == dialog->socket.transport && + !ast_sockaddr_cmp(&peer->address, &dialog->socket.address)) { + ast_debug(2, "Received %s notification for peer '%s' from %s\n", + offhook > 0 ? "off-hook" : "on-hook", peer->name, + ast_sockaddr_stringify(&dialog->socket.address)); + + ao2_lock(peer); + + if (offhook) { + peer->offhook++; + } else { + peer->offhook--; + } + + ao2_unlock(peer); + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + } + + ao2_ref(peer, -1); + sip_response_send(dialog, "200 OK", request); + return 0; +} + +static int sip_handle_publish_presence(struct sip_dialog *dialog, struct sip_message *request) +{ + const char *content_type; + char *content; + RAII_VAR(struct ast_xml_doc *, xml_document, NULL, ast_xml_close); + struct ast_xml_node *presence, *person, *activities; + struct sip_alias *alias; + int res, do_not_disturb; + + res = -1; + content_type = sip_message_find_header(request, "Content-Type"); + + if (strcmp(content_type, "application/pidf+xml")) { + const char *etag = sip_message_find_header(request, "SIP-If-Match"); + + if (!ast_strlen_zero(etag)) { + return 0; + } + + ast_debug(2, "Content-Type: is not 'application/pidf+xml'\n"); + goto cleanup; + } + + if (!(content = sip_message_get_content(request, 0, request->content_count))) { + ast_debug(2, "Unable to get content\n"); + goto cleanup; + } + + if (!(xml_document = ast_xml_read_memory(content, strlen(content)))) { + ast_debug(2, "Unable to parse XML\n"); + goto cleanup; + } + + presence = ast_xml_get_root(xml_document); + + if (strcmp(ast_xml_node_get_name(presence), "presence")) { + ast_debug(2, "Missing presence\n"); + goto cleanup; + } + + if (!(presence = ast_xml_node_get_children(presence))) { + ast_debug(2, "No elements within presence\n"); + goto cleanup; + } + + if (!(person = ast_xml_find_element(presence, "person", NULL, NULL))) { + ast_debug(2, "Missing person\n"); + goto cleanup; + } + + if (!(person = ast_xml_node_get_children(person))) { + ast_debug(2, "No elements within person\n"); + goto cleanup; + } + + if (!(activities = ast_xml_find_element(person, "activities", NULL, NULL))) { + ast_debug(2, "Missing activities\n"); + goto cleanup; + } + + if (!(activities = ast_xml_node_get_children(activities))) { + ast_debug(2, "No elements within activities\n"); + goto cleanup; + } + + if (ast_xml_find_element(activities, "dnd", NULL, NULL)) { + do_not_disturb = TRUE; + } else if (ast_xml_find_element(activities, "available", NULL, NULL)) { + do_not_disturb = FALSE; + } else { + ast_debug(2, "Missing dnd or available\n"); + goto cleanup; + } + + if (dialog->peer->do_not_disturb != do_not_disturb) { + dialog->peer->do_not_disturb = do_not_disturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", dialog->peer->name); + + AST_LIST_TRAVERSE(&dialog->peer->aliases, alias, next) { + if (alias->peer) { + alias->peer->do_not_disturb = dialog->peer->do_not_disturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", alias->peer->name); + } + } + + if (!dialog->peer->realtime) { + ast_db_put("SIP/DoNotDisturb", dialog->peer->name, dialog->peer->do_not_disturb ? "yes" : "no"); + } else if (sip_config.realtime_update_peer && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", dialog->peer->name, + "donotdisturb", dialog->peer->do_not_disturb ? "yes" : "no", SENTINEL); + } + } + + res = 0; + +cleanup: + if (res) { + sip_response_send(dialog, "400 Bad Request", request); + } + + return res; +} + +/* Handle incoming remotecc request */ +static int sip_handle_refer_remotecc(struct sip_dialog *dialog, struct sip_message *request) +{ + char *content, *boundary, *content_type; + RAII_VAR(struct ast_xml_doc *, xml_document, NULL, ast_xml_close); + struct ast_xml_node *remotecc_request, *soft_key_event_msg, *data_passthrough_req; + struct sip_remotecc_data remotecc_data; + int start, end, i, done; + + content_type = ast_strdupa(sip_message_find_header(request, "Content-Type")); + + if (ast_strlen_zero(content_type)) { + return -1; + } + + content_type = sip_parse_content_type(content_type, &boundary); + + /* If this is a datapassthrough request the actual remotecc request is in the second part */ + if (!strcmp(content_type, "multipart/mixed")) { + if (ast_strlen_zero(boundary)) { + return -1; + } + + done = FALSE; + + if ((start = sip_message_find_boundary(request, boundary, 0, &done)) == -1) { + return -1; + } + + start += 1; + + if ((end = sip_message_find_boundary(request, boundary, start, &done)) == -1) { + return -1; + } + + content_type = ""; + + for (i = start; i < end; i++) { + if (!strncasecmp(request->content[i], "Content-Type:", 13)) { + content_type = ast_skip_blanks(request->content[i] + 13); + } else if (ast_strlen_zero(request->content[i])) { + start = i + 1; + break; + } + } + + if (strcmp(content_type, "application/x-cisco-remotecc-request+xml")) { + ast_debug(1, "Invalid Content-Type '%s'\n", content_type); + return -1; + } + } else { + /* sip_handle_request_refer has already checked that first part is x-cisco-remotecc-request */ + done = TRUE; + start = 0; + end = request->content_count; + } + + if (!(content = sip_message_get_content(request, start, end - 1))) { + ast_debug(2, "Unable to get remotecc body\n"); + return -1; + } + + if (!(xml_document = ast_xml_read_memory(content, strlen(content)))) { + ast_debug(2, "Unable to parse XML\n"); + return -1; + } + + remotecc_request = ast_xml_get_root(xml_document); + + if (strcasecmp(ast_xml_node_get_name(remotecc_request), "x-cisco-remotecc-request")) { + ast_debug(2, "Missing x-cisco-remotecc-request node\n"); + return -1; + } + + if (!(remotecc_request = ast_xml_node_get_children(remotecc_request))) { + ast_debug(2, "No child nodes in x-cisco-remotecc-request node\n"); + return -1; + } + + memset(&remotecc_data, 0, sizeof(remotecc_data)); + + if ((soft_key_event_msg = ast_xml_find_element(remotecc_request, "softkeyeventmsg", NULL, NULL)) && + (soft_key_event_msg = ast_xml_node_get_children(soft_key_event_msg))) { + struct ast_xml_node *soft_key_event, *dialog_id, *consult_dialog_id, *join_dialog_id, *call_id, + *local_tag, *remote_tag; + + if ((soft_key_event = ast_xml_find_element(soft_key_event_msg, "softkeyevent", NULL, NULL))) { + RAII_VAR(const char *, text, NULL, ast_xml_free_text); + + text = ast_xml_get_text(soft_key_event); + remotecc_data.soft_key_event = ast_strdupa(text); + } + + if ((dialog_id = ast_xml_find_element(soft_key_event_msg, "dialogid", NULL, NULL)) && + (dialog_id = ast_xml_node_get_children(dialog_id))) { + if ((call_id = ast_xml_find_element(dialog_id, "callid", NULL, NULL))) { + RAII_VAR(const char *, text, NULL, ast_xml_free_text); + + text = ast_xml_get_text(call_id); + remotecc_data.dialog.call_id = ast_strdupa(text); + } + + if ((local_tag = ast_xml_find_element(dialog_id, "localtag", NULL, NULL))) { + RAII_VAR(const char *, text, NULL, ast_xml_free_text); + + text = ast_xml_get_text(local_tag); + remotecc_data.dialog.local_tag = ast_strdupa(text); + } + + if ((remote_tag = ast_xml_find_element(dialog_id, "remotetag", NULL, NULL))) { + RAII_VAR(const char *, text, NULL, ast_xml_free_text); + + text = ast_xml_get_text(remote_tag); + remotecc_data.dialog.remote_tag = ast_strdupa(text); + } + } + + if ((consult_dialog_id = ast_xml_find_element(soft_key_event_msg, "consultdialogid", NULL, NULL)) && + (consult_dialog_id = ast_xml_node_get_children(consult_dialog_id))) { + if ((call_id = ast_xml_find_element(consult_dialog_id, "callid", NULL, NULL))) { + RAII_VAR(const char *, text, NULL, ast_xml_free_text); + + text = ast_xml_get_text(call_id); + remotecc_data.consult_dialog.call_id = ast_strdupa(text); + } + + if ((local_tag = ast_xml_find_element(consult_dialog_id, "localtag", NULL, NULL))) { + RAII_VAR(const char *, text, NULL, ast_xml_free_text); + + text = ast_xml_get_text(local_tag); + remotecc_data.consult_dialog.local_tag = ast_strdupa(text); + } + + if ((remote_tag = ast_xml_find_element(consult_dialog_id, "remotetag", NULL, NULL))) { + RAII_VAR(const char *, text, NULL, ast_xml_free_text); + + text = ast_xml_get_text(remote_tag); + remotecc_data.consult_dialog.remote_tag = ast_strdupa(text); + } + } + + if ((join_dialog_id = ast_xml_find_element(soft_key_event_msg, "joindialogid", NULL, NULL)) && + (join_dialog_id = ast_xml_node_get_children(join_dialog_id))) { + if ((call_id = ast_xml_find_element(join_dialog_id, "callid", NULL, NULL))) { + RAII_VAR(const char *, text, NULL, ast_xml_free_text); + + text = ast_xml_get_text(call_id); + remotecc_data.join_dialog.call_id = ast_strdupa(text); + } + + if ((local_tag = ast_xml_find_element(join_dialog_id, "localtag", NULL, NULL))) { + RAII_VAR(const char *, text, NULL, ast_xml_free_text); + + text = ast_xml_get_text(local_tag); + remotecc_data.join_dialog.local_tag = ast_strdupa(text); + } + + if ((remote_tag = ast_xml_find_element(join_dialog_id, "remotetag", NULL, NULL))) { + RAII_VAR(const char *, text, NULL, ast_xml_free_text); + + text = ast_xml_get_text(remote_tag); + remotecc_data.join_dialog.remote_tag = ast_strdupa(text); + } + } + + if (!strcmp(remotecc_data.soft_key_event, "Park")) { + return sip_remotecc_park(dialog, request, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "ParkMonitor")) { + return sip_remotecc_parkmonitor(dialog, request, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "HLog")) { + return sip_remotecc_hlog(dialog, request, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "Conference")) { + return sip_remotecc_conference(dialog, request, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "ConfList")) { + return sip_remotecc_conflist(dialog, request, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "ConfDetails")) { + return sip_remotecc_conflist(dialog, request, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "RmLastConf")) { + return sip_remotecc_rmlastconf(dialog, request, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "Cancel")) { + sip_response_send(dialog, "202 Accepted", request); + return 0; + } else if (!strcmp(remotecc_data.soft_key_event, "Select")) { + return sip_remotecc_select(dialog, request, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "Unselect")) { + return sip_remotecc_unselect(dialog, request, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "Join")) { + return sip_remotecc_join(dialog, request, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "StartRecording")) { + return sip_remotecc_startrecording(dialog, request, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "StopRecording")) { + return sip_remotecc_stoprecording(dialog, request, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "IDivert")) { + return sip_remotecc_idivert(dialog, request, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "QRT")) { + return sip_remotecc_qrt(dialog, request, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "MCID")) { + return sip_remotecc_mcid(dialog, request, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "CallBack")) { + return sip_remotecc_callback(dialog, request, &remotecc_data); + } else { + ast_debug(2, "Unsupported softkeyevent '%s'\n", remotecc_data.soft_key_event); + } + } else if ((data_passthrough_req = ast_xml_find_element(remotecc_request, "datapassthroughreq", NULL, NULL)) && + (data_passthrough_req = ast_xml_node_get_children(data_passthrough_req))) { + struct ast_xml_node *application_id, *conf_id; + + if ((application_id = ast_xml_find_element(data_passthrough_req, "applicationid", NULL, NULL))) { + RAII_VAR(const char *, text, NULL, ast_xml_free_text); + + text = ast_xml_get_text(application_id); + remotecc_data.application_id = atoi(S_OR(text, "")); + } + + if ((conf_id = ast_xml_find_element(data_passthrough_req, "confid", NULL, NULL))) { + RAII_VAR(const char *, text, NULL, ast_xml_free_text); + + text = ast_xml_get_text(conf_id); + remotecc_data.conference_id = atoi(S_OR(text, "")); + } + + if (!done) { + start = end + 1; + + if ((end = sip_message_find_boundary(request, boundary, start, &done)) == -1) { + ast_debug(2, "Failed to find end boundary\n"); + return -1; + } + + content_type = ""; + + for (i = start; i < end; i++) { + if (!strncasecmp(request->content[i], "Content-Type:", 13)) { + content_type = ast_skip_blanks(request->content[i] + 13); + } else if (ast_strlen_zero(request->content[i])) { + start = i + 1; + break; + } + } + + if (!strcmp(content_type, "application/x-cisco-remotecc-cm+xml")) { + char *user_call_data; + + if (!(user_call_data = sip_message_get_content(request, start, end - 1))) { + ast_debug(2, "Unable to get user_call_data body\n"); + return -1; + } + + remotecc_data.user_call_data = ast_strip(ast_strdupa(user_call_data)); + } + } + + if (remotecc_data.application_id == SIP_REMOTECC_CONFLIST) { + return sip_remotecc_conflist(dialog, request, &remotecc_data); + } else if (remotecc_data.application_id == SIP_REMOTECC_CALLBACK) { + return sip_remotecc_callback(dialog, request, &remotecc_data); + } + + ast_debug(2, "Unsupported applicationid '%d'\n", remotecc_data.application_id); + } else if (ast_xml_find_element(remotecc_request, "x-cisco-location", NULL, NULL)) { + /* Wifi access point notifications */ + return 0; + } else { + ast_debug(1, "Unsupported x-cisco-remotecc-request+xml request\n"); + } + + return -1; +} + +/* Handle SIP response in dialog, only called by sip_handle_request */ +static void sip_handle_response(struct sip_dialog *dialog, struct sip_message *response) +{ + const char *via; + int iter, acked; + + ast_debug(4, "Received response '%s'\n", response->status_line); + + /* RFC 3261 - 8.1.3.3 If more than one Via header field value is present in a reponse the UAC SHOULD discard + * the message */ + iter = 0; + via = sip_message_next_header(response, "Via", &iter); + + if (strchr(via, ',') || !ast_strlen_zero(sip_message_next_header(response, "Via", &iter))) { + ast_debug(1, "Misrouted response '%s' for '%s', more than one Via: header\n", + response->status_line, response->call_id); + return; + } + + if (dialog->outgoing_cseq && dialog->outgoing_cseq < response->cseq) { + ast_debug(1, "Ignoring out of order response CSeq %u (expected CSeq %u)\n", + response->cseq, dialog->outgoing_cseq); + return; + } + + if (response->code == 200 || (response->code >= 300 && response->code <= 399)) { + char *contact; + + contact = ast_strdupa(sip_message_find_header(response, "Contact")); + contact = sip_get_uri(contact); + + if (!ast_strlen_zero(contact)) { + char *uri = strsep(&contact, ";"); + + ast_string_field_set(dialog, uri, uri); + } + } + + if (dialog->channel) { + if (sip_parse_reason(dialog, response)) { + /* Use the SIP cause */ + ast_channel_hangupcause_set(dialog->channel, sip_hangup2cause(response->code)); + } else { + ast_channel_hangupcause_set(dialog->channel, 0); + } + } + + /* Acknowledge whatever it is destined for */ + if (response->code >= 100 && response->code <= 199) { + /* NON-INVITE messages do not ack a 1XX response. RFC 3261 section 17.1.2.2 */ + if (response->method == SIP_METHOD_INVITE) { + acked = sip_packet_semi_ack(dialog, response->method, response->cseq, FALSE); + } else { + acked = FALSE; + } + } else { + acked = sip_packet_ack(dialog, response->method, response->cseq, FALSE); + } + + if (!acked) { + /* RFC 3261 13.2.2.4 and 17.1.1.2 - We must re-send ACKs to re-transmitted final responses */ + if (response->method == SIP_METHOD_INVITE && response->code >= 200) { + sip_request_send_ack(dialog, response->cseq, response->code < 300); + } + + return; + } + + /* Get their tag if we haven't already */ + if (ast_strlen_zero(dialog->remote_tag) || response->code >= 200) { + ast_string_field_set(dialog, remote_tag, response->to_tag); + } else { + /* Store remote_tag to track for changes when 200 responses to invites are received without SDP */ + ast_string_field_set(dialog, provisional_remote_tag, dialog->remote_tag); + } + + if (response->code == 200 && dialog->sent_authorization) { + dialog->sent_authorization = FALSE; /* Reset authentication */ + } + + /* Method Not Implemented */ + if (response->code == 501) { + dialog->allow_methods &= ~response->method; + ast_debug(2, "Peer '%s' does not support method '%s'\n", + dialog->peer->name, sip_method2str(response->method)); + } + + switch (response->method) { + case SIP_METHOD_OPTIONS: + sip_handle_response_options(dialog, response); + break; + case SIP_METHOD_INVITE: + sip_handle_response_invite(dialog, response); + break; + case SIP_METHOD_ACK: + sip_handle_response_ack(dialog, response); + break; + case SIP_METHOD_UPDATE: + sip_handle_response_update(dialog, response); + break; + case SIP_METHOD_BYE: + sip_handle_response_bye(dialog, response); + break; + case SIP_METHOD_CANCEL: + sip_handle_response_cancel(dialog, response); + break; + case SIP_METHOD_REGISTER: + sip_handle_response_register(dialog, response); + break; + case SIP_METHOD_REFER: + sip_handle_response_refer(dialog, response); + break; + case SIP_METHOD_SUBSCRIBE: + sip_handle_response_subscribe(dialog, response); + break; + case SIP_METHOD_NOTIFY: + sip_handle_response_notify(dialog, response); + break; + case SIP_METHOD_PUBLISH: + sip_handle_response_publish(dialog, response); + break; + case SIP_METHOD_MESSAGE: + sip_handle_response_message(dialog, response); + break; + case SIP_METHOD_INFO: + sip_handle_response_info(dialog, response); + break; + default: + /* Unknown methods are handled in sip_message_find_dialog */ + break; + } +} + +/* Handle SIP response to INVITE request */ +static void sip_handle_response_invite(struct sip_dialog *dialog, struct sip_message *response) +{ + int res; + + if (dialog->established) { + ast_debug(2, "Response '%s' to re-invite on %s '%s' from %s\n", + response->status_line, dialog->outgoing ? "outgoing" : "incoming", + dialog->call_id, ast_sockaddr_stringify(&dialog->socket.address)); + } else { + ast_debug(2, "Response '%s' to standard invite from %s\n", + response->status_line, ast_sockaddr_stringify(&dialog->socket.address)); + } + + if (dialog->already_gone) { /* This call is already gone */ + ast_debug(1, "Recevived response on '%s' that is already terminated, ignoring\n", dialog->call_id); + return; + } + + /* Acknowledge sequence number - This only happens on INVITE from SIP-call Don't auto congest anymore since + * we've gotten something useful back */ + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->invite_sched_id, ao2_cleanup(dialog)); + + /* RFC3261 says we must treat every 1xx response (but not 100) that we don't recognize as if it was 183 */ + if (response->code > 100 && response->code < 200 && + response->code != 101 && response->code != 180 && response->code != 181 && response->code != 182 && + response->code != 183) { + response->code = 183; + } else if (response->code >= 200 && response->code < 300) { + /* For INVITE, treat all 2XX responses as we would a 200 response */ + response->code = 200; + } + + /* Any response between 100 and 199 is PROCEEDING */ + if (response->code >= 100 && response->code < 200 && dialog->invite_state == SIP_INVITE_CALLING) { + dialog->invite_state = SIP_INVITE_PROCEEDING; + } else if (response->code >= 300 && + (dialog->invite_state == SIP_INVITE_CALLING || dialog->invite_state == SIP_INVITE_PROCEEDING || + dialog->invite_state == SIP_INVITE_EARLY_MEDIA)) { + dialog->invite_state = SIP_INVITE_COMPLETED; /* Final response, not 200 ? */ + } + + if (response->code >= 200 && dialog->established) { + dialog->ongoing_reinvite = FALSE; + sip_dialog_stop_reinvite(dialog); + } + + /* Final response, clear out pending invite */ + if ((response->code == 200 || response->code >= 300) && + dialog->pending_invite_cseq && response->cseq == dialog->pending_invite_cseq) { + dialog->pending_invite_cseq = 0; + } + + res = 0; + + switch (response->code) { + case 100: /* Trying */ + case 101: /* Dialog Established */ + if (!response->ignore && dialog->invite_state != SIP_INVITE_CANCELLED) { + sip_dialog_cancel_destroy(dialog); + } + + sip_dialog_sched_check_pending(dialog); + break; + case 180: /* 180 Ringing */ + case 182: /* 182 Queued */ + if (!response->ignore && dialog->invite_state != SIP_INVITE_CANCELLED) { + sip_dialog_cancel_destroy(dialog); + } + + /* Store Route-set from provisional SIP responses so early-dialog request can be routed properly */ + sip_dialog_set_contact(dialog, response); + + if (!dialog->established) { + sip_dialog_build_route(dialog, response, TRUE); + } + + if (!response->ignore && dialog->channel) { + if (sip_parse_identity(dialog, response)) { + sip_dialog_queue_connected_line(dialog, AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER); + } + + ast_queue_control(dialog->channel, AST_CONTROL_RINGING); + + if (ast_channel_state(dialog->channel) != AST_STATE_UP) { + ast_setstate(dialog->channel, AST_STATE_RINGING); + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_NOT_CACHABLE, + "SIP/%s", dialog->peer->name); + } + + if (dialog->peer->cisco_mode) { + const char *auto_answer = pbx_builtin_getvar_helper(dialog->channel, "CISCO_AUTOANSWER"); + + if (ast_true(auto_answer)) { + struct sip_dialog *answer_dialog; + + if ((answer_dialog = sip_dialog_alloc(NULL, &dialog->socket, SIP_METHOD_REFER, NULL, 0))) { + struct ast_str *content = ast_str_alloca(2048); + + sip_dialog_copy(answer_dialog, dialog); + + ast_str_append(&content, 0, "\n" + "\n" + " \n" + " \n"); + ast_str_append(&content, 0, " %s\n", + dialog->call_id); + ast_str_append(&content, 0, " %s\n", + dialog->remote_tag); + ast_str_append(&content, 0, " %s\n", + dialog->local_tag); + ast_str_append(&content, 0, " \n" + " \n" + "\n"); + + sip_request_send_refer_with_content(answer_dialog, + "application/x-cisco-remotecc-request+xml", + ast_str_buffer(content)); + ao2_ref(answer_dialog, -1); + } + } + } + } + + if (sip_sdp_find(response)) { + if (dialog->invite_state != SIP_INVITE_CANCELLED) { + dialog->invite_state = SIP_INVITE_EARLY_MEDIA; + } + + sip_sdp_parse(dialog, response, SIP_SDP_FAX_IGNORE, FALSE); + + if (!response->ignore && dialog->channel) { + /* Queue a progress frame only if we have SDP in 180 or 182 */ + ast_queue_control(dialog->channel, AST_CONTROL_PROGRESS); + + /* We have not sent progress, but we have been sent progress so enable early media */ + dialog->sent_progress = TRUE; + } + + ast_rtp_instance_activate(dialog->audio_rtp); + } + + sip_dialog_sched_check_pending(dialog); + break; + case 181: /* Call Is Being Forwarded */ + if (!response->ignore && dialog->invite_state != SIP_INVITE_CANCELLED) { + sip_dialog_cancel_destroy(dialog); + } + + /* Store Route-set from provisional SIP responses so early-dialog request can be routed properly */ + sip_dialog_set_contact(dialog, response); + + if (!dialog->established) { + sip_dialog_build_route(dialog, response, TRUE); + } + + if (!response->ignore && dialog->channel) { + sip_parse_diversion(dialog, response, FALSE); + sip_dialog_queue_redirecting(dialog); + } + + sip_dialog_sched_check_pending(dialog); + break; + case 183: /* Session Progress */ + if (!response->ignore && dialog->invite_state != SIP_INVITE_CANCELLED) { + sip_dialog_cancel_destroy(dialog); + } + + /* Store Route-set from provisional SIP responses so early-dialog request can be routed properly */ + sip_dialog_set_contact(dialog, response); + + if (!dialog->established) { + sip_dialog_build_route(dialog, response, TRUE); + } + + if (!response->ignore && dialog->channel) { + if (sip_parse_identity(dialog, response)) { + sip_dialog_queue_connected_line(dialog, AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER); + } + } + + if (sip_sdp_find(response)) { + if (dialog->invite_state != SIP_INVITE_CANCELLED) { + dialog->invite_state = SIP_INVITE_EARLY_MEDIA; + } + + sip_sdp_parse(dialog, response, SIP_SDP_FAX_IGNORE, FALSE); + + if (!response->ignore && dialog->channel) { + /* We have not sent progress, but we have been sent progress so enable early media */ + dialog->sent_progress = TRUE; + /* Queue a progress frame */ + ast_queue_control(dialog->channel, AST_CONTROL_PROGRESS); + } + + ast_rtp_instance_activate(dialog->audio_rtp); + } else { + /* Alcatel PBXs are known to send 183s with no SDP after sending a 100 Trying response. We're + * just going to treat this sort of thing the same as we would treat a 180 Ringing */ + if (!response->ignore && dialog->channel) { + ast_queue_control(dialog->channel, AST_CONTROL_RINGING); + } + } + + sip_dialog_sched_check_pending(dialog); + break; + case 200: /* 200 OK: on invite - someone's answering our call */ + if (!response->ignore && dialog->invite_state != SIP_INVITE_CANCELLED) { + sip_dialog_cancel_destroy(dialog); + } + + if (sip_sdp_find(response)) { + if (sip_sdp_parse(dialog, response, SIP_SDP_FAX_ACCEPT, FALSE) && !response->ignore) { + if (!dialog->established) { + /* This 200 OK's SDP is not acceptable, so we need to ack, then hangup. For + * re-invites, we try to recover */ + dialog->pending_bye = TRUE; + dialog->hangupcause = AST_CAUSE_BEARERCAPABILITY_NOTAVAIL; + + if (dialog->channel) { + ast_channel_hangupcause_set(dialog->channel, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); + sip_queue_hangup_cause(dialog, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); + } + } + } + + ast_rtp_instance_activate(dialog->audio_rtp); + } else if (!dialog->established) { + struct ast_sockaddr remote_address; + + ast_rtp_instance_get_requested_target_address(dialog->audio_rtp, &remote_address); + + if (ast_sockaddr_isnull(&remote_address) || + (!ast_strlen_zero(dialog->provisional_remote_tag) && + strcmp(dialog->remote_tag, dialog->provisional_remote_tag))) { + dialog->pending_bye = TRUE; + ast_rtp_instance_activate(dialog->audio_rtp); + } + } + + if (!response->ignore && dialog->channel) { + if (sip_parse_identity(dialog, response) || !dialog->established) { + sip_dialog_queue_connected_line(dialog, AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER); + } + } + + /* Parse contact header for continued conversation When we get 200 OK, we know which device (and IP) to + * contact for this call, This is important when we have a SIP proxy between us and the phone */ + if (dialog->outgoing) { + sip_dialog_change_inuse(dialog, SIP_RINGING_REMOVE); + sip_dialog_set_contact(dialog, response); + + /* Save Record-Route for any later requests we make on this dialogue */ + if (!dialog->established) { + sip_dialog_build_route(dialog, response, TRUE); + } + + if (dialog->nat_force_rport) { + /* NAT: Don't trust the contact field. Just use what they came to us with. We need to + * save the TRANSPORT here too */ + ast_sockaddr_copy(&dialog->address, &dialog->socket.address); + } else if (sip_get_uri_address(dialog->contact, &dialog->address)) { + /* Bad contact - we don't know how to reach this device. We need to ACK, but then send + * a bye */ + if (sip_route_empty(&dialog->route) && !response->ignore) { + dialog->pending_bye = TRUE; + } + } + } + + if (!response->ignore && dialog->channel) { + if (!dialog->established && !res) { + ast_queue_control(dialog->channel, AST_CONTROL_ANSWER); + } else { /* Re-invite */ + if (dialog->fax_state == SIP_FAX_DISABLED || dialog->fax_state == SIP_FAX_REJECTED) { + ast_queue_control(dialog->channel, AST_CONTROL_UPDATE_RTP_PEER); + } else { + ast_queue_frame(dialog->channel, &ast_null_frame); + } + } + } else { + /* It's possible we're getting an 200 OK after we've tried to disconnect by sending CANCEL. + * First send ACK, then send bye */ + if (!response->ignore) { + dialog->pending_bye = TRUE; + } + } + + /* Check for Session-Timers related headers */ + if (dialog->peer->session_timer_mode != SIP_SESSION_TIMER_MODE_REFUSE) { + /* UAS supports Session-Timers */ + if (!ast_strlen_zero(sip_message_find_header(response, "Session-Expires"))) { + if (sip_parse_session_expires(dialog, response)) { + dialog->pending_bye = TRUE; + } else if (dialog->session_timer_expires < dialog->peer->session_timer_min_expires) { + ast_debug(1, "Received Session-Expires: less than local Min-SE:, tearing down call\n"); + dialog->pending_bye = TRUE; + } + + dialog->session_timer_active = TRUE; + dialog->session_timer_remote_active = TRUE; + + sip_session_timer_start(dialog); + } else if (dialog->peer->session_timer_mode == SIP_SESSION_TIMER_MODE_ORIGINATE) { + /* UAS doesn't support Session-Timers */ + dialog->session_timer_refresher = SIP_SESSION_TIMER_REFRESHER_UAC; + dialog->session_timer_remote_active = FALSE; + + sip_session_timer_start(dialog); + } + } + + /* If I understand this right, the branch is different for a non-200 ACK only */ + dialog->invite_state = SIP_INVITE_TERMINATED; + dialog->established = TRUE; + + res = sip_request_send_ack(dialog, response->cseq, TRUE); + sip_dialog_sched_check_pending(dialog); + + if (dialog->sdp_relay_nearend || dialog->sdp_relay_farend) { + if (dialog->sdp_relay_nearend) { + dialog->sdp_relay_nearend = FALSE; + sip_recording_start(dialog->join_call_id, + dialog->join_local_tag, dialog->join_remote_tag, FALSE); + } else { + dialog->sdp_relay_farend = FALSE; + } + + dialog->ack_add_sdp = TRUE; + sip_request_send_invite(dialog, FALSE, SIP_INIT_BRANCH, NULL); + } else if (dialog->ack_add_sdp) { + dialog->ack_add_sdp = FALSE; + dialog->recording_active = TRUE; + } + + break; + case 300: /* Multiple Choices */ + case 301: /* Moved Permanently */ + case 302: /* Moved Temporarily */ + case 305: /* Use Proxy */ + if (dialog->channel) { + sip_parse_diversion(dialog, response, TRUE); + sip_set_redirecting(dialog); + } + + break; + case 401: /* Unauthorized */ + case 407: /* Proxy Authentication Required */ + res = sip_request_send_ack(dialog, response->cseq, FALSE); + + /* Forget their old tag, so we don't match tags when getting response */ + ast_string_field_set(dialog, remote_tag, NULL); + + if (!response->ignore) { + dialog->invite_state = SIP_INVITE_CALLING; + + if (!sip_dialog_handle_authentication(dialog, response)) { + break; + } + + ast_verb(3, "SIP call from '%s' to '%s' failed because '%s'\n", + sip_message_find_header(&dialog->initial_request, "From"), + sip_message_find_header(&dialog->initial_request, "To"), + response->status_line); + + sip_dialog_set_need_destroy(dialog, "authentication failure"); + sip_dialog_set_already_gone(dialog); + + if (dialog->channel) { + ast_queue_control(dialog->channel, AST_CONTROL_CONGESTION); + } + } + + break; + case 403: /* Forbidden */ + res = sip_request_send_ack(dialog, response->cseq, FALSE); + + if (!response->ignore && dialog->channel) { + sip_queue_hangup_cause(dialog, sip_hangup2cause(response->code)); + } + + ast_verb(3, "SIP call from '%s' to '%s' failed because '%s'\n", + sip_message_find_header(&dialog->initial_request, "From"), + sip_message_find_header(&dialog->initial_request, "To"), + response->status_line); + break; + case 422: /* Session-Timers: Session interval too small */ + res = sip_request_send_ack(dialog, response->cseq, FALSE); + + ast_string_field_set(dialog, remote_tag, NULL); + dialog->invite_state = SIP_INVITE_CALLING; + + if (sip_parse_min_se(dialog, response)) { + if (dialog->channel && !response->ignore) { + sip_queue_hangup_cause(dialog, sip_hangup2cause(response->code)); + } + } else { + sip_request_send_invite(dialog, TRUE, SIP_INIT_REQUEST, NULL); + } + + break; + case 480: /* Temporarily Unavailable */ + /* RFC 3261 encourages setting the reason phrase to something indicative of why the endpoint is not + * available. We will make this readable via the redirecting reason */ + res = sip_request_send_ack(dialog, response->cseq, FALSE); + + if (dialog->channel && !response->ignore) { + const char *reason = ast_skip_blanks(response->status_line + 3); + + if ((dialog->redirecting_code = ast_redirecting_reason_parse(reason)) == -1) { + ast_string_field_build(dialog, redirecting_reason, "\"%s\"", reason); + dialog->redirecting_code = AST_REDIRECTING_REASON_UNKNOWN; + } else { + ast_string_field_set(dialog, redirecting_reason, NULL); + dialog->redirecting_code = response->code; + } + + sip_dialog_queue_redirecting(dialog); + ast_queue_control(dialog->channel, AST_CONTROL_BUSY); + } + + break; + case 487: /* Transaction Cancelled */ + /* We have sent CANCEL on an outbound INVITE. This transaction is already scheduled to be killed by + * sip_channel_hangup() */ + res = sip_request_send_ack(dialog, response->cseq, FALSE); + + if (dialog->channel && !response->ignore) { + ast_queue_hangup_with_cause(dialog->channel, AST_CAUSE_NORMAL_CLEARING); + } else if (!response->ignore) { + sip_dialog_change_inuse(dialog, SIP_INUSE_REMOVE); + } + + sip_dialog_sched_check_pending(dialog); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + break; + case 415: /* Unsupported Media Type */ + case 488: /* Not Acceptable Here */ + case 606: /* Not Acceptable */ + res = sip_request_send_ack(dialog, response->cseq, FALSE); + + if (dialog->udptl && dialog->fax_state == SIP_FAX_LOCAL_REINVITE) { + sip_fax_set_state(dialog, SIP_FAX_REJECTED); + /* Try to reset RTP timers. Trigger a reinvite back to audio */ + sip_request_send_reinvite_with_sdp(dialog, FALSE, FALSE); + } else { + /* We can't set up this call, so give up */ + if (dialog->channel && !response->ignore) { + ast_queue_hangup_with_cause(dialog->channel, sip_hangup2cause(response->code)); + } + } + + break; + case 484: /* Address Incomplete */ + res = sip_request_send_ack(dialog, response->cseq, FALSE); + + if (dialog->channel) { + if (dialog->peer->allow_overlap == SIP_ALLOW_OVERLAP_INVITE) { + ast_queue_hangup_with_cause(dialog->channel, sip_hangup2cause(response->code)); + } else { + ast_queue_hangup_with_cause(dialog->channel, sip_hangup2cause(404)); + } + } + + break; + case 491: /* Pending */ + res = sip_request_send_ack(dialog, response->cseq, FALSE); + + if (dialog->channel && !response->ignore) { + if (ast_channel_state(dialog->channel) != AST_STATE_UP) { + ast_queue_hangup_with_cause(dialog->channel, sip_hangup2cause(response->code)); + } else { + sip_dialog_start_need_reinvite(dialog); + } + } + + break; + default: + res = sip_request_send_ack(dialog, response->cseq, FALSE); + + /* Something went wrong */ + if (response->code >= 400 && response->code < 700) { + if (dialog->channel && !response->ignore) { + ast_queue_hangup_with_cause(dialog->channel, sip_hangup2cause(response->code)); + } + } + + break; + } + + if (res == -1) { + ast_verb(3, "SIP acknowledgement to '%s' failed\n", dialog->call_id); + } +} + +/* Handle response to ACK */ +static void sip_handle_response_ack(struct sip_dialog *dialog, struct sip_message *response) +{ + /* Should only be 4XX or 5XX errors here? */ + if (response->code >= 400 && response->code < 700) { + ast_debug(1, "SIP %s to '%s' failed because '%s'\n", + sip_method2str(response->method), dialog->peer->name, response->status_line); + sip_dialog_set_need_destroy(dialog, "received error response"); + } +} + +/* Handle UPDATE response */ +static void sip_handle_response_update(struct sip_dialog *dialog, struct sip_message *response) +{ + switch (response->code) { + case 401: /* Unauthorized */ + case 407: /* Proxy Authorization Required */ + if (!sip_dialog_handle_authentication(dialog, response)) { + break; + } + + ast_verb(3, "Failed to authenticate %s from '%s' to '%s'\n", + sip_method2str(response->method), + sip_message_find_header(&dialog->initial_request, "To"), + sip_message_find_header(&dialog->initial_request, "From")); + + sip_dialog_set_need_destroy(dialog, "authentication failure"); + sip_dialog_set_already_gone(dialog); + + if (dialog->channel) { + ast_queue_control(dialog->channel, AST_CONTROL_CONGESTION); + } + + break; + default: + if (response->code >= 400 && response->code < 700) { + ast_debug(1, "SIP %s to '%s' failed because '%s'\n", + sip_method2str(response->method), dialog->peer->name, response->status_line); + sip_dialog_set_need_destroy(dialog, "received error response"); + } + + break; + } +} + +/* Handle response to CANCEL */ +static void sip_handle_response_cancel(struct sip_dialog *dialog, struct sip_message *response) +{ + switch (response->code) { + case 401: /* Unauthorized */ + case 407: /* Proxy Authorization Required */ + if (!sip_dialog_handle_authentication(dialog, response)) { + break; + } + + ast_verb(3, "Failed to authenticate %s from '%s' to '%s'\n", + sip_method2str(response->method), + sip_message_find_header(&dialog->initial_request, "To"), + sip_message_find_header(&dialog->initial_request, "From")); + + sip_dialog_set_need_destroy(dialog, "authentication failure"); + sip_dialog_set_already_gone(dialog); + + if (dialog->channel) { + ast_queue_control(dialog->channel, AST_CONTROL_CONGESTION); + } + + break; + case 487: /* Request Terminated */ + ast_debug(1, "Received '%s' on response to '%s'\n", + response->status_line, sip_method2str(response->method)); + break; + default: + if (response->code >= 400 && response->code < 700) { + ast_debug(1, "SIP %s to '%s' failed because '%s'\n", + sip_method2str(response->method), dialog->peer->name, response->status_line); + sip_dialog_set_need_destroy(dialog, "received error response"); + } + + break; + } +} + +/* Handle response to BYE */ +static void sip_handle_response_bye(struct sip_dialog *dialog, struct sip_message *response) +{ + switch (response->code) { + case 200: /* OK */ + sip_dialog_set_need_destroy(dialog, "received 200 response"); + dialog->established = FALSE; + + sip_parse_rtp_stats(dialog, response); + break; + case 401: /* Unauthorized */ + case 407: /* Proxy Authentication Required */ + if (!sip_dialog_handle_authentication(dialog, response)) { + break; + } + + ast_verb(3, "Failed to authenticate %s from '%s' to '%s'\n", + sip_method2str(response->method), + sip_message_find_header(&dialog->initial_request, "To"), + sip_message_find_header(&dialog->initial_request, "From")); + + sip_dialog_set_need_destroy(dialog, "authentication failure"); + sip_dialog_set_already_gone(dialog); + + if (dialog->channel) { + ast_queue_control(dialog->channel, AST_CONTROL_CONGESTION); + } + + break; + default: + /* RFC 3261 Section 15 specifies that if we receive a 408 or 481 in response to a BYE, then we should + * end the current dialog and session. It is known that at least one phone manufacturer potentially will + * send a 404 in response to a BYE, so we'll be liberal in what we accept and end the dialog and session + * if we receive any of those responses to a BYE */ + if (response->code >= 400 && response->code < 700) { + ast_debug(1, "SIP %s to '%s' failed because '%s'\n", + sip_method2str(response->method), dialog->peer->name, response->status_line); + sip_dialog_set_need_destroy(dialog, "received error response"); + } + + break; + } +} + +/* Handle response to SUBSCRIBE */ +static void sip_handle_response_subscribe(struct sip_dialog *dialog, struct sip_message *response) +{ + int expires, when; + + if (dialog->subscribe_event != SIP_SUBSCRIBE_MESSAGE_SUMMARY || !dialog->mwi_subscription) { + return; + } + + switch (response->code) { + case 200: /* OK */ + ast_verb(3, "SIP subscribed to MWI at '%s@%s' mailbox '%s'\n", + dialog->mwi_subscription->user, dialog->mwi_subscription->host, + dialog->mwi_subscription->mailbox); + + dialog->established = TRUE; + dialog->mwi_subscription->subscribed = TRUE; + + if (!(expires = atoi(sip_message_find_header(response, "Expires")))) { + expires = sip_config.default_expires; + } + + /* Refresh 30s before expiration but wih a minimum of 500ms */ + when = MAX((expires * 1000) - 3000, 500); + + ast_debug(1, "MWI subscription for '%s@%s/%s' expires in %ds (re-registration in %dms)\n", + dialog->mwi_subscription->user, dialog->mwi_subscription->host, + dialog->mwi_subscription->mailbox, expires, when); + + sip_mwi_subscription_start(dialog->mwi_subscription, when); + break; + case 401: /* Unauthorized */ + case 407: /* Proxy Authentication Required */ + /* Keep the remote_tag when resubscribing */ + if (!dialog->mwi_subscription->subscribed) { + ast_string_field_set(dialog, remote_tag, NULL); + } + + if (!sip_dialog_handle_authentication(dialog, response)) { + break; + } + + /* Fall through */ + default: + if (response->code >= 400 && response->code < 700) { + ast_verb(3, "SIP subscribe MWI to '%s@%s/%s' failed because '%s'\n", + dialog->mwi_subscription->user, dialog->mwi_subscription->host, + dialog->mwi_subscription->mailbox, response->status_line); + + ao2_ref(dialog->mwi_subscription->dialog, -1); + dialog->mwi_subscription->dialog = NULL; + + ao2_ref(dialog->mwi_subscription, -1); + sip_dialog_set_need_destroy(dialog, "received 4XX/5XX response"); + } + + break; + } +} + +/* Handle SIP response in NOTIFY transaction. We've sent a NOTIFY, now handle responses to it */ +static void sip_handle_response_notify(struct sip_dialog *dialog, struct sip_message *response) +{ + /* If this is a subscription clear the flag that indicates that we have a NOTIFY pending */ + if (!dialog->channel && dialog->pending_invite_cseq) { + dialog->pending_invite_cseq = 0; + } + + switch (response->code) { + case 200: /* OK */ + /* They got the notify, this is the end */ + if (dialog->channel) { + if (dialog->refer_state) { + ast_debug(2, "Recieved '%s' on NOTIFY for '%s' in response to REFER\n", + dialog->call_id, response->status_line); + } else { + ast_debug(2, "Received '%s' on NOTIFY for '%s'\n", + response->status_line, dialog->call_id); + } + } else { + if (!dialog->subscribe_event && dialog->refer_state) { + ast_debug(2, "Received '%s' on NOTIFY for '%s'\n", + response->status_line, dialog->call_id); + sip_dialog_set_need_destroy(dialog, "received 200 response"); + } + + if (dialog->queued_state_change) { + struct ast_state_cb_info state_info = { + .reason = 0, + .exten_state = dialog->last_exten_state, + .device_state_info = dialog->last_device_state_info, + .presence_state = dialog->last_presence_state, + .presence_subtype = dialog->last_presence_subtype, + .presence_message = dialog->last_presence_message, + }; + + /* Ready to send the next state we have on queue */ + dialog->queued_state_change = FALSE; + dialog->force_state_change = TRUE; + + sip_extension_state_event(S_OR(dialog->peer->subscribe_context, dialog->peer->context), + dialog->to_user, &state_info, dialog); + } + } + + break; + case 401: /* Unauthorized */ + case 407: /* Proxy Authentication Required */ + if (dialog->notify_headers) { + /* Only device notify can use NOTIFY auth */ + ast_string_field_set(dialog, remote_tag, NULL); + + if (!sip_dialog_handle_authentication(dialog, response)) { + break; + } + + ast_verb(3, "Failed to authenticate %s from '%s' to '%s'\n", + sip_method2str(response->method), + sip_message_find_header(&dialog->initial_request, "To"), + sip_message_find_header(&dialog->initial_request, "From")); + + sip_dialog_set_need_destroy(dialog, "authentication failure"); + sip_dialog_set_already_gone(dialog); + } + + /* Fall through */ + default: + if (response->code >= 400 && response->code < 700) { + ast_verb(3, "SIP %s to '%s' failed because '%s'\n", + sip_method2str(response->method), dialog->peer->name, response->status_line); + sip_dialog_set_need_destroy(dialog, "received error response"); + } + + break; + } +} + +/* Handle SIP response in REFER transaction. We've sent a REFER, now handle responses to it */ +static void sip_handle_response_refer(struct sip_dialog *dialog, struct sip_message *response) +{ + enum ast_control_transfer transfer = AST_TRANSFER_FAILED; + + /* If no refer status exists, then do nothing */ + if (!dialog->refer_state) { + return; + } + + switch (response->code) { + case 200: /* OK */ + case 202: /* Accepted */ + /* We need to do something here. The transferee is now sending INVITE to target we should hang around, + * waiting for NOTIFY's here */ + dialog->refer_state = SIP_REFER_ACCEPTED; + ast_debug(3, "Received '%s' on transfer for '%s'\n", response->status_line, response->call_id); + break; + case 401: /* Unauthorized */ + case 407: /* Proxy Authentication Required */ + if (!sip_dialog_handle_authentication(dialog, response)) { + break; + } + + ast_verb(3, "Failed to authenticate %s from '%s' to '%s'\n", + sip_method2str(response->method), + sip_message_find_header(&dialog->initial_request, "To"), + sip_message_find_header(&dialog->initial_request, "From")); + + dialog->refer_state = SIP_REFER_AUTHORIZATION_FAILED; + + if (dialog->channel) { + ast_queue_control_data(dialog->channel, AST_CONTROL_TRANSFER, &transfer, sizeof(transfer)); + } + + sip_dialog_set_need_destroy(dialog, "failed to authenticate REFER"); + break; + case 405: /* Method Not Allowed */ + /* Return to the current call onhold. Status flag needed to be reset */ + ast_debug(1, "Transfer to '%s' failed, REFER not allowed\n", dialog->refer_to_user); + + sip_dialog_set_need_destroy(dialog, "received 405 response"); + dialog->refer_state = SIP_REFER_FAILED; + + if (dialog->channel) { + ast_queue_control_data(dialog->channel, AST_CONTROL_TRANSFER, &transfer, sizeof(transfer)); + } + + break; + case 481: /* Call/Transaction Does Not Exist */ + /* A transfer with Replaces did not work */ + ast_debug(1, "Remote host can't match REFER request on '%s', giving up\n", dialog->call_id); + + if (dialog->channel) { + ast_queue_control(dialog->channel, AST_CONTROL_CONGESTION); + } + + sip_dialog_set_need_destroy(dialog, "received 481 response"); + break; + case 500: /* Internal Server Error */ + case 501: /* Method Not Implemented */ + /* Return to the current call onhold. Status flag needed to be reset */ + ast_debug(1, "Transfer to '%s' failed, call miserably fails\n", dialog->refer_to_user); + + sip_dialog_set_need_destroy(dialog, "received 500/501 response"); + dialog->refer_state = SIP_REFER_FAILED; + + if (dialog->channel) { + ast_queue_control_data(dialog->channel, AST_CONTROL_TRANSFER, &transfer, sizeof(transfer)); + } + + break; + case 603: /* Declined */ + ast_debug(1, "Transfer to '%s' declined, call miserably fails\n", dialog->refer_to_user); + + dialog->refer_state = SIP_REFER_FAILED; + sip_dialog_set_need_destroy(dialog, "received 603 response"); + + if (dialog->channel) { + ast_queue_control_data(dialog->channel, AST_CONTROL_TRANSFER, &transfer, sizeof(transfer)); + } + + break; + default: + /* We should treat unrecognized 9xx as 900. 400 is actually specified as a possible response, but any + * 4xx-6xx is theoretically possible */ + if (response->code >= 400 && response->code < 700) { + ast_verb(3, "SIP transfer to '%s' failed because '%s'\n", + dialog->refer_to_user, response->status_line); + + dialog->refer_state = SIP_REFER_FAILED; + sip_dialog_set_need_destroy(dialog, "received failure response"); + + if (dialog->channel) { + ast_queue_control_data(dialog->channel, AST_CONTROL_TRANSFER, + &transfer, sizeof(transfer)); + } + } + + break; + } +} + +/* Handle responses on REGISTER to services */ +static void sip_handle_response_register(struct sip_dialog *dialog, struct sip_message *response) +{ + char *contact, *expires, *headers; + int when; + + if (!dialog->registration) { + ast_debug(1, "Received '%s' on REGISTER, but there is not registration for '%s'\n", + response->status_line, dialog->peer->name); + + sip_dialog_set_need_destroy(dialog, "no registration"); + return; + } + + switch (response->code) { + case 200: /* 200 OK */ + if (!dialog->registration->last_registered) { + ast_verb(3, "SIP registered to '%s@%s'\n", + dialog->registration->user, dialog->registration->host); + } + + sip_registration_stop_timeout(dialog->registration); + + dialog->registration->state = SIP_REGISTRATION_REGISTERED; + dialog->registration->last_registered = time(NULL); /* Reset time of last successful registration */ + dialog->registration->attempts = 0; + + /* Set us up for re-registering figure out how long we got registered for according to section 6.13 of + * RFC, contact headers override expires headers, so check those first */ + expires = ast_strdupa(sip_message_find_header(response, "Expires")); + + if (ast_strlen_zero(expires)) { + contact = ast_strdupa(sip_message_find_header(response, "Contact")); + + if (!sip_parse_contact(contact, NULL, NULL, NULL, NULL, &headers)) { + sip_parse_parameters(headers, ';', "expires", &expires, NULL); + } + } + + if (!(dialog->registration->expires = atoi(expires))) { + dialog->registration->expires = sip_config.default_expires; + } + + /* Refresh 30s before expiration but wih a minimum of 500ms */ + when = MAX((dialog->registration->expires * 1000) - 3000, 500); + + ast_debug(1, "Registration for '%s@%s' expires in %ds (re-registration in %dms)\n", + dialog->registration->user, dialog->registration->host, dialog->registration->expires, when); + + /* Schedule re-registration before we expire */ + sip_registration_sched_resend(dialog->registration, when); + ast_system_publish_registry("SIP", dialog->registration->user, dialog->registration->host, + "Registered", NULL); + break; + case 401: /* Unauthorized */ + case 407: /* Proxy Authentication Required */ + if (sip_config.register_max_attempts && + dialog->registration->attempts >= sip_config.register_max_attempts) { + break; + } + + dialog->registration->attempts++; + + if (!sip_dialog_handle_authentication(dialog, response)) { + break; + } + + ast_verb(3, "SIP registration to '%s@%s' failed to authenticate (attempt %d)\n", + dialog->registration->user, dialog->registration->host, dialog->registration->attempts); + sip_dialog_set_need_destroy(dialog, "failed to authenticate REGISTER"); + + if (dialog->registration->dialog) { + ao2_ref(dialog->registration->dialog, -1); + dialog->registration->dialog = NULL; + } + + break; + case 408: /* Request Timeout */ + /* Received a timeout response, so reset the counter of failed responses */ + dialog->registration->attempts = 0; + break; + case 423: /* Interval Too Brief */ + ast_debug(1, "Received '%s' on SIP register to '%s@%s', minimum is %ds\n", + response->status_line, dialog->registration->user, dialog->registration->host, + dialog->registration->expires); + + dialog->registration->expires = atoi(sip_message_find_header(response, "Min-Expires")); + + if (dialog->registration->expires > sip_config.register_max_expires) { + ast_verb(3, "SIP registration to '%s@%s' failed because expires of %ds is too high\n", + dialog->registration->user, dialog->registration->host, dialog->registration->expires); + + dialog->registration->state = SIP_REGISTRATION_REJECTED; + sip_registration_stop_timeout(dialog->registration); + } else { + dialog->registration->state = SIP_REGISTRATION_UNREGISTERED; + sip_request_send_register(dialog, FALSE); + } + + ast_system_publish_registry("SIP", dialog->registration->user, dialog->registration->host, + dialog->registration->state == SIP_REGISTRATION_REJECTED ? "Rejected" : "Unregistered", NULL); + sip_dialog_set_need_destroy(dialog, "received 423 response"); + + if (dialog->registration->dialog) { + ao2_ref(dialog->registration->dialog, -1); + dialog->registration->dialog = NULL; + } + + break; + case 403: /* Forbidden */ + if (response->code == 403 && sip_config.register_retry_forbidden) { + ast_debug(1, "Treating 403 response to REGISTER as non-fatal for '%s@%s'\n", + dialog->registration->user, dialog->registration->host); + ast_string_field_set(dialog, nonce, NULL); + break; + } + /* Fall through */ + default: + if (response->code >= 400 && response->code < 700) { + ast_verb(3, "SIP registration to '%s@%s' failed because '%s'\n", + dialog->registration->user, dialog->registration->host, response->status_line); + + if (response->code == 403) { + dialog->registration->state = SIP_REGISTRATION_AUTHORIZATION_FAILED; + } else { + dialog->registration->state = SIP_REGISTRATION_REJECTED; + } + + sip_registration_stop_timeout(dialog->registration); + ast_system_publish_registry("SIP", dialog->registration->user, dialog->registration->host, + dialog->registration->state == SIP_REGISTRATION_REJECTED ? "Rejected" : "Authorization Failed", + NULL); + sip_dialog_set_need_destroy(dialog, "received 4xx response"); + + if (dialog->registration->dialog) { + ao2_ref(dialog->registration->dialog, -1); + dialog->registration->dialog = NULL; + } + } + + break; + } +} + +/* Handle qualification responses (OPTIONS) */ +static void sip_handle_response_options(struct sip_dialog *dialog, struct sip_message *response) +{ + int state_changed, is_reachable, was_reachable, old_qualify; + const char *status; + char qualify[32]; + struct sip_alias *alias; + + /* We don't really care what the response is, just that it replied back. Well, as long as it's not a 100 + * response since we might need to hang around for something more "definitive" */ + if (response->code == 100) { + return; + } + + ao2_ref(dialog->peer->qualify_dialog, -1); + dialog->peer->qualify_dialog = NULL; + + if (!dialog->peer->qualify_max) { /* This should never happens */ + sip_dialog_set_need_destroy(dialog, "received OPTIONS response but qualify is not enabled"); + return; + } + + /* Compute the response time to a ping (goes in peer->qualify) -1 means did not respond, 0 means unknown, + * 1..maxms is a valid response, >maxms means late response */ + old_qualify = dialog->peer->qualify; + dialog->peer->qualify = MAX(ast_tvdiff_ms(ast_tvnow(), dialog->peer->qualify_sent), 1); + + /* Now determine new state and whether it has changed. Use some helper variables to simplify the writing of + * the expressions */ + was_reachable = old_qualify > 0 && old_qualify <= dialog->peer->qualify_max; + is_reachable = dialog->peer->qualify <= dialog->peer->qualify_max; + + state_changed = old_qualify == 0 /* Yes, unknown before */ || was_reachable != is_reachable; + status = is_reachable ? "reachable" : "lagged"; + + if (sip_config.realtime_update_peer) { + snprintf(qualify, sizeof(qualify), "%d", dialog->peer->qualify); + } + + if (state_changed) { + ast_verb(3, "SIP peer '%s' is now %s at %dms\n", dialog->peer->name, status, dialog->peer->qualify); + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", dialog->peer->name); + + if (sip_config.realtime_update_peer) { + ast_update_realtime("sippeers", "name", dialog->peer->name, "lastqualify", qualify, SENTINEL); + } + + if (dialog->peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + + ast_endpoint_set_state(dialog->peer->endpoint, AST_ENDPOINT_ONLINE); + blob = ast_json_pack("{s: s, s: i}", "peer_status", status, "time", dialog->peer->qualify); + ast_endpoint_blob_publish(dialog->peer->endpoint, ast_endpoint_state_type(), blob); + } + } + + AST_LIST_TRAVERSE(&dialog->peer->aliases, alias, next) { + if (!alias->peer) { + continue; + } + + alias->peer->qualify = dialog->peer->qualify; + + if (state_changed) { + ast_verb(3, "SIP peer '%s' is now %s at %dms\n", + alias->peer->name, status, alias->peer->qualify); + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); + + if (sip_config.realtime_update_peer) { + ast_update_realtime("sippeers", "name", alias->peer->name, "lastqualify", qualify, SENTINEL); + } + + if (alias->peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + + ast_endpoint_set_state(alias->peer->endpoint, AST_ENDPOINT_ONLINE); + blob = ast_json_pack("{s: s, s: i}", + "peer_status", status, "time", alias->peer->qualify); + ast_endpoint_blob_publish(alias->peer->endpoint, ast_endpoint_state_type(), blob); + } + } + } + + sip_dialog_set_need_destroy(dialog, "received OPTIONS response"); + + /* Try again eventually */ + AST_SCHED_REPLACE_UNREF(dialog->peer->qualify_sched_id, sip_sched_context, + (is_reachable ? dialog->peer->qualify_expires : SIP_QUALIFY_UNREACHABLE) * 1000, + sip_peer_qualify, dialog->peer, ao2_cleanup(_data), ao2_cleanup(dialog->peer), ao2_bump(dialog->peer)); +} + +/* Handle PUBLISH response */ +static void sip_handle_response_publish(struct sip_dialog *dialog, struct sip_message *response) +{ + /* We don't send PUBLISH at the moment so this is an error if we get something */ + sip_dialog_set_already_gone(dialog); + sip_dialog_set_need_destroy(dialog, "received PUBLISH response"); +} + +/* Handle responses to INFO messages. The INFO method MUST NOT change the state of calls or related sessions + * (RFC 2976) */ +static void sip_handle_response_info(struct sip_dialog *dialog, struct sip_message *response) +{ + switch (response->code) { + case 200: /* OK */ + if (!dialog->channel) { + sip_dialog_set_need_destroy(dialog, "received INFO response\n"); + } + + break; + case 401: /* Unauthorizaed */ + case 407: /* Proxy Authentication Required */ + if (!sip_dialog_handle_authentication(dialog, response) || dialog->established) { + break; + } + + /* Fall through */ + default: + if (response->code >= 400 && response->code < 700) { + ast_verb(3, "SIP %s to '%s' failed because '%s'\n", + sip_method2str(response->method), dialog->peer->name, response->status_line); + + if (!dialog->channel) { + sip_dialog_set_need_destroy(dialog, "received error response\n"); + } + } + + break; + } +} + +/* Handle responses to MESSAGE messages. The MESSAGE method should not change the state of calls or related sessions + * if associated with a dialog. (Implied by RFC 3428 Section 2) */ +static void sip_handle_response_message(struct sip_dialog *dialog, struct sip_message *response) +{ + switch (response->code) { + case 200: /* OK */ + if (!dialog->channel) { + sip_dialog_set_need_destroy(dialog, "received MESSAGE response\n"); + } + + break; + case 401: /* Unauthorized */ + case 407: /* Proxy Authentication Required */ + if (!sip_dialog_handle_authentication(dialog, response) || dialog->established) { + break; + } + + /* Fall through */ + default: + if (response->code >= 400 && response->code < 700) { + ast_verb(3, "SIP %s to '%s' failed because '%s'\n", + sip_method2str(response->method), dialog->peer->name, response->status_line); + + if (!dialog->established) { + sip_dialog_set_need_destroy(dialog, "received error response"); + } + } + + break; + } +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/authentication_realms.h asterisk-22.6.0/channels/sip/include/authentication_realms.h --- asterisk-22.6.0.orig/channels/sip/include/authentication_realms.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/authentication_realms.h 2025-10-21 18:12:24.469604591 +1300 @@ -0,0 +1,43 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_AUTH_REALMS_H +#define _SIP_AUTH_REALMS_H + +/* Credentials for authentication to other SIP services */ +struct sip_authentication_realm { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(realm); /* Realm in which these credentials are valid */ + AST_STRING_FIELD(user); /* Username */ + AST_STRING_FIELD(secret); /* Secret */ + AST_STRING_FIELD(md5_secret); /* MD5Secret */ + ); +}; + +/* Container of SIP authentication credentials */ +extern struct ao2_container *sip_authentication_realms; + +int sip_authentication_realm_cmp(void *data, void *arg, int flags); +int sip_authentication_realm_build(struct ao2_container *authentication_realms, const char *config, int lineno); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/callback.h asterisk-22.6.0/channels/sip/include/callback.h --- asterisk-22.6.0.orig/channels/sip/include/callback.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/callback.h 2025-10-21 18:12:24.469604591 +1300 @@ -0,0 +1,42 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_CALLBACK_H +#define _SIP_CALLBACK_H + +/* Forward declarations */ +struct sip_peer; +struct sip_dialog; +struct sip_remotecc_data; + +struct sip_callback { + int state_id; + char *exten; + unsigned int busy:1; +}; + +int sip_callback_build(struct sip_dialog *dialog, struct sip_message *request, + const char *call_id, const char *local_tag, const char *remote_tag, const char *user_call_data); +void sip_callback_destroy(struct sip_callback *callback); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/channel_tech.h asterisk-22.6.0/channels/sip/include/channel_tech.h --- asterisk-22.6.0.orig/channels/sip/include/channel_tech.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/channel_tech.h 2025-10-21 18:12:24.470604564 +1300 @@ -0,0 +1,60 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_CHANNEL_TECH_H +#define _SIP_CHANNEL_TECH_H + +/* Forward declarations */ +struct sip_dialog; + +extern struct ast_channel_tech sip_channel_tech; +extern struct ast_sip_api_tech sip_api_tech; +extern const struct ast_msg_tech sip_msg_tech; + +struct ast_channel *sip_channel_requester(const char *type, struct ast_format_cap *cap, + const struct ast_assigned_ids *assigned_ids, const struct ast_channel *requestor_channel, + const char *destination, int *cause); +int sip_channel_call(struct ast_channel *channel, const char *destination, int timeout); +int sip_channel_hangup(struct ast_channel *channel); +int sip_channel_answer(struct ast_channel *channel); +struct ast_frame *sip_channel_read(struct ast_channel *channel); +int sip_channel_write(struct ast_channel *channel, struct ast_frame *frame); +int sip_channel_indicate(struct ast_channel *channel, int condition, const void *data, size_t data_len); +int sip_channel_transfer(struct ast_channel *channel, const char *destintation); +int sip_channel_fixup(struct ast_channel *old_channel, struct ast_channel *new_channel); +int sip_channel_send_digit_begin(struct ast_channel *channel, char digit); +int sip_channel_send_digit_end(struct ast_channel *channel, char digit, unsigned int duration); +int sip_channel_setoption(struct ast_channel *channel, int option, void *data, int data_len); +int sip_channel_queryoption(struct ast_channel *channel, int option, void *data, int *data_len); +int sip_channel_devicestate(const char *data); +int sip_channel_presencestate(const char *data, char **subtype, char **message); +int sip_channel_send_html(struct ast_channel *channel, int subclass, const char *data, int data_len); +int sip_channel_send_text(struct ast_channel *channel, const char *text); +const char *sip_channel_get_pvt_uniqueid(struct ast_channel *channel); + +int sip_api_sipinfo_send(struct ast_channel *channel, struct ast_variable *headers, const char *content_type, + const char *content, const char *useragent_filter); + +int sip_msg_send(const struct ast_msg *msg, const char *to, const char *from); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/cli_commands.h asterisk-22.6.0/channels/sip/include/cli_commands.h --- asterisk-22.6.0.orig/channels/sip/include/cli_commands.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/cli_commands.h 2025-10-21 18:12:24.470604564 +1300 @@ -0,0 +1,57 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_CLI_COMMANDS_H +#define _SIP_CLI_COMMANDS_H + +char *sip_cli_set_debug(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_reload(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_unregister(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_prune_realtime(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); + +char *sip_cli_notify(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_do_not_disturb(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_call_forward(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_hunt_group(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_qualify_peer(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); + +char *sip_cli_show_settings(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_show_peers(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_show_sched(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_show_tcp(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_show_peers(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_show_peer(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_show_calls(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_show_subscriptions(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_show_dialog(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_show_stats(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_show_mwi(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_show_registrations(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_show_inuse(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_show_domains(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); +char *sip_cli_show_objects(struct ast_cli_entry *entry, enum ast_cli_command command, struct ast_cli_args *args); + +extern struct ast_cli_entry sip_cli_commands[]; +extern const int sip_cli_commands_count; + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/conference.h asterisk-22.6.0/channels/sip/include/conference.h --- asterisk-22.6.0.orig/channels/sip/include/conference.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/conference.h 2025-10-21 18:12:24.471604537 +1300 @@ -0,0 +1,81 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_CONFERENCE_H +#define _SIP_CONFERENCE_H + +/* Forward declarations */ +struct sip_dialog; +struct sip_message; +struct sip_peer; + +struct sip_conference { + int id; + int next_participant_id; + struct ast_bridge *bridge; + unsigned int keep:1; + unsigned int multi_admin:1; + int administrator_count; + int user_count; + AST_LIST_HEAD_NOLOCK(, sip_participant) participants; +}; + +AST_LIST_HEAD(sip_conferences_head, sip_conference); + +struct sip_participant { + AST_LIST_ENTRY(sip_participant) next; + int id; + struct sip_conference *conference; + struct ast_channel *channel; + unsigned int administrator:1; + unsigned int removed:1; + unsigned int muted:1; + unsigned int talking:1; +}; + +struct sip_selected { + AST_LIST_ENTRY(sip_selected) next; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(call_id); + AST_STRING_FIELD(local_tag); + AST_STRING_FIELD(remote_tag); + ); +}; + +extern struct ao2_container *sip_conferences; + +int sip_conference_cmp(void *data, void *arg, int flags); +int sip_conference_build(struct sip_dialog *dialog, struct sip_message *request, + const char *call_id, const char *local_tag, const char *remote_tag, + const char *join_call_id, const char *join_local_tag, const char *join_remote_tag, int joining); +int sip_conference_participants(struct sip_dialog *dialog, struct sip_message *request, + const char *call_id, const char *local_tag, const char *remote_tag, int conference_id, + const char *user_call_data); +int sip_conference_remove_last(struct sip_dialog *dialog, struct sip_message *request, + const char *call_id, const char *local_tag, const char *remote_tag); + +int sip_selected_add(struct sip_dialog *dialog, const char *call_id, const char *local_tag, const char *remote_tag); +int sip_selected_remove(struct sip_dialog *dialog, const char *call_id, const char *local_tag, const char *remote_tag); +void sip_selected_destroy(struct sip_selected *selected); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/config.h asterisk-22.6.0/channels/sip/include/config.h --- asterisk-22.6.0.orig/channels/sip/include/config.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/config.h 2025-10-21 18:12:24.471604537 +1300 @@ -0,0 +1,134 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_CONFIG_H +#define _SIP_CONFIG_H + +/* We store separately the debugging requests from the config file and requests from the CLI. Debugging is enabled if + * either is set (if debug is set in the config file, we can only turn it off by reloading the config) */ +enum sip_debug { + SIP_DEBUG_CONFIG = 1 << 0, + SIP_DEBUG_CONSOLE = 1 << 1, +}; + +/* Global settings apply to the channel (often settings you can change in the general section of sip.conf */ +struct sip_config { + int timer_t1; /* T1 time */ + int min_timer_t1; /* T1 roundtrip time minimum */ + int timer_b; /* Timer B - RFC 3261 Section 17.1.1.2 */ + char language[MAX_LANGUAGE]; /* Default language setting for new channels */ + char moh_interpret[MAX_MUSICCLASS]; /* Global setting for moh class to use when put on hold */ + char moh_suggest[MAX_MUSICCLASS]; /* Global setting for moh class to suggest when putting a bridged channel on + * hold */ + char from_domain[AST_MAX_EXTENSION]; /* Default domain on outbound messages */ + int from_domain_port; /* Default domain port on outbound messages */ + char tone_zone[MAX_TONEZONE_COUNTRY]; /* Default tone zone for channels created from the SIP driver */ + char realm[MAXHOSTNAMELEN]; /* Default realm */ + char useragent[AST_MAX_EXTENSION]; /* Useragent for the SIP channel */ + char sdp_username[AST_MAX_EXTENSION]; /* SDP username for the SIP channel */ + char sdp_session[AST_MAX_EXTENSION]; /* SDP session name for the SIP channel */ + int rtp_timeout; /* Time out call if no RTP */ + int rtp_hold_timeout; /* Time out call if no RTP during hold */ + int rtp_keepalive; /* Send RTP keepalives */ + int qualify_max; /* Default Qualify= setting */ + int qualify_expires; /* Qualify frequency */ + int qualify_gap; /* Time between our group of peer qualify */ + int qualify_peers; /* Number of peers to qualify at a given time */ + struct ast_format_cap *format_cap; /* Supported codecs */ + unsigned int tcp_enabled:1; /* TCP enabled */ + int tcp_authentication_limit; /* TCP auth limit */ + int tcp_authentication_timeout; /* TCP auth timeout */ + int register_timeout; /* Global time between attempts for outbound registrations */ + int register_max_attempts; /* Registration attempts before giving up */ + unsigned int register_retry_forbidden:1; /* Treat 403 responses to registrations as 401 responses */ + struct ast_sockaddr udp_bind_address; /* UDP: The address we bind to */ + struct ast_sockaddr rtp_bind_address; /* RTP: The address we bind to */ + struct ast_sockaddr external_address; /* External IP address if we are behind NAT */ + char external_host[MAXHOSTNAMELEN]; /* External host name */ + int external_expires; /* Refresh timer for DNS-based external address (dyndns) */ + uint16_t external_tcp_port; /* External tcp port */ + uint16_t external_tls_port; /* External tls port */ + struct ast_sockaddr media_address; /* External RTP IP address if we are behind NAT */ + struct ast_ha *internal_networks; /* List of internal networks. We store "localnet" addresses from the config + * file into an access list marked as 'DENY', so the call to ast_apply_ha will + * return AST_SENSE_DENY for 'local' addresses, and AST_SENSE_ALLOW for 'non + * local' (i.e. presumably public) addresses */ + unsigned int direct_rtp_setup:1; /* Enable support for Direct RTP setup (no re-invites) */ + unsigned int srv_lookup:1; /* SRV Lookup on or off. Default is off */ + unsigned int always_send_unauthorized:1; /* Send 401 Unauthorized for all failing requests */ + unsigned int authentication_failure_events:1; /* Whether we send authentication failure manager events */ + unsigned int authenticate_options:1; /* Authenticate OPTIONS requests */ + unsigned int allow_message:1; /* Accept MESSAGE outside of a call */ + unsigned int shrink_callerid:1; /* Shrink Caller IDs */ + unsigned int allow_external_domains:1; /* Accept calls to external SIP domains? */ + unsigned int allow_early_media:1; /* Enable/disable premature frames in a call (causing 183 early media) */ + unsigned int notify_callerid:1; /* Send caller ID with ringing notifications */ + unsigned int pickup_context:1; /* When to use context when doing a call pickup */ + unsigned int nat_force_rport:1; /* Force rport even if not present in the request */ + unsigned int nat_auto_rport:1; /* Set nat_force_rport when NAT is detected */ + unsigned int nat_rtp:1; /* Whether symmetric RTP is enabled */ + unsigned int nat_auto_rtp:1; /* Set symmetric rtp when NAT is detected */ + unsigned int domains_as_realm:1; /* Use domains lists as realms */ + unsigned int match_authorization_username:1; /* Match auth username if available instead of From: */ + unsigned int tos_sip; /* IP type of service for SIP packets */ + unsigned int tos_audio; /* IP type of service for audio RTP packets */ + unsigned int tos_video; /* IP type of service for video RTP packets */ + unsigned int tos_text; /* IP type of service for text RTP packets */ + unsigned int cos_sip; /* 802.1p class of service for SIP packets */ + unsigned int cos_audio; /* 802.1p class of service for audio RTP packets */ + unsigned int cos_video; /* 802.1p class of service for video RTP packets */ + unsigned int cos_text; /* 802.1p class of service for text RTP packets */ + int default_expires; /* Default expires */ + int register_min_expires; /* Minimum accepted registration time */ + int register_max_expires; /* Maximum accepted registration time */ + int subscribe_min_expires; /* Minimum accepted subscription time */ + int subscribe_max_expires; /* Maximum accepted subscription time */ + int mwi_expires; /* Default MWI expires */ + struct sip_proxy proxy; /* Outbound proxy */ + unsigned int realtime_update_peer:1; /* Update database with registration data for peer? */ + unsigned int realtime_save_sysname:1; /* Save system name at registration? */ + unsigned int realtime_save_path:1; /* Save path header on registration */ + unsigned int realtime_cache_peer:1; /* Cache realtime peers */ + unsigned int realtime_auto_clear; /* Realtime auto expiry */ + unsigned int realtime_ignore_expires:1; /* Ignore expiration of peer */ +}; + +extern struct sip_config sip_config; +extern struct ast_jb_conf sip_jb_config; +extern struct ast_tls_config sip_tls_config; + +extern ast_mutex_t sip_config_lock; +extern int sip_config_reloading; + +extern int sip_debug; +extern struct ast_sockaddr sip_debug_address; +extern struct ast_sockaddr sip_our_address; + +int sip_config_load(void); +int sip_config_unload(void); +int sip_config_reload(enum channelreloadreason reason); +int sip_config_parse(void); + +struct stasis_message_type *sip_session_timeout_type(void); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/dialog.h asterisk-22.6.0/channels/sip/include/dialog.h --- asterisk-22.6.0.orig/channels/sip/include/dialog.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/dialog.h 2025-10-21 18:12:24.472604511 +1300 @@ -0,0 +1,415 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_DIALOG_H +#define _SIP_DIALOG_H + +enum sip_rtp_fd { + SIP_AUDIO_RTP_FD = 0, + SIP_AUDIO_RTCP_FD, + SIP_VIDEO_RTP_FD, + SIP_VIDEO_RTCP_FD, + SIP_TEXT_RTP_FD, + SIP_UDPTL_FD, +}; + +enum sip_change_inuse_type { + SIP_INUSE_ADD = 0, + SIP_INUSE_REMOVE, + SIP_RINGING_ADD, + SIP_RINGING_REMOVE, +}; + +enum sip_onhold_mode { + SIP_ONHOLD_SENDRECV = 0, + SIP_ONHOLD_RECVONLY, + SIP_ONHOLD_INACTIVE, +}; + +enum sip_check_authorization_result { + SIP_AUTHORIZATION_DONT_KNOW = -10, + SIP_AUTHORIZATION_SESSION_LIMIT = -9, + SIP_AUTHORIZATION_RTP_FAILED = -8, + SIP_AUTHORIZATION_INVALID_TRANSPORT = -7, + SIP_AUTHORIZATION_ACL_FAILED = -6, + SIP_AUTHORIZATION_PEER_NOT_DYNAMIC = -5, + SIP_AUTHORIZATION_UNKNOWN_DOMAIN = -4, + SIP_AUTHORIZATION_NOT_FOUND = -3, + SIP_AUTHORIZATION_USERNAME_MISMATCH = -2, + SIP_AUTHORIZATION_SECRET_FAILED = -1, + SIP_AUTHORIZATION_SUCCESS = 0, + SIP_AUTHORIZATION_CHALLENGE_SENT = 1, +}; + +enum sip_get_destination_result { + SIP_DESTINATION_INVALID_URI = -3, + SIP_DESTINATION_REFUSED = -2, + SIP_DESTINATION_EXTEN_NOT_FOUND = -1, + SIP_DESTINATION_EXTEN_FOUND = 0, + SIP_DESTINATION_EXTEN_MATCH_MORE = 1, +}; + +/* States for the INVITE transaction, not the dialog */ +enum sip_invite_state { + SIP_INVITE_NONE = 0, + SIP_INVITE_CALLING, /* Invite sent, no answer */ + SIP_INVITE_PROCEEDING, /* We got/sent 1xx message */ + SIP_INVITE_EARLY_MEDIA, /* We got 18x message with to-tag back */ + SIP_INVITE_COMPLETED, /* Got final response with error. Wait for ACK, then CONFIRMED */ + SIP_INVITE_CONFIRMED, /* Confirmed response - we've got an ack (Incoming calls only) */ + SIP_INVITE_TERMINATED, /* Transaction done - either successful (AST_STATE_UP) or failed, but done + * The only way out of this is a BYE from one side */ + SIP_INVITE_CANCELLED, /* Transaction cancelled by client or server in non-terminated state */ +}; + +/* Status of transfer */ +enum sip_refer_state { + SIP_REFER_NONE = 0, + SIP_REFER_SENT, /* Sent REFER to transferee */ + SIP_REFER_ACCEPTED, /* Accepted by transferee */ + SIP_REFER_SUCCESS, /* Answered by transfer target */ + SIP_REFER_FAILED, /* REFER declined */ + SIP_REFER_AUTHORIZATION_FAILED, /* We failed to authenticate REFER */ +}; + +/* Type of subscription, based on the packages we do support */ +enum sip_subscribe_event { + SIP_SUBSCRIBE_NONE = 0, + SIP_SUBSCRIBE_DIALOG, + SIP_SUBSCRIBE_PRESENCE, + SIP_SUBSCRIBE_MESSAGE_SUMMARY, + SIP_SUBSCRIBE_FEATURE_EVENTS, + SIP_SUBSCRIBE_REMOTECC, +}; + +enum sip_feature_event { + SIP_FEATURE_NONE = 0, + SIP_FEATURE_BULK_UPDATE, + SIP_FEATURE_DO_NOT_DISTURB, + SIP_FEATURE_CALL_FORWARD +}; + +/* Forward declarations */ +struct sip_peer; +struct sip_registration; +struct sip_mwi_subscription; +struct sip_sdp_media; + +/* SIP dialog */ +struct sip_dialog { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(call_id); /* Global Call-ID */ + AST_STRING_FIELD(local_tag); /* Our tag for this session */ + AST_STRING_FIELD(remote_tag); /* Their tag */ + AST_STRING_FIELD(via_branch); /* The branch ID from the topmost Via header in the initial request */ + AST_STRING_FIELD(via_sent_by); /* The sent-by from the topmost Via header in the initial request */ + AST_STRING_FIELD(uri); /* Original requested URI */ + AST_STRING_FIELD(branch); /* The branch identifier of this session */ + AST_STRING_FIELD(invite_branch); /* The branch used when we sent the initial INVITE */ + AST_STRING_FIELD(from); /* The From: header */ + AST_STRING_FIELD(useragent); /* User agent in SIP request */ + AST_STRING_FIELD(unsupported_options); /* Unsupported options from Supported or Require */ + AST_STRING_FIELD(caller_number); /* Caller*ID number */ + AST_STRING_FIELD(caller_name); /* Caller*ID name */ + AST_STRING_FIELD(caller_tag); /* Caller*ID tag */ + AST_STRING_FIELD(from_domain); /* Domain to show in the from field */ + AST_STRING_FIELD(from_user); /* User to show in the user field */ + AST_STRING_FIELD(from_name); /* Name to show in the user field */ + AST_STRING_FIELD(to_user); /* Extension where to start */ + AST_STRING_FIELD(to_host); /* Host we should put in the "to" field */ + AST_STRING_FIELD(contact); /* The Contact: that the UA registers with us */ + AST_STRING_FIELD(our_contact); /* Our contact header, we only store the part in <> in this field */ + AST_STRING_FIELD(html_url); /* URL to be sent with next message to peer */ + AST_STRING_FIELD(device_name); /* The argument used to call this SIP endpoint */ + AST_STRING_FIELD(last_presence_subtype); /* The last presence subtype sent for a subscription */ + AST_STRING_FIELD(last_presence_message); /* The last presence message for a subscription */ + AST_STRING_FIELD(sdp_unique); /* Remote UA's SDP Session unique parts */ + AST_STRING_FIELD(join_call_id); /* Join call-id */ + AST_STRING_FIELD(join_local_tag); /* Join tag */ + AST_STRING_FIELD(join_remote_tag); /* Join remote_tag */ + AST_STRING_FIELD(provisional_status_line); /* The last successfully transmitted provisional response */ + AST_STRING_FIELD(provisional_remote_tag); /* Provisional remote tag */ + AST_STRING_FIELD(notify_content); /* Content for NOTIFY requests */ + AST_STRING_FIELD(refer_to); /* Outgoin Refer-to header */ + AST_STRING_FIELD(refer_to_user); /* Place to store Refer-To domain */ + AST_STRING_FIELD(refer_to_domain); /* Place to store Refer-To domain */ + AST_STRING_FIELD(refer_to_context); /* Place to store Refer-To context */ + AST_STRING_FIELD(referred_by); /* Place to store REFERRED-BY extension */ + AST_STRING_FIELD(content_id); /* Outgoing Content-ID header */ + AST_STRING_FIELD(content_type); /* Outgoing Content-Type header */ + AST_STRING_FIELD(refer_content); /* Outgoing Content-Type header */ + AST_STRING_FIELD(replaces_call_id); /* Replace info: call-id */ + AST_STRING_FIELD(replaces_to_tag); /* Replace info: to-tag */ + AST_STRING_FIELD(replaces_from_tag); /* Replace info: from-tag */ + AST_STRING_FIELD(replaces); /* Full replaces header */ + AST_STRING_FIELD(require); /* Outgoing Require header */ + AST_STRING_FIELD(uri_options); /* URI options to add to the URI */ + AST_STRING_FIELD(nonce); /* Sent authorization nonce */ + AST_STRING_FIELD(authorization); /* Outgoing Authorization header */ + AST_STRING_FIELD(authorization_uri); /* Authorization URI */ + AST_STRING_FIELD(authorization_username); /* Authorization username */ + AST_STRING_FIELD(authorization_realm); /* Authorization realm */ + AST_STRING_FIELD(authorization_domain); /* Authorization domain */ + AST_STRING_FIELD(authorization_nonce); /* Authorization nonce */ + AST_STRING_FIELD(authorization_opaque); /* Authorization opaque */ + AST_STRING_FIELD(authorization_qop); /* Authorization QoP */ + AST_STRING_FIELD(authorization_response); /* Authorization response */ + AST_STRING_FIELD(redirecting_from_name); /* Redirecting from name */ + AST_STRING_FIELD(redirecting_from_number); /* Redirecing from number */ + AST_STRING_FIELD(redirecting_to_name); /* Redirecting to name */ + AST_STRING_FIELD(redirecting_to_number); /*Redirecing to number */ + AST_STRING_FIELD(redirecting_reason); /* Redirecting reason */ + AST_STRING_FIELD(message_content); /* Text for a MESSAGE body */ + ); + ast_callid logger_callid; /* Identifier for call used in log messages */ + struct sip_socket socket; /* The socket used for this dialog */ + unsigned int already_gone:1; /* the peer has sent a message indicating termination of the dialog */ + unsigned int need_destroy:1; /* this dialog needs to be destroyed by the monitor thread */ + unsigned int destroy_scheduled:1; /* dialog destruction is scheduled. Keep dialog around until then + * to handle retransmits */ + unsigned int originated_call:1; /* We originated this call */ + unsigned int outgoing:1; /* Dialog direction is outgoing */ + unsigned int established:1; /* Has the dialog been established */ + unsigned int sent_ringing:1; /* Have we sent an 180 Ringing response */ + unsigned int sent_progress:1; /* Have we sent a 183 Progress response */ + unsigned int sent_authorization:1; /* Have we tried to authenticate */ + unsigned int transferring_call:1; /* Have we receieved a REFER */ + unsigned int answered_elsewhere:1; /* This call is cancelled due to answer on another channel */ + unsigned int no_video_support:1; /* Didn't get video in invite, don't offer */ + unsigned int no_text_support:1; /* Text not supported (?) */ + unsigned int sdp_changed:1; /* Session modification request true/false */ + unsigned int route_persistent:1; /* Is this the "real" route? */ + unsigned int secure_signaling:1;/* Whether we are required to have secure signaling or not */ + unsigned int secure_media:1; /* Whether we should offer only SRTP */ + unsigned int nat_detected:1; /* Whether we detected a NAT when processing the Via */ + unsigned int port_in_uri:1; /* Non zero if a port has been specified, will also disable srv lookups */ + unsigned int stale_nonce:1; /* Marks the current nonce as responded too */ + unsigned int ongoing_reinvite:1; /* There is a reinvite in progress that might need to be cleaned up */ + unsigned int need_reinvite:1; /* Do we need to send another reinvite */ + unsigned int pending_bye:1; /* Need to send bye after we ack? */ + unsigned int pending_connected_line:1; /* Pending connected line update */ + unsigned int defer_bye_on_transfer:1; /* Do not hangup at first ast_hangup */ + unsigned int force_state_change:1; /* Force a state change */ + unsigned int queued_state_change:1; /* There is a queued state change */ + unsigned int inuse:1; /* Did this dialog increment the counter of in-use calls */ + unsigned int ringing:1; /* Did this dialog increment the counter of ringing calls */ + unsigned int onhold:2; /* Dialog onhold state */ + unsigned int attended_transfer:1; /* Attended or blind transfer? */ + unsigned int local_transfer:1; /* Transfer to local domain? */ + unsigned int rport_present:1; /* Was rport present in Via header */ + unsigned int nat_force_rport:1; /* Force rport even if not present in the request */ + unsigned int nat_auto_rport:1; /* Set nat_force_rport when NAT is detected */ + unsigned int nat_rtp:1; /* Whether symmetric RTP is enabled or not */ + unsigned int nat_auto_rtp:1; /* Set rtp when NAT is detected */ + unsigned int direct_media:3; /* Allow peers to be reinvited to send media directly p2p */ + unsigned int transfer_response_error:1; /* Alternate transfer response for Cisco phones */ + unsigned int provisional_keepalive_sdp:1; /* Add SDP to provisional keepalive */ + unsigned int sdp_relay_nearend:1; /* Add relay-nearend to SDP */ + unsigned int sdp_relay_farend:1; /* Add relay-farend to SDP */ + unsigned int ack_add_sdp:1; /* Add SDP to ACK */ + unsigned int recording_active:1; /* Recording is active */ + unsigned int send_qrt_url:1; /* Send QRT URL on hangup */ + unsigned int add_headers:1; /* Add extra headers in SIPADDHEADER */ + int timer_t1; /* Timer T1, ms rtt */ + int timer_b; /* Timer B, ms */ + int invite_state; /* Invite state */ + int method; /* Method that opened this dialog */ + uint32_t outgoing_cseq; /* Current outgoing cseq */ + uint32_t incoming_cseq; /* Current incoming cseq */ + uint32_t initial_incoming_cseq; /* Initial incoming cseq from first request */ + uint32_t last_invite_cseq; /* Last cseq of invite */ + uint32_t pending_invite_cseq; /* Any pending INVITE or state NOTIFY (in subscribe dialog's) ? (cseq of this) */ + uint32_t glare_invite_cseq; /* A invite received while a pending invite is already present is stored here. Its + * cseq is the value. Since this glare invite's cseq is not the same as the pending + * INVITE's, it must be held in order to properly process acknowledgements for our + * 491 response */ + unsigned int allow_methods; /* The SIP methods supported by this peer. We get this information from the Allow + * header of the first message we receive from * an endpoint during a dialog */ + unsigned int supported_options; /* Supported SIP options on the other end */ + unsigned int require_options; /* Required SIP options on the other end */ + int nonce_count; /* Nonce count */ + int via_ttl; /* Via maddr TTL */ + int max_forwards; /* Max-Forwards */ + int hangupcause; /* hangupcause copied from our channel before we disconnect from the channel */ + int from_domain_port; /* Domain port to show in from field */ + int caller_presentation; /* Calling presentation */ + int expires; /* How long we take to expire */ + struct sip_message initial_request; /* Latest request that opened a new transactiom within this dialog. NOT the + * request that opened the dialog */ + int sdp_id; /* SDP Session ID */ + int sdp_version; /* SDP Session Version */ + int sdp_remote_version; /* Remote SDP Session Version */ + struct ast_sockaddr address; /* Our peer */ + struct ast_sockaddr our_address; /* Our IP (as seen from the outside) */ + struct ast_sockaddr audio_redirect_address; /* Where our RTP should be going if not to us */ + struct ast_sockaddr video_redirect_address; /* Where our Video RTP should be going if not to us */ + struct ast_sockaddr text_redirect_address; /* Where our Text RTP should be going if not to us */ + struct timeval last_rtp_received; /* Last RTP received */ + struct timeval last_rtp_sent; /* Last RTP sent */ + int rtp_timeout; /* RTP timeout time */ + int rtp_hold_timeout; /* RTP timeout time on hold*/ + int rtp_keepalive; /* RTP send packets for keepalive */ + struct ast_channel *channel; /* Who owns us (if we have an owner) */ + int invite_sched_id; /* Auto-congest if no response */ + int need_reinvite_sched_id; /* Wait ID for scheduler after 491 or other delays */ + int reinvite_sched_id; /* Reinvite in case of provisional, but no final response */ + int auto_destroy_sched_id; /* Auto-destroy if no response */ + int provisional_keepalive_sched_id; /* Provisional responses that need to be sent out to avoid cancellation */ + int fax_abort_sched_id; /* Abort fax negotiation timeout */ + int refer_state; /* Refer state */ + int subscribe_event; /* Dialog a subscription event type */ + int extension_state_id; /* ID for devicestate subscriptions */ + int last_exten_state; /* Last known extension state */ + int last_presence_state; /* Last known presence state */ + struct ao2_container *last_device_state_info; /* Last known extended extension state */ + struct timeval last_ringing_time; /* Channel timestamp of the channel which caused the last early-state + * notification */ + struct ast_format_cap *format_cap; /* Codec capability */ + struct ast_format_cap *joint_format_cap; /* Supported codec capability at both ends */ + struct ast_format_cap *remote_format_cap; /* Supported remote codec capability */ + struct ast_format_cap *redirect_format_cap; /* Redirect codecs */ + struct ast_format_cap *outgoing_format_cap; /* Preferred codec (outbound only) */ + int non_format_cap; /* DTMF RFC2833 telephony-event */ + int joint_non_format_cap; /* Joint Non codec capability */ + struct ast_rtp_instance *audio_rtp; /* RTP Session */ + struct ast_rtp_instance *video_rtp; /* Video RTP session */ + struct ast_rtp_instance *text_rtp; /* Text RTP session */ + struct ast_sdp_srtp *secure_audio_rtp; /* Structure to hold Secure RTP session data for audio */ + struct ast_sdp_srtp *secure_video_rtp; /* Structure to hold Secure RTP session data for video */ + struct ast_sdp_srtp *secure_text_rtp; /* Structure to hold Secure RTP session data for text */ + AST_LIST_HEAD_NOLOCK(, sip_sdp_media) sdp_media; /* List of media streams offered */ + AST_LIST_HEAD_NOLOCK(, sip_packet) packet_queue; /* Packets scheduled for re-transmission */ + struct ast_dsp *dsp; /* Inband DTMF or Fax CNG tone Detection dsp */ + struct sip_proxy *proxy; /* Outbound proxy for this dialog */ + struct sip_route route; /* List of routing steps (Record-Route) */ + struct ao2_container *authentication_realms; /* Realm authentication credentials */ + struct ast_variable *notify_headers; /* Custom notify headers */ + struct sip_peer *peer; /* Peer related to this dialog */ + struct sip_registration *registration; /* If this is a REGISTER dialog, to which registration */ + struct sip_mwi_subscription *mwi_subscription; /* If this is a subscription MWI dialog, to which subscription */ + struct ast_variable *channel_variables; /* Channel variables to set for inbound call */ + struct ast_variable *message_headers; /* Additional MESSAGE headers to send */ + int redirecting_code; /* Redirect code */ + struct sip_conference *conference; /* Ad-hoc n-way conference support */ + struct sip_dialog *record_outgoing_dialog; /* Pvt for the outbound recording leg */ + struct sip_dialog *record_incoming_dialog; /* Pvt for the inbound recording leg */ + int authorization_code; /* Authentication response code */ + int session_timer_active:1; /* Session-Timers on/off */ + int session_timer_remote_active:1; /* Remote Session-Timers on/off */ + int session_timer_refresher; /* Session-Timers refresher */ + int session_timer_expires; /* Session-Timers negotiated session refresh interval */ + int session_timer_sched_id; /* Session-Timers ast_sched scheduler id */ + struct ast_udptl *udptl; /* T.38 UDPTL session */ + unsigned int fax_red:1; /* T.140 RTP Redundancy */ + int fax_state; /* T.38 state */ + struct ast_control_t38_parameters fax_config; /* Local t38 state */ + struct ast_control_t38_parameters fax_remote_config; /* Remote t38 state */ +}; + +extern struct ao2_container *sip_dialogs; +extern struct ao2_container *sip_dialogs_need_destroy; +extern struct ao2_container *sip_dialogs_with_rtp; + +int sip_dialog_hash(const void *data, int flags); +int sip_dialog_cmp(void *data, void *arg, int flags); + +struct sip_dialog *sip_dialog_alloc(const char *call_id, struct sip_socket *socket, int method, + struct sip_message *message, ast_callid logger_callid); +int sip_dialog_alloc_channel(struct sip_dialog *dialog, int state, const char *name, + const struct ast_assigned_ids *assigned_ids, const struct ast_channel *requestor_channel, ast_callid callid); + +void sip_dialog_unlink(struct sip_dialog *dialog); + +struct sip_dialog *sip_dialog_find(const char *call_id, const char *to_tag, const char *from_tag); +struct sip_dialog *sip_dialog_find_with_channel(const char *call_id, const char *to_tag, const char *from_tag, + struct ast_channel **channel); +struct ast_channel *sip_dialog_lock_with_channel(struct sip_dialog *dialog); + +int sip_dialog_sched_destroy(struct sip_dialog *dialog, int when); +void sip_dialog_cancel_destroy(struct sip_dialog *dialog); + +int sip_dialog_check_need_destroy(void *data, void *arg, int flags); +int sip_dialog_check_rtp_timeout(void *data, void *arg, int flags); + +int sip_dialog_auto_destroy(const void *data); +int sip_dialog_auto_congest(const void *data); +void sip_dialog_check_pending(struct sip_dialog *dialog); +void sip_dialog_sched_check_pending(struct sip_dialog *dialog); + +int sip_dialog_debug(struct sip_dialog *dialog); +void sip_dialog_copy(struct sip_dialog *to_dialog, struct sip_dialog *from_dialog); + +void sip_dialog_set_need_destroy(struct sip_dialog *dialog, const char *reason); +void sip_dialog_set_already_gone(struct sip_dialog *dialog); +void sip_dialog_set_channel(struct sip_dialog *dialog, struct ast_channel *channel); +void sip_dialog_set_our_address(struct sip_dialog *dialog); +void sip_dialog_set_realm(struct sip_dialog *dialog, const struct sip_message *message); +void sip_dialog_set_dsp_detect(struct sip_dialog *dialog, int enabled); +void sip_dialog_set_contact(struct sip_dialog *dialog, struct sip_message *message); + +int sip_dialog_get_destination(struct sip_dialog *dialog, struct sip_message *message); + +void sip_dialog_check_nat(struct sip_dialog *dialog, const struct ast_sockaddr *address); +void sip_dialog_set_rtp_nat(struct sip_dialog *dialog); + +int sip_dialog_build(struct sip_dialog *dialog, const char *name, struct ast_sockaddr *address, int new_dialog); +int sip_dialog_build_from_peer(struct sip_dialog *dialog, struct sip_peer *peer); +void sip_dialog_build_contact(struct sip_dialog *dialog, struct sip_message *message); +void sip_dialog_build_route(struct sip_dialog *dialog, struct sip_message *message, int backwards); +void sip_dialog_build_local_tag(struct sip_dialog *dialog); +void sip_dialog_build_nonce(struct sip_dialog *dialog, int force); + +void sip_dialog_check_via(struct sip_dialog *dialog, struct sip_message *message); +void sip_dialog_stop_rtp(struct sip_dialog *dialog); + +void sip_dialog_start_reinvite(struct sip_dialog *dialog); +void sip_dialog_stop_reinvite(struct sip_dialog *dialog); +int sip_dialog_reinvite_timeout(const void *data); + +int sip_dialog_need_reinvite(const void *data); +void sip_dialog_start_need_reinvite(struct sip_dialog *dialog); +void sip_dialog_stop_need_reinvite(struct sip_dialog *dialog); + +int sip_dialog_parse_authorization(struct sip_dialog *dialog, struct sip_message *response); +int sip_dialog_build_authorization(struct sip_dialog *dialog, int method); + +void sip_dialog_queue_connected_line(struct sip_dialog *dialog, int source); +void sip_dialog_update_connected_line(struct sip_dialog *dialog); + +void sip_dialog_queue_redirecting(struct sip_dialog *dialog); +void sip_dialog_update_redirecting(struct sip_dialog *dialog); + +void sip_dialog_sched_provisional_keepalive(struct sip_dialog *dialog, int with_sdp); +void sip_dialog_cancel_provisional_keepalive(struct sip_dialog *dialog); + +int sip_dialog_change_inuse(struct sip_dialog *dialog, int event); +void sip_dialog_change_onhold(struct sip_dialog *dialog, int onhold); + +int sip_dialog_handle_authorization(struct sip_dialog *dialog, struct sip_message *message, int reliable); +int sip_dialog_handle_authentication(struct sip_dialog *dialog, struct sip_message *response); +int sip_dialog_handle_registration(struct sip_dialog *dialog, struct sip_message *message); + +void sip_extension_state_destroy(int state_id, void *data); +int sip_extension_state_event(const char *context, const char *exten, struct ast_state_cb_info *state_info, void *data); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/dialplan_applications.h asterisk-22.6.0/channels/sip/include/dialplan_applications.h --- asterisk-22.6.0.orig/channels/sip/include/dialplan_applications.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/dialplan_applications.h 2025-10-21 18:12:24.472604511 +1300 @@ -0,0 +1,31 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_DIALPLAN_APPLICATIONS_H +#define _SIP_DIALPLAN_APPLICATIONS_H + +int sip_application_add_header(struct ast_channel *channel, const char *data); +int sip_application_remove_header(struct ast_channel *channel, const char *data); +int sip_application_cisco_page(struct ast_channel *channel, const char *data); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/dialplan_functions.h asterisk-22.6.0/channels/sip/include/dialplan_functions.h --- asterisk-22.6.0.orig/channels/sip/include/dialplan_functions.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/dialplan_functions.h 2025-10-21 18:12:24.473604484 +1300 @@ -0,0 +1,45 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_DIALPLAN_FUNCTIONS_H +#define _SIP_DIALPLAN_FUNCTIONS_H + +extern struct ast_custom_function sip_function_peer; +extern struct ast_custom_function sip_function_peer_deprecated; +extern struct ast_custom_function sip_function_headers; +extern struct ast_custom_function sip_function_header; +extern struct ast_custom_function sip_function_check_domain; +extern struct ast_custom_function sip_function_check_domain_deprecated; + +int sip_function_channel_read(struct ast_channel *channel, const char *function, char *data, char *buf, size_t buf_len); +int sip_function_headers_read(struct ast_channel *channel, const char *function, char *data, struct ast_str **buf, + ssize_t max_len); +int sip_function_header_read(struct ast_channel *channel, const char *function, char *data, struct ast_str **buf, + ssize_t max_len); +int sip_function_peer_read(struct ast_channel *channel, const char *function, char *data, struct ast_str **buf, + ssize_t max_len); +int sip_function_peer_write(struct ast_channel *channel, const char *function, char *data, const char *value); +int sip_function_check_domain_read(struct ast_channel *channel, const char *function, char *data, struct ast_str **buf, + ssize_t max_len); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/domains.h asterisk-22.6.0/channels/sip/include/domains.h --- asterisk-22.6.0.orig/channels/sip/include/domains.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/domains.h 2025-10-21 18:12:24.473604484 +1300 @@ -0,0 +1,40 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_DOMAIN_H +#define _SIP_DOMAIN_H + +struct sip_domain { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(name); /* SIP domain name we are responsible for */ + AST_STRING_FIELD(context); /* Incoming context for this domain */ + ); +}; + +extern struct ao2_container *sip_domains; /* The SIP domain list */ + +int sip_domain_cmp(void *data, void *arg, int flags); +int sip_domain_build(const char *name, int lineno); +int sip_domain_check(const char *name, char *context, size_t context_len); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/events.h asterisk-22.6.0/channels/sip/include/events.h --- asterisk-22.6.0.orig/channels/sip/include/events.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/events.h 2025-10-21 18:12:24.473604484 +1300 @@ -0,0 +1,35 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_EVENTS_H +#define _SIP_EVENTS_H + +void sip_startup_event(void *data, struct stasis_subscription *subscription, struct stasis_message *message); + +void sip_network_change_subscribe(void); +void sip_network_change_unsubscribe(void); + +void sip_acl_change_subscribe(void); +void sip_acl_change_unsubscribe(void); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/fax.h asterisk-22.6.0/channels/sip/include/fax.h --- asterisk-22.6.0.orig/channels/sip/include/fax.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/fax.h 2025-10-21 18:12:24.474604457 +1300 @@ -0,0 +1,44 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_FAX_H +#define _SIP_FAX_H + +/* T38 States for a call */ +enum sip_fax_state { + SIP_FAX_DISABLED = 0, /* Not enabled */ + SIP_FAX_LOCAL_REINVITE, /* Offered from local - REINVITE */ + SIP_FAX_REMOTE_REINVITE, /* Offered from peer - REINVITE */ + SIP_FAX_ENABLED, /* Negotiated (enabled) */ + SIP_FAX_REJECTED, /* Refused */ +}; + +/* Forward declarations */ +struct sip_dialog; + +int sip_fax_alloc(struct sip_dialog *dialog); +int sip_fax_update(struct sip_dialog *dialog, const struct ast_control_t38_parameters *parameters); +void sip_fax_set_state(struct sip_dialog *dialog, int state); +void sip_fax_start_abort(struct sip_dialog *dialog); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/handlers.h asterisk-22.6.0/channels/sip/include/handlers.h --- asterisk-22.6.0.orig/channels/sip/include/handlers.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/handlers.h 2025-10-21 18:12:24.474604457 +1300 @@ -0,0 +1,29 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_HANDLERS_H +#define _SIP_HANDLERS_H + +int sip_handle_incoming(struct sip_socket *socket, char *data); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/manager.h asterisk-22.6.0/channels/sip/include/manager.h --- asterisk-22.6.0.orig/channels/sip/include/manager.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/manager.h 2025-10-21 18:12:24.474604457 +1300 @@ -0,0 +1,39 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_MANAGER_H +#define _SIP_MANAGER_H + +int sip_manager_peers(struct mansession *session, const struct message *message); +int sip_manager_registrations(struct mansession *session, const struct message *message); +int sip_manager_mwi_subscriptions(struct mansession *session, const struct message *message); +int sip_manager_show_peer(struct mansession *session, const struct message *message); +int sip_manager_qualify_peer(struct mansession *session, const struct message *message); +int sip_manager_peer_status(struct mansession *session, const struct message *message); +int sip_manager_notify(struct mansession *session, const struct message *message); + +struct ast_manager_event_blob *sip_session_timeout_to_ami(struct stasis_message *message); +void sip_publish_qualify_peer(const char *peer, const char *action_id); +void sip_publish_session_timeout(struct ast_channel *channelnel, const char *source); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/message.h asterisk-22.6.0/channels/sip/include/message.h --- asterisk-22.6.0.orig/channels/sip/include/message.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/message.h 2025-10-21 18:12:24.474604457 +1300 @@ -0,0 +1,118 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_MESSAGE_H +#define _SIP_MESSAGE_H + +/* Methods we support */ +#define SIP_ALLOW_METHODS "ACK,BYE,CANCEL,INFO,INVITE,MESSAGE,NOTIFY,OPTIONS,PUBLISH,REFER,REGISTER,SUBSCRIBE,UPDATE" + +/* Whether to initialise a new request/branch in sip_message_build */ +enum sip_message_init_type { + SIP_INIT_NONE = 0, + SIP_INIT_BRANCH, + SIP_INIT_REQUEST, +}; + +/* When sending a message, we can send with a few options, depending of type of SIP request. UNRELIABLE is mostly used + * for responses to repeated requests, where the original response would be sent RELIABLE in an INVITE transaction */ +enum sip_message_send_type { + SIP_SEND_UNRELIABLE = 0, /* Send SIP message without bothering with re-transmits */ + SIP_SEND_RELIABLE, /* Send SIP message reliably, with re-transmits */ + SIP_SEND_CRITICAL, /* Send critical SIP message reliably, with re-transmit. If it fails, it's critical and will + * cause a teardown of the dialog */ +}; + +/* Forward declarations */ +struct sip_route; +struct sip_dialog; + +struct sip_message { + const char *uri; /* Request URI */ + const char *status_line; /* Response status line */ + struct { + const char *name; /* Header name */ + const char *value; /* Header value */ + } headers[64]; + const char *content[256]; /* Content lines */ + int header_count; /* Number of headers */ + int content_count; /* Number of content lines */ + int method; /* Method for both request or response */ + const char *call_id; /* Call-ID */ + const char *from_tag; /* From-tag */ + const char *to_tag; /* To-tag */ + uint32_t cseq; /* CSeq */ + int code; /* Response code */ + unsigned int response:1; /* Non-zero if this is a response */ + unsigned int debug:1; /* Print extra debugging if non zero */ + unsigned int ignore:1; /* Non-zero This is a re-transmit, ignore it */ + unsigned int authenticated:1; /* non-zero if this request was authenticated */ + unsigned int sdp_start; /* The line number where the SDP begins */ + unsigned int sdp_end; /* The line number where the SDP ends */ + char *via_sent_by; /* First Via address */ + char *via_branch; /* Via branch= parameter */ + char *via_maddr; /* Via maddr= parameter */ + int via_ttl; /* Via maddr ttl */ + int via_rport:1; /* Via has rport query */ +}; + +int sip_message_parse(struct sip_message *message, char *data); +void sip_message_destroy(struct sip_message *message); +void sip_message_copy(struct sip_message *to_message, const struct sip_message *from_message); + +void sip_message_build_initial_request(struct sip_message *request, struct sip_dialog *dialog, int method, + const char *explicit_uri); +int sip_message_build_request(struct sip_message *request, struct sip_dialog *dialog, int method, uint32_t cseq, + int new_branch); +int sip_message_build_response(struct sip_message *response, struct sip_dialog *dialog, const char *status_line, + struct sip_message *request); + +int sip_message_send(struct sip_dialog *dialog, struct sip_message *message, int reliable, uint32_t cseq); + +const char *sip_message_find_header(const struct sip_message *message, const char *name); +const char *sip_message_next_header(const struct sip_message *message, const char *name, int *iter); +void sip_message_add_header(struct sip_message *message, const char *name, const char *value); +void sip_message_build_header(struct sip_message *message, const char *name, const char *format, ...); +void sip_message_copy_header(struct sip_message *to_message, const struct sip_message *from_message, const char *name); + +char *sip_message_get_content(struct sip_message *message, int start, int end); +void sip_message_add_content(struct sip_message *message, const char *content); +void sip_message_build_content(struct sip_message *message, const char *format, ...); + +int sip_message_find_boundary(struct sip_message *message, const char *boundary, int start, int *done); +const char *sip_message_find_content_type(struct sip_message *message); + +void sip_message_add_via(struct sip_message *message, struct sip_dialog *dialog); +void sip_message_add_supported(struct sip_message *message, struct sip_dialog *dialog); +void sip_message_add_require(struct sip_message *message); +void sip_message_add_date(struct sip_message *message); +void sip_message_add_authorization(struct sip_message *message, struct sip_dialog *dialog); +void sip_message_add_identity(struct sip_message *message, struct sip_dialog *dialog); +void sip_message_add_call_info(struct sip_message *message, struct sip_dialog *dialog); +void sip_message_add_route(struct sip_message *message, struct sip_route *route, int skip); +void sip_message_add_diversion(struct sip_message *message, struct sip_dialog *dialog); +void sip_message_add_join(struct sip_message *message, struct sip_dialog *dialog); + +struct sip_dialog *sip_message_find_dialog(struct sip_message *message, struct sip_socket *socket); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/monitor.h asterisk-22.6.0/channels/sip/include/monitor.h --- asterisk-22.6.0.orig/channels/sip/include/monitor.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/monitor.h 2025-10-21 18:12:24.475604431 +1300 @@ -0,0 +1,49 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_MONITOR_H +#define _SIP_MONITOR_H + +/* Forward declarations */ +struct sip_dialog; +struct sip_registration; +struct sip_mwi_subscription; + +/* Generic data for ast_sched_add callbacks */ +struct sip_sched_data { + union { + struct sip_dialog *dialog; + struct sip_registration *registration; + struct sip_mwi_subscription *mwi_subscription; + }; + int when; +}; + +extern ast_mutex_t sip_monitor_lock; +extern pthread_t sip_monitor_threadid; +extern struct ast_sched_context *sip_sched_context; + +void *sip_monitor_thread(void *data); +int sip_monitor_restart(void); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/mwi_subscriptions.h asterisk-22.6.0/channels/sip/include/mwi_subscriptions.h --- asterisk-22.6.0.orig/channels/sip/include/mwi_subscriptions.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/mwi_subscriptions.h 2025-10-21 18:12:24.475604431 +1300 @@ -0,0 +1,63 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_MWI_SUBSCRIPTIONS_H +#define _SIP_MWI_SUBSCRIPTIONS_H + +/* Forward declarations */ +struct sip_dialog; + +struct sip_mwi_subscription { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(config); /* mwi string from config */ + AST_STRING_FIELD(user); /* Who we are sending the subscription as */ + AST_STRING_FIELD(host); /* Domain or host we subscribe to */ + AST_STRING_FIELD(authorization_user); /* Who we *authenticate* as */ + AST_STRING_FIELD(secret); /* Password in clear text */ + AST_STRING_FIELD(md5_secret); /* Password MD5 hash */ + AST_STRING_FIELD(mailbox); /* Mailbox store to put MWI into */ + AST_STRING_FIELD(context); /* Mailbox context to put MWI into */ + ); + enum ast_transport transport; /* Transport to use */ + int port; /* Optional port override */ + int expires_sched_id; /* Sched ID of resubscription */ + unsigned int subscribed:1; /* Whether we are currently subscribed or not */ + struct sip_dialog *dialog; /* Outbound subscription dialog */ + struct ast_dnsmgr_entry *dnsmgr; /* DNS refresh manager for subscription */ + struct ast_sockaddr address; /* Who the server thinks we are */ + int new_messages; /* New messages */ + int old_messages; /* Old messages */ +}; + +/* The MWI subscription list */ +extern struct ao2_container *sip_mwi_subscriptions; + +int sip_mwi_subscription_cmp(void *data, void *arg, int flags); +int sip_mwi_subscription_build(const char *config, int lineno); +int sip_mwi_subscription_unlink(void *data, void *arg, int flags); + +void sip_mwi_subscription_start(struct sip_mwi_subscription *mwi_subscription, int when); +void sip_mwi_subscription_send_all(void); +int sip_mwi_subscription_resend(const void *data); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/netsock.h asterisk-22.6.0/channels/sip/include/netsock.h --- asterisk-22.6.0.orig/channels/sip/include/netsock.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/netsock.h 2025-10-21 18:12:24.475604431 +1300 @@ -0,0 +1,100 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_NETSOCK_H +#define _SIP_NETSOCK_H + +/* Forward declarations */ +struct sip_message; +struct sip_dialog; + +/* The SIP socket definition */ +struct sip_socket { + enum ast_transport transport; /* UDP, TCP or TLS */ + int fd; /* File descriptor, the actual socket */ + struct ast_tcptls_session_instance *tcptls_session; /* Socket manager if using TCP or TLS */ + struct ast_sockaddr address; /* Received address */ +}; + +/* sip packet - raw format for outbound packets that are sent or scheduled for transmission Packets are linked in a list, whose head is in + * the struct sip_dialog they belong to. Each packet holds a reference to the parent struct sip_dialog. This structure is allocated in + * sip_packet_send() and only for packets that require retransmissions */ +struct sip_packet { + AST_LIST_ENTRY(sip_packet) next; /* Next packet in linked list */ + int method; /* SIP method for this packet */ + uint32_t cseq; /* Sequence number */ + int code; /* If this is a response, the response code */ + unsigned int response:1; /* If this is a response packet (e.g. 200 OK) */ + unsigned int critical:1; /* If this is a critical packet that must be sent */ + unsigned int timeout:1; /* Timeout is reached, stop retransmission */ + struct sip_dialog *dialog; /* Owner dialog */ + int resend_sched_id; /* Retransmission sched ID */ + int timer_a; /* SIP timer A, retransmission timer */ + int timer_t1; /* SIP timer T1, estimated RTT or 500 ms */ + struct timeval send_start; /* When pkt was sent */ + int64_t send_end; /* Time in ms after 'now' that retransmission must stop */ + int attempts; /* Resend attempt count */ + struct ast_str *data; /* Packet data */ +}; + +/* Definition of a thread that handles a socket */ +struct sip_tcptls_thread { + int alert_pipe[2]; /* Used to alert tcptls thread when packet is ready to be written */ + pthread_t threadid; + struct ast_tcptls_session_instance *tcptls_session; + AST_LIST_HEAD_NOLOCK(, sip_tcptls_packet) packet_queue; +}; + +struct sip_tcptls_packet { + AST_LIST_ENTRY(sip_tcptls_packet) next; + struct ast_str *data; +}; + +extern ast_mutex_t sip_netsock_lock; +extern struct io_context *sip_io_context; +extern int *sip_socket_io_id; +extern int sip_socket_fd; + +extern struct ast_tcptls_session_args sip_tcp_session; +extern struct ast_tcptls_session_args sip_tls_session; + +extern struct ao2_container *sip_tcptls_threads; + +int sip_socket_recv(int *id, int fd, short events, void *ignore); +void sip_socket_set_transport(struct sip_socket *socket, int transport); +void sip_socket_copy(struct sip_socket *to_sock, const struct sip_socket *from_sock); + +int sip_tcptls_thread_hash(const void *obj, const int flags); +int sip_tcptls_thread_cmp(void *obj, void *arg, int flags); +int sip_tcptls_session_write(struct ast_tcptls_session_instance *tcptls_session, const char *data); + +int sip_packet_send(struct sip_dialog *dialog, struct ast_str *data); +int sip_packet_send_reliable(struct sip_dialog *dialog, int method, uint32_t cseq, int code, struct ast_str *data, int critical); +int sip_packet_resend(const void *data); +void sip_dialog_cancel_resend(struct sip_packet *packet); + +int sip_packet_ack(struct sip_dialog *dialog, int method, uint32_t cseq, int response); +int sip_packet_semi_ack(struct sip_dialog *dialog, int method, uint32_t cseq, int response); +void sip_packet_pretend_ack(struct sip_dialog *dialog); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/notifications.h asterisk-22.6.0/channels/sip/include/notifications.h --- asterisk-22.6.0.orig/channels/sip/include/notifications.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/notifications.h 2025-10-21 18:12:24.475604431 +1300 @@ -0,0 +1,42 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_NOTIFICATONS_H +#define _SIP_NOTIFICATONS_H + +/* Forward declarations */ +struct sip_peer; + +struct sip_notification { + char *type; + struct ast_variable *headers; + struct ast_str *content; +}; + +extern struct ao2_container *sip_notifications; + +int sip_notification_cmp(void *data, void *arg, int flags); +int sip_notification_build(const char *type, struct ast_variable *variables, int deprecated); +int sip_notification_send(struct sip_notification *notification, struct sip_peer *peer); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/parking.h asterisk-22.6.0/channels/sip/include/parking.h --- asterisk-22.6.0.orig/channels/sip/include/parking.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/parking.h 2025-10-21 18:12:24.476604404 +1300 @@ -0,0 +1,34 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_PARKING_H +#define _SIP_PARKING_H + +/* Forward declarations */ +struct sip_dialog; +struct sip_message; + +int sip_park_call(struct sip_dialog *dialog, struct sip_message *request, const char *call_id, const char *local_tag, + const char *remote_tag, int monitor); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/peers.h asterisk-22.6.0/channels/sip/include/peers.h --- asterisk-22.6.0.orig/channels/sip/include/peers.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/peers.h 2025-10-21 18:12:24.476604404 +1300 @@ -0,0 +1,299 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_PEERS_H +#define _SIP_PEERS_H + +#define SIP_QUALIFY_UNREACHABLE 15 /* How often in seconds to check, if the host is down */ + +enum sip_peer_status { + SIP_PEER_UNKNOWN = -1, + SIP_PEER_UNREACHABLE = 0, + SIP_PEER_REACHABLE = 1, +}; + +enum sip_dtmf_mode { + SIP_DTMF_MODE_INBAND = 0, /* Inband audio, only for ULAW/ALAW */ + SIP_DTMF_MODE_RFC2833, /* RTP DTMF */ +}; + +enum sip_allow_overlap_mode { + SIP_ALLOW_OVERLAP_NONE = 0, + SIP_ALLOW_OVERLAP_INVITE, + SIP_ALLOW_OVERLAP_DTMF, +}; + +enum sip_direct_media_mode { + SIP_DIRECT_MEDIA_NO_NAT = 1 << 0, + SIP_DIRECT_MEDIA_NAT = 1 << 1, + SIP_DIRECT_MEDIA_OUTGOING = 1 << 2, + SIP_DIRECT_MEDIA_UPDATE = 1 << 3, +}; + +enum sip_identity_support { + SIP_IDENTITY_NONE = 0, + SIP_IDENTITY_REMOTE_PARTY = 1, + SIP_IDENTITY_P_ASSERTED = 2, +}; + +enum sip_pickup_notify { + SIP_PICKUP_NOTIFY_FROM = 1 << 0, /* Include the from number in the status line for pickup notify */ + SIP_PICKUP_NOTIFY_TO = 1 << 1, /* Include the to number in the status line for pickup notify */ + SIP_PICKUP_NOTIFY_BEEP = 1 << 2, /* Play a beep for pickup notify */ +}; + +enum sip_fax_detect_mode { + SIP_FAX_DETECT_CNG = 1 << 0, /* Detect CNG in audio */ + SIP_FAX_DETECT_T38 = 1 << 1, /* Detect Reinvite from peer */ +}; + +/* Forward declarations */ +struct sip_dialog; +struct sip_selected; +struct sip_callback; + +/* Structure for SIP peer */ +struct sip_peer { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(name); /* the unique name of this object */ + AST_STRING_FIELD(secret); /* Password for inbound auth */ + AST_STRING_FIELD(md5_secret); /* Password in MD5 */ + AST_STRING_FIELD(remote_secret); /* Remote secret (trunks, remote devices) */ + AST_STRING_FIELD(authorization_user); /* Username to use for authorization */ + AST_STRING_FIELD(description); /* Description of this peer */ + AST_STRING_FIELD(context); /* Default context for incoming calls */ + AST_STRING_FIELD(message_context); /* Default context for outofcall messages */ + AST_STRING_FIELD(subscribe_context); /* Default context for subscriptions */ + AST_STRING_FIELD(accountcode); /* Account code */ + AST_STRING_FIELD(host); /* If not dynamic, IP address */ + AST_STRING_FIELD(from_user); /* From: user when calling this peer */ + AST_STRING_FIELD(from_domain); /* From: domain when calling this peer */ + AST_STRING_FIELD(contact); /* Contact registered with us (not in sip.conf) */ + AST_STRING_FIELD(caller_number); /* Caller ID num */ + AST_STRING_FIELD(caller_name); /* Caller ID name */ + AST_STRING_FIELD(caller_tag); /* Caller ID tag */ + AST_STRING_FIELD(mwi_exten); /* Dialplan extension for MWI notify message*/ + AST_STRING_FIELD(language); /* Default language for prompts */ + AST_STRING_FIELD(moh_interpret); /* Music on Hold class */ + AST_STRING_FIELD(moh_suggest); /* Music on Hold class */ + AST_STRING_FIELD(parkinglot); /* Parkinglot */ + AST_STRING_FIELD(useragent); /* User agent in SIP request (saved from registration) */ + AST_STRING_FIELD(unsolicited_mailbox); /* Mailbox unsolicited MWI NOTIFY */ + AST_STRING_FIELD(tone_zone); /* Tonezone for this device */ + AST_STRING_FIELD(call_forward); /* Call forwarding extension */ + AST_STRING_FIELD(register_call_id); /* Call-ID of the REGISTER dialog */ + AST_STRING_FIELD(soft_key); /* Name of the last soft-key sent via remotecc */ + AST_STRING_FIELD(device_name); /* Name of the device */ + AST_STRING_FIELD(active_load); /* Name of the active firmware load */ + AST_STRING_FIELD(inactive_load); /* Name of the inactive firmware load */ + AST_STRING_FIELD(qrt_url); /* QRT URL */ + AST_STRING_FIELD(rtp_rx_stat); /* RTP Rx-stat */ + AST_STRING_FIELD(rtp_tx_stat); /* RTP Tx-stat */ + ); + struct sip_socket socket; /* Socket used for this peer */ + enum ast_transport transports; /* Transports (enum ast_transport) that are acceptable for this peer */ + enum ast_transport default_transport; /* Peer Registration may change the default outbound transport If + * register expires, default should be reset. to this value */ + unsigned int host_dynamic:1; /* Dynamic Peers register with Asterisk */ + unsigned int removed:1; /* Peer has been removed from config */ + unsigned int auto_framing:1; /* Whether to use our local configuration for frame sizes (off) or respect the + * other endpoint's request for frame sizes (on) for incoming calls */ + struct ao2_container *authentication_realms; /* Realm authentication credentials */ + int amaflags; /* AMA Flags (for billing) */ + int caller_presentation; /* Calling id presentation */ + int inuse; /* Number of calls in use */ + int ringing; /* Number of calls ringing */ + int onhold; /* Peer has someone on hold */ + int offhook; /* Peer has signalled that they are off-hook */ + int max_calls; /* Maximum number of concurrent calls */ + int busy_level; /* Level of active calls where we signal busy */ + unsigned int allow_transfer:1; /* Allow transfers from this peer */ + unsigned int allow_subscribe:1; /* Allow subscriptions from this peer */ + unsigned int allow_overlap:2; /* Overlap dialing support */ + unsigned int dtmf_mode:2; /* DTMF mode */ + unsigned int relax_dtmf:1; /* Relax DTMF */ + unsigned int secure_media:1; /* Whether we should offer only SRTP */ + unsigned int video_support:1; /* Allow video */ + unsigned int text_support:1; /* Allow text */ + unsigned int ice_support:1; /* ICE support */ + unsigned int subscribe_mwi_only:1; /* Only issue MWI notification if subscribed to */ + unsigned int preferred_codec_only:1; /* Only respond with single most preferred joint codec */ + unsigned int ignore_outgoing_format:1; /* Ignore outgoing requested format */ + unsigned int identity_support:2; /* Remote-Party-ID or P-Asserted-Identity support */ + unsigned int allow_identity:1; /* Allow incoming identity headers */ + unsigned int trust_identity_outgoing:1; /* Add identity when presentation is prohibited */ + unsigned int path_support:1; /* Trust and use incoming Path headers */ + unsigned int reason_support:1; /* Get/send cause code via Reason header */ + unsigned int rtcp_mux:1; /* Attempt to negotiate RFC 5761 RTCP multiplexing */ + unsigned int user_eq_phone:1; /* Add user=phone to numeric URI */ + unsigned int diversion_support:1; /* Send diversion header */ + unsigned int nat_force_rport:1; /* Force rport even if not present in the request */ + unsigned int nat_auto_rport:1; /* Set nat_force_rport when NAT is detected */ + unsigned int nat_rtp:1; /* Whether symmetric RTP is enabled or not */ + unsigned int nat_auto_rtp:1; /* Set rtp when NAT is detected */ + unsigned int direct_media:3; /* Allow peers to be reinvited to send media directly p2p */ + unsigned int busy_when_dnd:1; /* Treat endpoint as busy when DND is enabled */ + unsigned int hunt_group_default:1; /* Default state of hunt-group login */ + unsigned int cisco_mode:1; /* Enable Cisco USECALLMANAGER features */ + unsigned int keep_conference:1; /* Keep ad-hoc conference after initiator hangs up */ + unsigned int multi_admin_conference:1; /* Promote other Cisco phones to conference admins */ + unsigned int do_not_disturb:1; /* Peer has set DoNotDisturb */ + unsigned int hunt_group:1; /* Peer is logged into the HuntGroup */ + unsigned int fax_detect:2; /* Fax detection support */ + unsigned int fax_support:1; /* Fax (T.38) Support */ + unsigned int fax_max_datagram; /* T.38 FaxMaxDatagram override */ + unsigned int udptl_error_correction:2; /* t38 error correction mode */ + unsigned int udptl_nat:1; /* Use source IP of RTP as destination if NAT is enabled */ + int max_forwards; /* SIP Loop prevention */ + int new_messages; /* Heard voicemail messages */ + int old_messages; /* Unheard voicemail messages */ + unsigned int allow_methods; /* Allowed methods */ + unsigned int supported_options; /* Supported options */ + AST_LIST_HEAD_NOLOCK(, sip_mailbox) mailboxes; /* Mailboxes that this peer cares about */ + AST_LIST_HEAD_NOLOCK(, sip_subscription) subscriptions; /* Extension state subscriptions */ + AST_LIST_HEAD_NOLOCK(, sip_alias) aliases; /* Register peer aliases */ + AST_LIST_HEAD_NOLOCK(, sip_selected) selected; /* Dialogs selected for joining a conference */ + int max_call_bitrate; /* Maximum Bitrate for a video call */ + int register_expires_sched_id; /* When to expire this peer registration */ + struct ast_format_cap *format_cap; /* Codec capability */ + int rtp_timeout; /* RTP timeout */ + int rtp_hold_timeout; /* RTP Hold Timeout */ + int rtp_keepalive; /* Send RTP packets for keepalive */ + ast_group_t callgroup; /* Call group */ + ast_group_t pickupgroup; /* Pickup group */ + struct ast_namedgroups *named_callgroups; /* Named call group */ + struct ast_namedgroups *named_pickupgroups; /* Named pickup group */ + struct sip_proxy *proxy;/* Outbound proxy for this peer */ + struct ast_dnsmgr_entry *dnsmgr;/* DNS refresh manager for peer */ + struct ast_sockaddr address; /* IP address of peer */ + unsigned int port_in_uri:1; /* Whether the port should be included in the URI */ + struct sip_dialog *qualify_dialog; /* Pointer to our qualify dialog */ + int qualify_sched_id; /* Qualification: When to expire qualify (qualify= checking) */ + int qualify; /* Qualification: How long last response took (in ms), or -1 for no response */ + int qualify_max; /* Qualification: Max ms we will accept for the host to be up, 0 to not monitor */ + int qualify_expires; /* Qualification: How often to check for the host to be up */ + struct timeval qualify_sent; /* Qualification: Time for sending SIP OPTION in sip_peer_qualify() */ + struct ast_acl_list *address_acl; /* Access control list */ + struct ast_acl_list *contact_acl; /* Restrict what IPs are allowed in the Contact header (for registration) */ + struct ast_acl_list *direct_media_acl; /* Restrict what IPs are allowed to interchange direct media with */ + struct ast_variable *channel_variables; /* Variables to set for channel created by user */ + struct sip_dialog *mwi_dialog; /* Subscription for MWI */ + struct sip_dialog *feature_events_dialog; /* Subscription for Feature Events */ + struct sip_callback *callback; /* Extension State watcher for Call Back requests */ + int session_timer_mode; /* Mode of operation for Session-Timers */ + int session_timer_refresher; /* Session-Timer refresher */ + int session_timer_max_expires; /* Highest threshold for session refresh interval */ + int session_timer_min_expires; /* Lowest threshold for session refresh interval */ + int timer_t1; /* The maximum T1 value for the peer */ + int timer_b; /* The maximum timer B (transaction timeouts) */ + int from_domain_port; /* The From: domain port */ + struct sip_route path; /* List of out-of-dialog outgoing routing steps (from Path headers) */ + int line_index; /* Line index number */ + int pickup_notify:3; /* Pickup notify options (To, From and Beep) */ + int pickup_notify_timer; /* Toast timer for pickup notify */ + int park_notify_timer; /* Toast timer for park notify */ + struct timeval pickup_notify_sent; /* Last time a pickup notify was sent */ + unsigned int realtime:1; /* This is a 'realtime' peer */ + unsigned int realtime_cache_peer:1; /* This is a cached realtime peer */ + unsigned int realtime_contact:1; /* Contact was from realtime */ + struct ast_endpoint *endpoint; +}; + +/* A peer's mailbox */ +struct sip_mailbox { + AST_LIST_ENTRY(sip_mailbox) next; + struct ast_mwi_subscriber *mwi_subscription; + struct sip_peer *peer; + char *name; + unsigned int removed:1; +}; + +/* A peer's bulk register aliases */ +struct sip_alias { + AST_LIST_ENTRY(sip_alias) next; + char *name; + struct sip_peer *peer; + int line_index; + unsigned int removed:1; +}; + +/* A peer's subscription */ +struct sip_subscription { + AST_LIST_ENTRY(sip_subscription) next; + struct sip_dialog *dialog; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(exten); + AST_STRING_FIELD(context); + ); + unsigned int removed:1; +}; + +extern struct ao2_container *sip_peers; +extern struct ao2_container *sip_peer_addresses; + +extern int sip_peer_static_count; +extern int sip_peer_realtime_count; + +extern struct sip_peer *sip_invalid_peer; + +int sip_peer_hash(const void *data, int flags); +int sip_peer_cmp(void *data, void *arg, int flags); +int sip_peer_address_hash(const void *data, int flags); +int sip_peer_address_cmp(void *data, void *arg, int flags); +struct sip_peer *sip_peer_find(const char *peer, int realtime, int devicestate_only); +struct sip_peer *sip_peer_address_find(const struct ast_sockaddr *address, int transport, int realtime, + int devicestate_only); + +int sip_peer_unlink(void *data, void *arg, int flags); +int sip_peer_set_removed(void *data, void *arg, int flags); + +struct sip_peer *sip_peer_temp_alloc(const char *name); +struct sip_peer *sip_peer_build(const char *name, struct ast_variable *variables, int realtime, int devicestate_only); + +int sip_peer_register(struct sip_peer *peer, struct sip_dialog *dialog, struct sip_message *request, + int *address_changed); +int sip_peer_unregister(const void *data); + +void sip_peer_update_subscriptions(struct sip_peer *peer); +void sip_peer_update_mailboxes(struct sip_peer *peer); +void sip_peer_update_aliases(struct sip_peer *peer); + +char *sip_peer_status2str(struct sip_peer *peer); +char *sip_peer_get_mailboxes(struct sip_peer *peer, struct ast_str **mailboxes); + +void sip_peer_set_messages(struct sip_peer *peer, int new_messages, int old_messages, int locked); +void sip_peer_set_auto_nat(struct sip_peer *peer, int nat_detected); + +int sip_peer_send_qualify(struct sip_peer *peer, int force); +int sip_peer_qualify(const void *data); +int sip_peer_qualify_timeout(const void *data); +void sip_peer_qualify_all(void); + +int sip_peer_send_mwi(struct sip_peer *peer, int cache_only); +int sip_peer_send_bulk_update(struct sip_peer *peer); +int sip_peer_send_do_not_disturb(struct sip_peer *peer); +int sip_peer_send_call_forward(struct sip_peer *peer); +int sip_peer_send_hunt_group(struct sip_peer *peer); +int sip_peer_send_qrt_url(struct sip_peer *peer); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/pickup.h asterisk-22.6.0/channels/sip/include/pickup.h --- asterisk-22.6.0.orig/channels/sip/include/pickup.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/pickup.h 2025-10-21 18:12:24.476604404 +1300 @@ -0,0 +1,33 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_PICKUP_H +#define _SIP_PICKUP_H + +int sip_pickup_call(struct ast_channel *channel); +int sip_pickup_exten(struct ast_channel *channel, const char *exten, const char *context); + +void sip_pickup_notify_subscribe(void); +void sip_pickup_notify_unsubscribe(void); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/proxy.h asterisk-22.6.0/channels/sip/include/proxy.h --- asterisk-22.6.0.orig/channels/sip/include/proxy.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/proxy.h 2025-10-21 18:12:24.477604377 +1300 @@ -0,0 +1,47 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_PROXY_H +#define _SIP_PROXY_H + +/* Forward declarations */ +struct sip_peer; +struct sip_dialog; + +/* Definition of a sip proxy server. For outbound proxies, a sip_peer will contain a reference to a dynamically + * allocated instance of a sip_proxy. A sip_dialog may also contain a reference to a peer's outboundproxy, or it may + * contain a reference to sip_config.proxy */ +struct sip_proxy { + enum ast_transport transport; + char host[MAXHOSTNAMELEN]; /* DNS name of domain/host or IP */ + int port; + struct ast_sockaddr address; /* Currently used IP address and port */ + time_t last_dns_update; /* When this was resolved */ + unsigned int force:1; /* Force use of this outbound proxy for all outbound requests */ +}; + +void sip_proxy_set(struct sip_dialog *dialog, struct sip_proxy *proxy); +struct sip_proxy *sip_proxy_get(struct sip_dialog *dialog, struct sip_peer *peer); +struct sip_proxy *sip_proxy_build(const char *config, int lineno, struct sip_proxy *proxy); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/realtime.h asterisk-22.6.0/channels/sip/include/realtime.h --- asterisk-22.6.0.orig/channels/sip/include/realtime.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/realtime.h 2025-10-21 18:12:24.477604377 +1300 @@ -0,0 +1,33 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_REALTIME_H +#define _SIP_REALTIME_H + +/* Forward declarations */ +struct sip_peer; + +struct sip_peer *sip_realtime_load(const char *name, const struct ast_sockaddr *address, int devicestate_only); +void sip_realtime_update(struct sip_peer *peer); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/recording.h asterisk-22.6.0/channels/sip/include/recording.h --- asterisk-22.6.0.orig/channels/sip/include/recording.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/recording.h 2025-10-21 18:12:24.477604377 +1300 @@ -0,0 +1,40 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_RECORDING_H +#define _SIP_RECORDING_H + +/* Information required to record a call */ +struct sip_recording_data { + unsigned int outgoing:1; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(call_id); + AST_STRING_FIELD(local_tag); + AST_STRING_FIELD(remote_tag); + ); +}; + +int sip_recording_start(const char *call_id, const char *local_tag, const char *remote_tag, int outgoing); +int sip_recording_stop(const char *call_id, const char *local_tag, const char *remote_tag); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/registrations.h asterisk-22.6.0/channels/sip/include/registrations.h --- asterisk-22.6.0.orig/channels/sip/include/registrations.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/registrations.h 2025-10-21 18:12:24.478604351 +1300 @@ -0,0 +1,92 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_REGISTRATIONS_H +#define _SIP_REGISTRATIONS_H + +enum sip_registration_state { + SIP_REGISTRATION_UNREGISTERED = 0, /* We are not registered. We should have a timeout scheduled for the initial + * (or next) registration transmission */ + SIP_REGISTRATION_REQUEST_SENT, /* Registration request sent sent initial request, waiting for an ack or a + * timeout to retransmit the initial request */ + SIP_REGISTRATION_AUTHORIZATION_SENT, /* We have tried to authenticate, waiting for a response */ + SIP_REGISTRATION_REGISTERED, /* Registered and done */ + SIP_REGISTRATION_REJECTED, /* Registration rejected only used when the remote party has an expire larger than + * our max-expire. This is a final state from which we do not recover */ + SIP_REGISTRATION_TIMEOUT, /* Registration about to expire, renewing registration */ + SIP_REGISTRATION_AUTHORIZATION_FAILED, /* We have no accepted credentials fatal - no chance to proceed */ + SIP_REGISTRATION_FAILED, /* Registration failed after several tries fatal - no chance to proceed */ +}; + +/* Forward declarations */ +struct sip_dialog; +struct sip_message; + +/* Registrations with other SIP proxies Created by sip_register_build(), the entry is linked in the 'sip_registration' + * list, and never deleted (other than at 'sip reload' or module unload times). The entry always has a pending timeout, + * either waiting for an ACK to the REGISTER message (in which case we have to retransmit the request), or waiting for + * the next REGISTER message to be sent (either the initial one, or once the previously completed registration one + * expires). The registration can be in one of many states, though at the moment the handling is a bit mixed */ +struct sip_registration { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(config);/* register string from config */ + AST_STRING_FIELD(user); /* Who we are registering as */ + AST_STRING_FIELD(domain); /* Registration domain */ + AST_STRING_FIELD(host); /* Domain or host we register to */ + AST_STRING_FIELD(secret); /* Password in clear text */ + AST_STRING_FIELD(md5_secret); /* Password MD5 hash */ + AST_STRING_FIELD(authorization_user); /* Who we *authenticate* as */ + AST_STRING_FIELD(exten); /* Contact extension */ + ); + enum ast_transport transport; /* Transport for this registration UDP, TCP or TLS */ + int port; /* Optional port override */ + int domain_port; /* Port override for domainport */ + int expires; /* Configured value to use for the Expires header */ + int attempts; /* Number of attempts (since the last success) */ + int timeout_sched_id; /* Sechd ID of timeout (registration failed) */ + int expires_sched_id; /* Sched ID of expiration */ + struct sip_dialog *dialog; /* Dialog for this registration */ + int state; /* Registration state (see above) */ + time_t last_registered; /* Last successful registration time */ + struct ast_dnsmgr_entry *dnsmgr; /* DNS refresh manager for register */ + struct ast_sockaddr address; /* Who the server thinks we are */ +}; + +extern struct ao2_container *sip_registrations; + +int sip_registration_cmp(void *data, void *arg, int flags); +int sip_registration_build(const char *config, int lineno); +int sip_registration_unlink(void *data, void *arg, int flags); + +void sip_registration_dnsmgr_lookup(struct ast_sockaddr *old_address, struct ast_sockaddr *new_address, void *data); + +void sip_registration_send_all(void); +int sip_registration_send(struct sip_registration *registration); +void sip_registration_sched_resend(struct sip_registration *registration, int when); +int sip_registration_resend(const void *data); + +int sip_registration_timeout(const void *data); +void sip_registration_start_timeout(struct sip_registration *registration); +void sip_registration_stop_timeout(struct sip_registration *registration); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/remotecc.h asterisk-22.6.0/channels/sip/include/remotecc.h --- asterisk-22.6.0.orig/channels/sip/include/remotecc.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/remotecc.h 2025-10-21 18:12:24.478604351 +1300 @@ -0,0 +1,92 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_REMOTECC_H +#define _SIP_REMOTECC_H + +/* Forward declarations */ +struct sip_dialog; +struct sip_message; +struct sip_peer; + +/* Remotecc applications */ +enum sip_remotecc_application { + SIP_REMOTECC_NONE = 0, + SIP_REMOTECC_CONFLIST = 1, + SIP_REMOTECC_CALLBACK = 2, +}; + +/* Information about a remotecc request */ +struct sip_remotecc_data { + char *soft_key_event; + struct { + char *call_id; + char *local_tag; + char *remote_tag; + } dialog; + struct { + char *call_id; + char *local_tag; + char *remote_tag; + } consult_dialog; + struct { + char *call_id; + char *local_tag; + char *remote_tag; + } join_dialog; + int application_id; + int conference_id; + char *user_call_data; +}; + +int sip_remotecc_park(struct sip_dialog *dialog, struct sip_message *request, struct sip_remotecc_data *remotecc_data); +int sip_remotecc_parkmonitor(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data); + +int sip_remotecc_conference(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data); +int sip_remotecc_join(struct sip_dialog *dialog, struct sip_message *request, struct sip_remotecc_data *remotecc_data); +int sip_remotecc_rmlastconf(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data); +int sip_remotecc_conflist(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data); + +int sip_remotecc_select(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data); +int sip_remotecc_unselect(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data); + +int sip_remotecc_startrecording(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data); +int sip_remotecc_stoprecording(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data); + +int sip_remotecc_idivert(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data); +int sip_remotecc_hlog(struct sip_dialog *dialog, struct sip_message *request, struct sip_remotecc_data *remotecc_data); +int sip_remotecc_qrt(struct sip_dialog *dialog, struct sip_message *request, struct sip_remotecc_data *remotecc_data); +int sip_remotecc_mcid(struct sip_dialog *dialog, struct sip_message *request, struct sip_remotecc_data *remotecc_data); +int sip_remotecc_callback(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/request.h asterisk-22.6.0/channels/sip/include/request.h --- asterisk-22.6.0.orig/channels/sip/include/request.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/request.h 2025-10-21 18:12:24.478604351 +1300 @@ -0,0 +1,56 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_REQUEST_H +#define _SIP_REQUEST_H + +/* Forward declarations */ +struct sip_dialog; + +int sip_request_send_invite(struct sip_dialog *dialog, int add_sdp, int init, const char *explicit_uri); +int sip_request_send_update(struct sip_dialog *dialog); + +int sip_request_send_ack(struct sip_dialog *dialog, uint32_t cseq, int new_branch); +int sip_request_send_bye(struct sip_dialog *dialog); +int sip_request_send_cancel(struct sip_dialog *dialog); + +int sip_request_send_reinvite_with_sdp(struct sip_dialog *dialog, int old_sdp_version, int add_image); + +int sip_request_send_options(struct sip_dialog *dialog); +int sip_request_send_register(struct sip_dialog *dialog, int add_authorization); +int sip_request_send_subscribe(struct sip_dialog *dialog, int init); + +int sip_request_send_notify(struct sip_dialog *dialog, int init); +int sip_request_send_notify_with_sipfrag(struct sip_dialog *dialog, uint32_t cseq, char *status_line); +int sip_request_send_notify_with_extension_state(struct sip_dialog *dialog, struct ast_state_cb_info *state_info, + int timeout); +int sip_request_send_notify_with_mwi(struct sip_dialog *dialog, int new_messages, int old_messages, + const char *mwi_exten); + +int sip_request_send_refer(struct sip_dialog *dialog, int init); +int sip_request_send_refer_with_content(struct sip_dialog *dialog, const char *content_type, const char *content); + +int sip_request_send_info_with_media_control(struct sip_dialog *dialog); +int sip_request_send_message(struct sip_dialog *dialog, int init); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/response.h asterisk-22.6.0/channels/sip/include/response.h --- asterisk-22.6.0.orig/channels/sip/include/response.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/response.h 2025-10-21 18:12:24.478604351 +1300 @@ -0,0 +1,64 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_RESPONSE_H +#define _SIP_RESPONSE_H + +/* Forward declarations */ +struct sip_socket; +struct sip_dialog; +struct sip_message; + +int sip_response_send(struct sip_dialog *dialog, const char *status_line, struct sip_message *request); +int sip_response_send_provisional(struct sip_dialog *dialog, const char *status_line, struct sip_message *request, + int with_sdp); +int sip_response_send_reliable(struct sip_dialog *dialog, const char *status_line, struct sip_message *request); + +int sip_response_send_with_sdp(struct sip_dialog *dialog, const char *status_line, struct sip_message *request, + int reliable, int old_sdp_version, int add_identity); + +int sip_response_send_with_date(struct sip_dialog *dialog, const char *status_line, struct sip_message *request); +int sip_response_send_with_unsupported(struct sip_dialog *dialog, struct sip_message *request, const char *unsupported); + +int sip_response_send_with_www_authenticate(struct sip_dialog *dialog, struct sip_message *request, int reliable, + int stale); +int sip_response_send_with_authorization_failure(struct sip_dialog *dialog, struct sip_message *request, int res, + int reliable); +int sip_response_send_with_fake_authorization(struct sip_dialog *dialog, struct sip_message *request); + +int sip_response_send_with_identity(struct sip_dialog *dialog, const char *status_line, struct sip_message *request); +int sip_response_send_with_diversion(struct sip_dialog *dialog, struct sip_message *request); + +int sip_response_send_with_etag(struct sip_dialog *dialog, struct sip_message *request, char *etag); +int sip_response_send_with_min_se(struct sip_dialog *dialog, struct sip_message *request, int min_se); +int sip_response_send_with_min_expires(struct sip_dialog *dialog, struct sip_message *request, int min_expires); + +int sip_response_send_with_accept(struct sip_dialog *dialog, const char *status_line, struct sip_message *request); +int sip_response_send_with_retry_after(struct sip_dialog *dialog, struct sip_message *request, int retry_after); + +int sip_response_send_with_optionsind(struct sip_dialog *dialog, struct sip_message *request); +int sip_response_send_with_feature_event(struct sip_dialog *dialog, struct sip_message *request, int feature_event); + +int sip_response_send_using_temp(struct sip_socket *socket, const char *status_line, struct sip_message *request); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/route.h asterisk-22.6.0/channels/sip/include/route.h --- asterisk-22.6.0.orig/channels/sip/include/route.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/route.h 2025-10-21 18:12:24.479604324 +1300 @@ -0,0 +1,54 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_ROUTE_H +#define _SIP_ROUTE_H + +/* Internal enum to remember last calculated */ +enum sip_route_type { + SIP_ROUTE_UNKNOWN = 0, /* strict/loose routing needs to be rechecked */ + SIP_ROUTE_LOOSE, /* The first hop contains ;lr or does not exist */ + SIP_ROUTE_STRICT, /* The first hop exists and does not contain ;lr */ +}; + +/* Structure to store route information */ +struct sip_route { + AST_LIST_HEAD_NOLOCK(, sip_route_hop) hops; + int type; +}; + +/* Structure to save a route hop */ +struct sip_route_hop { + AST_LIST_ENTRY(sip_route_hop) next; + char *uri; +}; + +void sip_route_parse(struct sip_route *route, const char *path, int forwards); +void sip_route_copy(struct sip_route *to_route, const struct sip_route *from_route); +void sip_route_destroy(struct sip_route *route); +struct ast_str *sip_route_list(const struct sip_route *route, int skip); +int sip_route_empty(const struct sip_route *route); +int sip_route_is_strict(struct sip_route *route); +const char *sip_route_first_uri(const struct sip_route *route); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/rtp_glue.h asterisk-22.6.0/channels/sip/include/rtp_glue.h --- asterisk-22.6.0.orig/channels/sip/include/rtp_glue.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/rtp_glue.h 2025-10-21 18:12:24.480604297 +1300 @@ -0,0 +1,40 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_RTP_GLUE_H +#define _SIP_RTP_GLUE_H + +extern struct ast_rtp_glue sip_rtp_glue; + +void sip_rtp_get_codec(struct ast_channel *channel, struct ast_format_cap *format_cap); +int sip_rtp_update_peer(struct ast_channel *channel, struct ast_rtp_instance *instance, + struct ast_rtp_instance *video_rtp, struct ast_rtp_instance *text_rtp, const struct ast_format_cap *cap, + int nat_active); +enum ast_rtp_glue_result sip_rtp_get_info(struct ast_channel *channel, struct ast_rtp_instance **audio_rtp); +enum ast_rtp_glue_result sip_vrtp_get_info(struct ast_channel *channel, struct ast_rtp_instance **video_rtp); +enum ast_rtp_glue_result sip_trtp_get_info(struct ast_channel *channel, struct ast_rtp_instance **text_rtp); +int sip_rtp_allow_remote(struct ast_channel *channel1, struct ast_rtp_instance *rtp); +int sip_vrtp_allow_remote(struct ast_channel *channel1, struct ast_rtp_instance *rtp); +int sip_rtp_allow_any_remote(struct ast_channel *channel, struct ast_rtp_instance *rtp, const char *type); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/sdp.h asterisk-22.6.0/channels/sip/include/sdp.h --- asterisk-22.6.0.orig/channels/sip/include/sdp.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/sdp.h 2025-10-21 18:12:24.480604297 +1300 @@ -0,0 +1,51 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_SDP_H +#define _SIP_SDP_H + +enum sip_sdp_fax_mode { + SIP_SDP_FAX_IGNORE = 0, /* Do not modify T38 information at all */ + SIP_SDP_FAX_INITIATE, /* Remote side has requested T38 with us */ + SIP_SDP_FAX_ACCEPT, /* Remote side accepted our T38 request */ +}; + +/* Structure for remembering offered media in an INVITE, to make sure we reply to all media streams */ +struct sip_sdp_media { + AST_LIST_ENTRY(sip_sdp_media) next; + enum ast_media_type type; /* The type of media that was offered */ + struct ast_str *decline_m_line; /* Used if the media type is unknown/unused or a media stream is declined */ +}; + +/* Forward declarations */ +struct sip_dialog; +struct sip_message; + +int sip_sdp_find(struct sip_message *request); +int sip_sdp_parse(struct sip_dialog *dialog, struct sip_message *request, int add_image, int offered); +int sip_sdp_build(struct sip_dialog *dialog, struct sip_message *request, int old_sdp_version, int add_media, + int add_image); + +void sip_sdp_media_destroy(struct sip_dialog *dialog); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/security_events.h asterisk-22.6.0/channels/sip/include/security_events.h --- asterisk-22.6.0.orig/channels/sip/include/security_events.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/security_events.h 2025-10-21 18:12:24.480604297 +1300 @@ -0,0 +1,35 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2011, Digium, Inc. + * + * Michael L. Young + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_SECURITY_EVENTS_H +#define _SIP_SECURITY_EVENTS_H + +/* Forward declarations */ +struct sip_dialog; +struct sip_message; + +int sip_security_event(const struct sip_dialog *dialog, const struct sip_message *request, const int res); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/session_timer.h asterisk-22.6.0/channels/sip/include/session_timer.h --- asterisk-22.6.0.orig/channels/sip/include/session_timer.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/session_timer.h 2025-10-21 18:12:24.481604271 +1300 @@ -0,0 +1,51 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_SESSION_TIMER_H +#define _SIP_SESSION_TIMER_H + +/* Modes in which Asterisk can be configured to run SIP Session-Timers */ +enum sip_session_timer_mode { + SIP_SESSION_TIMER_MODE_REFUSE = 0, /* Ignore inbound Session-Timers requests */ + SIP_SESSION_TIMER_MODE_ACCEPT, /* Honor inbound Session-Timer requests */ + SIP_SESSION_TIMER_MODE_ORIGINATE, /* Originate outbound and honor inbound requests */ +}; + +/* The entity playing the refresher role for Session-Timers */ +enum sip_session_timer_refresher { + SIP_SESSION_TIMER_REFRESHER_AUTO = 0, /* Negotiated */ + SIP_SESSION_TIMER_REFRESHER_UAC, /* Initially prefer session refresh by Asterisk */ + SIP_SESSION_TIMER_REFRESHER_UAS, /* Initially prefer session refresh by the other side */ +}; + +/* Forward declarations */ +struct sip_dialog; +struct sip_message; + +int sip_session_timer_handle_invite(struct sip_dialog *dialog, struct sip_message *request, int reinvite); + +void sip_session_timer_start(struct sip_dialog *dialog); +void sip_session_timer_stop(struct sip_dialog *dialog); +void sip_session_timer_restart(struct sip_dialog *dialog); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/sip.h asterisk-22.6.0/channels/sip/include/sip.h --- asterisk-22.6.0.orig/channels/sip/include/sip.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/sip.h 2025-10-21 18:12:24.481604271 +1300 @@ -0,0 +1,49 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_H +#define _SIP_H + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +/* In many SIP headers, absence of a port number implies port 5060, and this is why we cannot change the below constant. There is a + * limited number of places in asterisk where we could, in principle, use a different "default" port number, but we do not support this + * feature at the moment. You can run Asterisk with SIP on a different port with a configuration option. If you change this value in the + * source code, the signalling will be incorrect. */ + +#define SIP_STANDARD_PORT 5060 /* Standard SIP port for UDP and TCP from RFC 3261 */ +#define SIP_STANDARD_TLS_PORT 5061 /* Standard SIP TLS port from RFC 3261 */ + +#define SIP_MAGIC_COOKIE "z9hG4bK" /* z9hG4bK is a magic cookie. See RFC 3261 section 8.1.1.7 */ + +void sip_module_ref(void); +void sip_module_unref(void); +void sip_module_notice(void); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/transfer.h asterisk-22.6.0/channels/sip/include/transfer.h --- asterisk-22.6.0.orig/channels/sip/include/transfer.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/transfer.h 2025-10-21 18:12:24.481604271 +1300 @@ -0,0 +1,49 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_TRANSFER_H +#define _SIP_TRANSFER_H + +/* Forward declarations */ +struct sip_dialog; + +/* Data to set on a channel that runs dialplan + * at the completion of a blind transfer */ +struct sip_transfer_blind_data { + const char *referred_by; /* Contents of the REFER's Referred-by header */ + const char *domain; /* Domain of the URI in the REFER's Refer-To header */ + const char *replaces; /* Contents of what to place in a Replaces header of an INVITE */ + const char *from_name; /* Redirecting from name */ + const char *from_number; /* Redirecting from number */ + const char *to_name; /* Redirecting to name */ + const char *to_number; /* Redirecting to number */ + const char *tag; /* Caller tag */ + const char *reason; /* Custom redirect reason, NULL if code is non-zero */ + int code; /* Standard redirect code */ +}; + +void sip_transfer_blind(struct ast_channel *channel, struct transfer_channel_data *chan_data, + enum ast_transfer_type transfer_type); +int sip_transfer_attended(struct sip_dialog *dialog, struct ast_channel *channel, uint32_t cseq, int *no_unlock); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/include/utils.h asterisk-22.6.0/channels/sip/include/utils.h --- asterisk-22.6.0.orig/channels/sip/include/utils.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/include/utils.h 2025-10-21 18:12:24.482604244 +1300 @@ -0,0 +1,106 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_UTILS_H +#define _SIP_UTILS_H + +/* SIP Methods */ +enum sip_methods { + SIP_METHOD_ACK = 1 << 0, /* End of a three-way handshake started with INVITE. */ + SIP_METHOD_BYE = 1 << 1, /* End of a session */ + SIP_METHOD_CANCEL = 1 << 2, /* Cancel an INVITE */ + SIP_METHOD_INVITE = 1 << 3, /* Set up a session */ + SIP_METHOD_INFO = 1 << 4, /* Information updates during a session */ + SIP_METHOD_MESSAGE = 1 << 5, /* Text messaging */ + SIP_METHOD_NOTIFY = 1 << 6, /* Status update, result of a SUBSCRIBE or a REFER */ + SIP_METHOD_OPTIONS = 1 << 7, /* Check capabilities of a device, used for "ping" too */ + SIP_METHOD_PUBLISH = 1 << 8, /* Update presence state */ + SIP_METHOD_REFER = 1 << 9, /* Refer to another URI (transfer) */ + SIP_METHOD_REGISTER = 1 << 10, /* Registration to the proxy */ + SIP_METHOD_SUBSCRIBE = 1 << 11, /* Subscribe for updates (voicemail, device status, presence) */ + SIP_METHOD_UPDATE = 1 << 12, /* Update a session */ +}; + +/* SIP Options */ +enum sip_options { + SIP_OPTION_REPLACES = 1 << 0, /* Transfer */ + SIP_OPTION_TIMER = 1 << 1, /* Session timers */ +}; + +enum sip_parse_refer_to_result { + SIP_REFER_MISSING_HEADER = -3, + SIP_REFER_INVALID_URI = -2, + SIP_REFER_EXTEN_NOT_FOUND = -1, + SIP_REFER_EXTEN_FOUND = 0, +}; + +/* Forward declarations */ +struct sip_peer; +struct sip_dialog; + +int sip_hangup2cause(int cause); +const char *sip_cause2hangup(int cause); +const char *sip_reason2str(struct ast_party_redirecting_reason *reason); + +const char *sip_srv_protocol(enum ast_transport transport); +const char *sip_srv_service(enum ast_transport transport); + +int sip_find_method(const char *name); +const char *sip_method2str(int method); +char *sip_methods2str(unsigned int methods); +char *sip_options2str(unsigned int options); + +char *sip_get_uri(char *contact); +int sip_cmp_uri(const char *uri1, const char *uri2); +int sip_get_uri_address(const char *uri, struct ast_sockaddr *address); + +int sip_parse_uri(char *uri, char **scheme, char **user, char **domain, char **parameters, char **headers); +int sip_parse_parameters(char *parameters, const char sep, ...); +int sip_parse_contact(char *contact, char **name, char **user, char **domain, char **parameters, char **headers); +char *sip_parse_tag(char *contact); +enum ast_transport sip_parse_transport(char *parameters); +int sip_parse_port(char *host, int *port); + +int sip_parse_via(struct sip_message *message); +int sip_parse_allow(struct sip_dialog *dialog, struct sip_message *message); +int sip_parse_supported(struct sip_dialog *dialog, struct sip_message *message); +int sip_parse_require(struct sip_dialog *dialog, struct sip_message *message); + +int sip_parse_reason(struct sip_dialog *dialog, struct sip_message *message); +int sip_parse_path(struct sip_peer *peer, struct sip_message *message); +int sip_parse_identity(struct sip_dialog *dialog, struct sip_message *message); +int sip_parse_refer_to(struct sip_dialog *dialog, struct sip_message *message); +int sip_parse_diversion(struct sip_dialog *dialog, struct sip_message *message, int set_call_forward); +int sip_parse_authorization(struct sip_dialog *dialog, char *authorization); +int sip_parse_session_expires(struct sip_dialog *dialog, struct sip_message *message); +int sip_parse_min_se(struct sip_dialog *dialog, struct sip_message *message); + +void sip_parse_rtp_stats(struct sip_dialog *dialog, struct sip_message *message); +char *sip_parse_content_type(char *content_type, char **boundary); + +void sip_queue_hangup_cause(struct sip_dialog *dialog, int cause); +void sip_try_suggested_codec(struct sip_dialog *dialog); +struct ast_channel *sip_find_ringing_channel(struct ao2_container *device_state); +void sip_set_redirecting(struct sip_dialog *dialog); + +#endif diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/manager.c asterisk-22.6.0/channels/sip/manager.c --- asterisk-22.6.0.orig/channels/sip/manager.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/manager.c 2025-10-21 18:38:17.452218092 +1300 @@ -0,0 +1,1224 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/channel.h" +#include "asterisk/callerid.h" +#include "asterisk/indications.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_channels.h" +#include "asterisk/stasis_endpoints.h" +#include "asterisk/stasis_system.h" +#include "asterisk/pbx.h" +#include "asterisk/acl.h" +#include "asterisk/manager.h" +#include "asterisk/udptl.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/registrations.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/manager.h" +#include "include/mwi_subscriptions.h" +#include "include/fax.h" + +/*** DOCUMENTATION + + + 1.2.0 + 22.3.0 + + + List SIP peers. + + + + + + Lists SIP peers in text format with details on current status. SIP peers will follow as + separate events called SIPPeer, followed by a final event called + SIPPeersComplete. + + + + + + + 22.3.0 + + + Information about a SIP MWI subscription. + + + + + Name of the peer. + + + Text description of peer. + + + Remote IP address of peer. + + + Remote port of peer. + + + Whether the peer is dynamic. + + + Caller-ID of peer. + + + When the peer registration expires in milliseconds. + + + Qualify status of peer. + + + Whether the peer was loaded from realtime or not. + + + Account code of peer. + + + Cisco device name. + + + + SIPPeersComplete + + + + + + + + 22.3.0 + + + Indicates the end of the list of SIP MWI subscriptions. + + + + + Conveys the status of the command response list + + + The total number of list items produced + + + + SIPPeer + + + + + + + + 22.3.0 + + + List SIP registrations. + + + + + + Lists all registration requests and status. Registrations will follow as separate + events called SIPRegistration followed by a final event called + SIPRegistrationsComplete. + + + + + + + 22.3.0 + + + Information about a SIP registration. + + + + + Transport (protocol). + + + Remote host. + + + Remote port. + + + From username. + + + From domain name. + + + From domain port. + + + State of the registration. + + + Seconds until registration expires. + + + UNIX epoch time when last registered. + + + + SIPRegistrationsComplete + + + + + + + + 22.3.0 + + + Indicates the end of the list of SIP registrations. + + + + + Conveys the status of the command response list + + + The total number of list items produced + + + + SIPRegistration + + + + + + + + 22.3.0 + + + List SIP MWI subscriptions. + + + + + + Lists all MWI subscriptions and status. MWI subscriptions will follow as separate + events called SIPMWISubscription followed by a final event called + SIPMWISubscriptionsComplete. + + + + + + + 22.3.0 + + + Information about a SIP MWI subscription. + + + + + Transport (protocol). + + + Remote host. + + + Remote port. + + + From username. + + + Local mailbox name. + + + Number of new messages + + + Number of old messages + + + Whether subscription is active. + + + + SIPMWISubscriptionsComplete + + + + + + + + 22.3.0 + + + Indicates the end of the list of SIP MWI subscriptions. + + + + + Conveys the status of the command response list + + + The total number of list items produced + + + + SIPMWISubscription + + + + + + + + 1.2.0 + 22.3.0 + + + Show SIP peer. + + + + + The peer name you want to check. + + + + Show one SIP peer with details on current status. + + + + + 11.0.0 + 22.3.0 + + + Show the status of one or all of SIP peers. + + + + + The peer name you want to check. + + + + Retrieves the status of one or all of the sip peers. If no peer name is specified, status + for all of the sip peers will be retrieved. + + + + + + + 22.3.0 + + + Information about a SIP peer. + + + + + Name of the peer. + + + Status of the peer. + + + Qualify (ping) time in milliseconds. + + + + SIPPeersComplete + + + + + + + + 22.3.0 + + + Indicates the end of the list of SIP peers. + + + + + Conveys the status of the command response list + + + The total number of list items produced + + + + SIPPeerStatus + + + + + + + + 1.6.1.0 + 22.3.0 + + + Send a SIP NOTIFY request. + + + + + Peer to receive the NOTIFY. + + + When specified, NOTIFY will be sent as a part of an existing dialog. + + + At least one variable pair must be specified. + name=value + + + + Sends a SIP NOTIFY. + All parameters for this event must be specified in the body of this request + via multiple Variable: name=value sequences. + + + + + 1.6.1.0 + 22.3.0 + + + Qualify a SIP peer. + + + + + The peer name you want to qualify. + + + + Qualify a SIP peer. + + + SIPQualifyPeerDone + + + + + Raised when SIPQualifyPeer has finished qualifying the specified peer. + + + + The name of the peer. + + + + SIPQualifyPeer + + + + + + Raised when a SIP session times out. + + + + The source of the session timeout. + + + + + + + + + ***/ + +/* Show SIP peers in the manager API */ +int sip_manager_peers(struct mansession *session, const struct message *message) +{ + char callerid[AST_MAX_EXTENSION]; + const char *action_id; + struct sip_peer *peer; + struct ao2_iterator iter; + int count, realtime_peers; + + realtime_peers = ast_check_realtime("sippeers"); + + action_id = astman_get_header(message, "ActionID"); + astman_send_listack(session, message, "Peers list will follow", "start"); + + iter = ao2_iterator_init(sip_peers, 0); + + for (count = 0; (peer = ao2_iterator_next(&iter)); count++) { + astman_append(session, "Event: SIPPeer\r\n"); + + if (!ast_strlen_zero(action_id)) { + astman_append(session, "ActionID: %s\r\n", action_id); + } + + astman_append(session, + "Name: %s\r\n" + "Description: %s\r\n" + "Address: %s\r\n" + "AddressPort: %s\r\n" + "Dynamic: %s\r\n" + "CallerID: %s\r\n" + "RegisterExpires: %ld\r\n" + "Status: %s\r\n" + "Realtime: %s\r\n" + "AccountCode: %s\r\n" + "DeviceName: %s\r\n" + "\r\n", + peer->name, + peer->description, + !ast_sockaddr_isnull(&peer->address) ? ast_sockaddr_stringify_addr(&peer->address) : "", + !ast_sockaddr_isnull(&peer->address) ? ast_sockaddr_stringify_port(&peer->address) : "", + peer->host_dynamic ? "yes" : "no", + ast_callerid_merge(callerid, sizeof(callerid), peer->caller_name, peer->caller_number, ""), + ast_sched_when(sip_sched_context, peer->register_expires_sched_id), + sip_peer_status2str(peer), + realtime_peers ? (peer->realtime ? "yes" : "no") : "no", + peer->accountcode, + peer->device_name); + + ao2_unlock(peer); + ao2_ref(peer, -1); + } + + ao2_iterator_destroy(&iter); + + /* Send final confirmation */ + astman_send_list_complete_start(session, message, "SIPPeersComplete", count); + astman_send_list_complete_end(session); + return 0; +} + +/* Show SIP registrations in the manager API */ +int sip_manager_registrations(struct mansession *session, const struct message *message) +{ + const char *action_id; + struct ao2_iterator iter; + struct sip_registration *registration; + int count; + + action_id = astman_get_header(message, "ActionID"); + astman_send_listack(session, message, "Registrations list will follow", "start"); + + iter = ao2_iterator_init(sip_registrations, 0); + + for (count = 0; (registration = ao2_iterator_next(&iter)); count++) { + ao2_lock(registration); + astman_append(session, "Event: SIPRegistration\r\n"); + + if (!ast_strlen_zero(action_id)) { + astman_append(session, "ActionID: %s\r\n", action_id); + } + + astman_append(session, + "Transport: %s\r\n" + "Host: %s\r\n" + "Port: %d\r\n" + "User: %s\r\n" + "Domain: %s\r\n" + "DomainPort: %d\r\n" + "Expires: %ld\r\n", + ast_transport2str(registration->transport), + registration->host, + registration->port ? registration->port : SIP_STANDARD_PORT, + registration->user, + S_OR(registration->domain, registration->host), + registration->domain_port ? registration->domain_port : SIP_STANDARD_PORT, + ast_sched_when(sip_sched_context, registration->expires_sched_id)); + + astman_append(session, "State: "); + + switch (registration->state) { + case SIP_REGISTRATION_UNREGISTERED: + astman_append(session, "unregistered"); + break; + case SIP_REGISTRATION_REQUEST_SENT: + astman_append(session, "request sent"); + break; + case SIP_REGISTRATION_AUTHORIZATION_SENT: + astman_append(session, "authorization sent"); + break; + case SIP_REGISTRATION_AUTHORIZATION_FAILED: + astman_append(session, "authorization failed"); + break; + case SIP_REGISTRATION_REGISTERED: + case SIP_REGISTRATION_TIMEOUT: /* Hidden state. We are renewing registration */ + astman_append(session, "registered"); + break; + case SIP_REGISTRATION_FAILED: + astman_append(session, "failed"); + break; + case SIP_REGISTRATION_REJECTED: + astman_append(session, "rejected"); + break; + } + + astman_append(session, "\r\n"); + astman_append(session, "LastRegistered: %ld\r\n" + "\r\n", + (long) registration->last_registered); + + ao2_unlock(registration); + ao2_ref(registration, -1); + } + + ao2_iterator_destroy(&iter); + + astman_send_list_complete_start(session, message, "SIPRegistrationsComplete", count); + astman_send_list_complete_end(session); + return 0; +} + +/* Show SIP registrations in the manager API */ +int sip_manager_mwi_subscriptions(struct mansession *session, const struct message *message) +{ + const char *action_id; + struct ao2_iterator iter; + struct sip_mwi_subscription *mwi_subscription; + int count; + + action_id = astman_get_header(message, "ActionID"); + astman_send_listack(session, message, "MWI subscriptions list will follow", "start"); + + iter = ao2_iterator_init(sip_mwi_subscriptions, 0); + + for (count = 0; (mwi_subscription = ao2_iterator_next(&iter)); count++) { + ao2_lock(mwi_subscription); + astman_append(session, "Event: SIPMWISubscription\r\n"); + + if (!ast_strlen_zero(action_id)) { + astman_append(session, "ActionID: %s\r\n", action_id); + } + + astman_append(session, + "Transport: %s\r\n" + "Host: %s\r\n" + "Port: %d\r\n" + "User: %s\r\n" + "Mailbox: %s\r\n" + "NewMessages: %d\r\n" + "OldMessages: %d\r\n" + "Subscribed: %s\r\n" + "\r\n", + ast_transport2str(mwi_subscription->transport), + mwi_subscription->host, + mwi_subscription->port ? mwi_subscription->port : SIP_STANDARD_PORT, + mwi_subscription->user, + mwi_subscription->mailbox, + mwi_subscription->new_messages, + mwi_subscription->old_messages, + mwi_subscription->subscribed ? "yes" : "no"); + + ao2_unlock(mwi_subscription); + ao2_ref(mwi_subscription, -1); + } + + ao2_iterator_destroy(&iter); + + astman_send_list_complete_start(session, message, "SIPMWISubscriptionsComplete", count); + astman_send_list_complete_end(session); + return 0; +} + +/* Show SIP peers in the manager API */ +int sip_manager_show_peer(struct mansession *session, const struct message *message) +{ + const char *peer_name, *action_id; + char callerid[AST_MAX_EXTENSION], groups[64]; + struct sip_peer *peer; + struct ast_str *format_names, *named_groups, *mailboxes, *transports; + struct sip_alias *alias; + struct sip_subscription *subscription; + + action_id = astman_get_header(message, "ActionID"); + peer_name = astman_get_header(message, "Peer"); + + if (ast_strlen_zero(peer_name)) { + astman_send_error(session, message, "Peer name missing"); + return 0; + } + + if (!(peer = sip_peer_find(peer_name, TRUE, FALSE))) { + astman_send_error(session, message, "Peer not found"); + return 0; + } + + astman_append(session, "Response: Success\r\n"); + + if (!ast_strlen_zero(action_id)) { + astman_append(session, "ActionID: %s\r\n", action_id); + } + + astman_append(session, "Name: %s\r\n", peer->name); + astman_append(session, "Description: %s\r\n", peer->description); + astman_append(session, "AuthorizationUser: %s\r\n", peer->authorization_user); + astman_append(session, "SecretExist: %s\r\n", !ast_strlen_zero(peer->secret) ? "yes" : "no"); + astman_append(session, "MD5SecretExist: %s\r\n", !ast_strlen_zero(peer->md5_secret) ? "yes" : "no"); + astman_append(session, "RemoteSecretExist: %s\r\n", !ast_strlen_zero(peer->remote_secret) ? "yes" : "no"); + astman_append(session, "Context: %s\r\n", peer->context); + + if (!ast_strlen_zero(peer->subscribe_context)) { + astman_append(session, "SubscribeContext: %s\r\n", peer->subscribe_context); + } + + if (!ast_strlen_zero(peer->message_context)) { + astman_append(session, "MessageContext: %s\r\n", peer->message_context); + } + + astman_append(session, "Language: %s\r\n", peer->language); + astman_append(session, "ToneZone: %s\r\n", peer->tone_zone); + astman_append(session, "MOHSuggest: %s\r\n", peer->moh_suggest); + astman_append(session, "MOHInterpret: %s\r\n", peer->moh_interpret); + + if (!ast_strlen_zero(peer->accountcode)) { + astman_append(session, "AccountCode: %s\r\n", peer->accountcode); + } + + astman_append(session, "AMAFlags: %s\r\n", ast_channel_amaflags2string(peer->amaflags)); + + if (!ast_strlen_zero(peer->from_user)) { + astman_append(session, "FromUser: %s\r\n", peer->from_user); + } + + if (!ast_strlen_zero(peer->from_domain)) { + astman_append(session, "FromDomain: %s\r\n", peer->from_domain); + astman_append(session, "FromDomainPort: %d\r\n", peer->from_domain_port ? peer->from_domain_port : SIP_STANDARD_PORT); + } + + astman_append(session, "CallGroup: %s\r\n", ast_print_group(groups, sizeof(groups), peer->callgroup)); + astman_append(session, "PickupGroup: %s\r\n", ast_print_group(groups, sizeof(groups), peer->pickupgroup)); + + named_groups = ast_str_alloca(512); + astman_append(session, "NamedCallGroup: %s\r\n", ast_print_namedgroups(&named_groups, peer->named_callgroups)); + ast_str_reset(named_groups); + astman_append(session, "NamedPickupGroup: %s\r\n", ast_print_namedgroups(&named_groups, peer->named_pickupgroups)); + + mailboxes = ast_str_alloca(512); + astman_append(session, "Mailbox: %s\r\n", sip_peer_get_mailboxes(peer, &mailboxes)); + astman_append(session, "VoiceMailNewMessages: %d\r\n", peer->new_messages); + astman_append(session, "VoiceMailOldMessages: %d\r\n", peer->old_messages); + astman_append(session, "AllowTransfer: %s\r\n", peer->allow_transfer ? "yes" : "no"); + astman_append(session, "AllowSubscriptions: %s\r\n", peer->allow_subscribe ? "yes" : "no"); + astman_append(session, "MaxForwards: %d\r\n", peer->max_forwards); + astman_append(session, "AutoFraming: %s\r\n", peer->auto_framing ? "yes" : "no"); + astman_append(session, "MaxCalls: %d\r\n", peer->max_calls); + astman_append(session, "BusyLevel: %d\r\n", peer->busy_level); + astman_append(session, "MaxCallBitrate: %d\r\n", peer->max_call_bitrate); + astman_append(session, "CallerID: %s\r\n", + ast_callerid_merge(callerid, sizeof(callerid), peer->caller_name, peer->caller_number, "")); + astman_append(session, "CallerPresentation: %s\r\n", ast_named_caller_presentation(peer->caller_presentation)); + astman_append(session, "NATForceRport: %s%s\r\n", peer->nat_force_rport ? "yes" : "no", peer->nat_auto_rport ? ",auto" : ""); + astman_append(session, "NATRTP: %s%s\r\n", peer->nat_rtp ? "yes" : "no", peer->nat_auto_rtp ? ",auto" : ""); + astman_append(session, "IPAddressACL: %s\r\n", !ast_acl_list_is_empty(peer->address_acl) ? "yes" : "no"); + astman_append(session, "ContactACL: %s\r\n", !ast_acl_list_is_empty(peer->contact_acl) ? "yes" : "no"); + astman_append(session, "DirectMediaACL: %s\r\n", !ast_acl_list_is_empty(peer->direct_media_acl) ? "yes" : "no"); + astman_append(session, "UserEqPhone: %s\r\n", peer->user_eq_phone ? "yes" : "no"); + astman_append(session, "Video: %s\r\n", peer->video_support ? "yes" : "no"); + astman_append(session, "Text: %s\r\n", peer->text_support ? "yes" : "no"); + astman_append(session, "DirectMedia: %s\r\n", peer->direct_media ? "yes" : "no"); + astman_append(session, "TimerT1: %d\r\n", peer->timer_t1); + astman_append(session, "TimerB: %d\r\n", peer->timer_b); + astman_append(session, "RTPTimeout: %d\r\n", peer->rtp_timeout); + astman_append(session, "RTPHoldTimeout: %d\r\n", peer->rtp_hold_timeout); + astman_append(session, "RTPKeepAlive: %d\r\n", peer->rtp_keepalive); + astman_append(session, "Fax: %s\r\n", peer->fax_support ? "yes" : "no"); + astman_append(session, "FaxErrorCorrection: "); + + if (peer->udptl_error_correction == UDPTL_ERROR_CORRECTION_FEC) { + astman_append(session, "fec"); + } else if (peer->udptl_error_correction == UDPTL_ERROR_CORRECTION_REDUNDANCY) { + astman_append(session, "redundancy"); + } else { + astman_append(session, "none"); + } + + astman_append(session, "\r\n"); + astman_append(session, "FaxMaxDatagram: %d\r\n", peer->fax_max_datagram); + astman_append(session, "Timer: "); + + if (peer->session_timer_mode == SIP_SESSION_TIMER_MODE_ORIGINATE) { + astman_append(session, "originate"); + } else if (peer->session_timer_mode == SIP_SESSION_TIMER_MODE_ACCEPT) { + astman_append(session, "accept"); + } else if (peer->session_timer_mode == SIP_SESSION_TIMER_MODE_REFUSE) { + astman_append(session, "refuse"); + } + + astman_append(session, "\r\n"); + astman_append(session, "TimerRefresher: "); + + if (peer->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_AUTO) { + astman_append(session, "auto"); + } else if (peer->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_UAC) { + astman_append(session, "uac"); + } else if (peer->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_UAS) { + astman_append(session, "uas"); + } + + astman_append(session, "\r\n"); + astman_append(session, "TimerMaxExpires: %d\r\n", peer->session_timer_max_expires); + astman_append(session, "TimerMinExpires: %d\r\n", peer->session_timer_min_expires); + astman_append(session, "RTPEncryption: %s\r\n", peer->secure_media ? "yes" : "no"); + astman_append(session, "RTCPMux: %s\r\n", peer->rtcp_mux ? "yes" : "no"); + astman_append(session, "DTMFMode: "); + + if (peer->dtmf_mode == SIP_DTMF_MODE_INBAND) { + astman_append(session, "inband"); + } else if (peer->dtmf_mode == SIP_DTMF_MODE_RFC2833) { + astman_append(session, "rfc2833"); + } + + astman_append(session, "\r\n"); + astman_append(session, "RelaxDTMF: %s\r\n", peer->relax_dtmf ? "yes" : "no"); + astman_append(session, "AllowOverlap: "); + + if (peer->allow_overlap == SIP_ALLOW_OVERLAP_INVITE) { + astman_append(session, "invite"); + } else if (peer->allow_overlap == SIP_ALLOW_OVERLAP_DTMF) { + astman_append(session, "dtmf"); + } else { + astman_append(session, "no"); + } + + astman_append(session, "\r\n"); + astman_append(session, "Host: %s\r\n", peer->host_dynamic ? "dynamic" : peer->host); + astman_append(session, "IPAddress: %s\r\n", ast_sockaddr_stringify_addr(&peer->address)); + astman_append(session, "Port: %d\r\n", ast_sockaddr_port(&peer->address)); + + astman_append(session, "PrimaryTransport: "); + + if (peer->default_transport == AST_TRANSPORT_UDP) { + astman_append(session, "udp"); + } else if (peer->default_transport == AST_TRANSPORT_TCP) { + astman_append(session, "tcp"); + } else if (peer->default_transport == AST_TRANSPORT_TLS) { + astman_append(session, "tls"); + } + + astman_append(session, "\r\n"); + transports = ast_str_alloca(16); + + if (peer->transports & AST_TRANSPORT_UDP) { + ast_str_append(&transports, 0, "%s", "udp"); + } + + if (peer->transports & AST_TRANSPORT_TCP) { + ast_str_append(&transports, 0, "%s%s", ast_str_strlen(transports) ? "," : "", "tcp"); + } + + if (peer->transports & AST_TRANSPORT_TLS) { + ast_str_append(&transports, 0, "%s%s", ast_str_strlen(transports) ? "," : "", "tls"); + } + + astman_append(session, "Transports: %s\r\n", ast_str_buffer(transports)); + + format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + astman_append(session, "Codecs: %s\r\n", ast_format_cap_get_names(peer->format_cap, &format_names)); + astman_append(session, "Status: %s\r\n", sip_peer_status2str(peer)); + astman_append(session, "UserAgent: %s\r\n", peer->useragent); + astman_append(session, "RegisterContact: %s\r\n", peer->contact); + astman_append(session, "RegisterExpires: %ld\r\n", ast_sched_when(sip_sched_context, peer->register_expires_sched_id)); + astman_append(session, "AllowMethods: %s\r\n", sip_methods2str(peer->allow_methods)); + astman_append(session, "SupportedOptions: %s\r\n", sip_options2str(peer->supported_options)); + astman_append(session, "QualifyExpires: %d\r\n", peer->qualify_expires); + astman_append(session, "QualifyMax: %d\r\n", peer->qualify_max); + astman_append(session, "Parkinglot: %s\r\n", peer->parkinglot); + + if (peer->channel_variables) { + struct ast_variable *variable; + + for (variable = peer->channel_variables; variable; variable = variable->next) { + astman_append(session, "ChanVariable: %s=%s\r\n", variable->name, variable->value); + } + } + + astman_append(session, "Reason: %s\r\n", peer->reason_support ? "yes" : "no"); + astman_append(session, "Diversion: %s\r\n", peer->diversion_support ? "yes" : "no"); + astman_append(session, "Identity: "); + + if (peer->identity_support == SIP_IDENTITY_REMOTE_PARTY) { + astman_append(session, "remote_party"); + } else if (peer->identity_support == SIP_IDENTITY_P_ASSERTED) { + astman_append(session, "p_asserted"); + } else { + astman_append(session, "no"); + } + + if (peer->allow_identity) { + astman_append(session, ",allow"); + } + + if (peer->trust_identity_outgoing) { + astman_append(session, ",trusted"); + } + + astman_append(session, "\r\n"); + astman_append(session, "Path: %s\r\n", peer->path_support ? "yes" : "no"); + astman_append(session, "ICE: %s\r\n", peer->ice_support ? "yes" : "no"); + astman_append(session, "DoNotDisturb: %s\r\n", peer->do_not_disturb ? "yes" : "no"); + astman_append(session, "CallForward: %s\r\n", peer->call_forward); + astman_append(session, "HuntGroup: %s\r\n", peer->hunt_group ? "yes" : "no"); + astman_append(session, "BusyWhenDND: %s\r\n", peer->busy_when_dnd ? "yes" : "no"); + astman_append(session, "CiscoMode: %s\r\n", peer->cisco_mode ? "yes" : "no"); + + if (peer->cisco_mode) { + struct ast_str *pickup_notify; + int count; + + astman_append(session, "KeepConference: %s\r\n", peer->keep_conference ? "yes" : "no"); + astman_append(session, "MultiAdminConference: %s\r\n", peer->keep_conference ? "yes" : "no"); + + pickup_notify = ast_str_alloca(16); + + if (peer->pickup_notify & SIP_PICKUP_NOTIFY_FROM) { + ast_str_append(&pickup_notify, 0, "%s", "from"); + } + + if (peer->pickup_notify & SIP_PICKUP_NOTIFY_TO) { + ast_str_append(&pickup_notify, 0, "%s%s", ast_str_strlen(pickup_notify) ? "," : "", "to"); + } + + if (peer->pickup_notify & SIP_PICKUP_NOTIFY_BEEP) { + ast_str_append(&pickup_notify, 0, "%s%s", ast_str_strlen(pickup_notify) ? "," : "", "beep"); + } + + if (!ast_str_strlen(pickup_notify)) { + ast_str_set(&pickup_notify, 0, "none"); + } + + astman_append(session, "PickupNotify: %s\r\n", ast_str_buffer(pickup_notify)); + astman_append(session, "PickupNotifyTimer: %d\r\n", peer->pickup_notify_timer); + astman_append(session, "ParkNotifyTimer: %d\r\n", peer->park_notify_timer); + astman_append(session, "DeviceName: %s\r\n", peer->device_name); + astman_append(session, "ActiveLoad: %s\r\n", peer->active_load); + astman_append(session, "InactiveLoad: %s\r\n", peer->inactive_load); + astman_append(session, "LineIndex: %d\r\n", peer->line_index); + + count = 1; + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + astman_append(session, "Register%d: %s,%d\r\n", count++, alias->name, alias->line_index); + } + + count = 1; + + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, next) { + astman_append(session, "Subscribe%d: %s@%s\r\n", count++, subscription->exten, subscription->context); + } + + astman_append(session, "QRTURL: %s\r\n", peer->qrt_url); + astman_append(session, "RTPRxStat: %s\r\n", peer->rtp_rx_stat); + astman_append(session, "RTPTxStat: %s\r\n", peer->rtp_tx_stat); + } + + astman_append(session, "\r\n"); + ao2_ref(peer, -1); + return 0; +} + +/* Show SIP peers in the manager API */ +int sip_manager_peer_status(struct mansession *session, const struct message *message) +{ + const char *peer_name, *status, *action_id; + struct sip_peer *peer; + int count; + + peer_name = astman_get_header(message, "Peer"); + + if (!ast_strlen_zero(peer_name)) { + if (!(peer = sip_peer_find(peer_name, TRUE, FALSE))) { + astman_send_error(session, message, "No such peer"); + return 0; + } + } else { + peer = NULL; + } + + action_id = astman_get_header(message, "ActionID"); + astman_send_listack(session, message, "Peer status list will follow", "start"); + + if (!peer) { + struct ao2_iterator iter = ao2_iterator_init(sip_peers, 0); + + for (count = 0; (peer = ao2_iterator_next(&iter)); count++) { + ao2_lock(peer); + + if (peer->qualify_max) { + if (peer->qualify < 0) { + status = "Unreachable"; + } else if (peer->qualify > peer->qualify_max) { + status = "Lagged"; + } else if (peer->qualify) { + status = "Reachable"; + } else { + status = "Unknown"; + } + } else { + status = "Unmonitored"; + } + + astman_append(session, "Event: SIPPeerStatus\r\n"); + + if (!ast_strlen_zero(action_id)) { + astman_append(session, "ActionID: %s\r\n", action_id); + } + + astman_append(session, + "Name: %s\r\n" + "Status: %s\r\n" + "Qualify: %d\r\n" + "\r\n", + peer->name, status, peer->qualify); + + ao2_unlock(peer); + ao2_ref(peer, -1); + } + + ao2_iterator_destroy(&iter); + } else { + ao2_lock(peer); + + if (peer->qualify_max) { + if (peer->qualify < 0) { + status = "Unreachable"; + } else if (peer->qualify > peer->qualify_max) { + status = "Lagged"; + } else if (peer->qualify) { + status = "Reachable"; + } else { + status = "Unknown"; + } + } else { + status = "Unmonitored"; + } + + astman_append(session, "Event: SIPPeerStatus\r\n"); + + if (!ast_strlen_zero(action_id)) { + astman_append(session, "ActionID: %s\r\n", action_id); + } + + astman_append(session, + "Name: %s\r\n" + "Status: %s\r\n" + "Qualify: %d\r\n" + "\r\n", + peer->name, status, peer->qualify); + + ao2_unlock(peer); + ao2_ref(peer, -1); + + count = 1; + } + + astman_send_list_complete_start(session, message, "SIPPeerStatusComplete", count); + astman_send_list_complete_end(session); + return 0; +} + +int sip_manager_notify(struct mansession *session, const struct message *message) +{ + const char *peer_name, *call_id; + struct ast_variable *variables, *variable; + struct sip_dialog *dialog; + struct ast_str *content; + + peer_name = astman_get_header(message, "Peer"); + + if (ast_strlen_zero(peer_name)) { + astman_send_error(session, message, "Peer name missing"); + return 0; + } + + call_id = astman_get_header(message, "Call-ID"); + + /* Check if Call-ID header is set */ + if (!ast_strlen_zero(call_id)) { + if (!(dialog = ao2_find(sip_dialogs, call_id, OBJ_SEARCH_KEY))) { + astman_send_error(session, message, "Call-ID not found"); + return 0; + } + + if (dialog->notify_headers) { + ast_variables_destroy(dialog->notify_headers); + dialog->notify_headers = NULL; + } + + if (!ast_strlen_zero(dialog->notify_content)) { + ast_string_field_set(dialog, notify_content, NULL); + } + } else { + if (!(dialog = sip_dialog_alloc(NULL, NULL, SIP_METHOD_NOTIFY, NULL, 0))) { + astman_send_error(session, message, "Dialog error"); + return 0; + } + + if (sip_dialog_build(dialog, peer_name, NULL, TRUE)) { + /* Maybe they're not registered, etc */ + sip_dialog_unlink(dialog); + ao2_ref(dialog, -1); + + astman_send_error(session, message, "Peer error"); + return 0; + } + + /* Notify is outgoing call */ + dialog->outgoing = TRUE; + } + + variables = astman_get_variables_order(message, ORDER_NATURAL); + + dialog->notify_headers = ast_variable_new("Subscription-State", "terminated", ""); + content = ast_str_alloca(4096); + + for (variable = variables; variable; variable = variable->next) { + if (!strcasecmp(variable->name, "Content")) { + if (ast_str_strlen(content)) { + ast_str_append(&content, 0, "\r\n"); + } + + ast_str_append(&content, 0, "%s", variable->value); + } else if (!strcasecmp(variable->name, "Content-Length")) { + continue; + } else { + ast_variable_list_append(&dialog->notify_headers, ast_variable_new(variable->name, variable->value, "")); + } + } + + ast_string_field_set(dialog, notify_content, ast_str_buffer(content)); + + sip_dialog_sched_destroy(dialog, dialog->timer_b); + sip_request_send_notify(dialog, SIP_INIT_REQUEST); + ao2_ref(dialog, -1); + + astman_send_ack(session, message, "Notify sent"); + ast_variables_destroy(variables); + return 0; +} + +/* Qualify SIP peers in the manager API */ +int sip_manager_qualify_peer(struct mansession *session, const struct message *message) +{ + const char *peer_name; + struct sip_peer *peer; + + peer_name = astman_get_header(message, "Peer"); + + if (ast_strlen_zero(peer_name)) { + astman_send_error(session, message, "Peer name missing"); + return 0; + } + + if ((peer = sip_peer_find(peer_name, FALSE, FALSE))) { + const char *action_id = astman_get_header(message, "ActionID"); + + astman_send_ack(session, message, "SIP peer found, will qualify"); + + sip_peer_send_qualify(peer, TRUE); + sip_publish_qualify_peer(peer_name, action_id); + + ao2_ref(peer, -1); + } else { + astman_send_error(session, message, "Peer not found"); + } + + return 0; +} + +void sip_publish_qualify_peer(const char *peer, const char *action_id) +{ + RAII_VAR(struct ast_json *, body, NULL, ast_json_unref); + + if (ast_strlen_zero(action_id)) { + body = ast_json_pack("{s: s}", "Peer", peer); + } else { + body = ast_json_pack("{s: s, s: s}", "Peer", peer, "ActionID", action_id); + } + + if (!body) { + return; + } + + ast_manager_publish_event("SIPQualifyPeerDone", EVENT_FLAG_CALL, body); +} + +struct ast_manager_event_blob *sip_session_timeout_to_ami(struct stasis_message *message) +{ + RAII_VAR(struct ast_str *, state, NULL, ast_free_ptr); + struct ast_channel_blob *chan_blob; + const char *source; + + chan_blob = stasis_message_data(message); + source = ast_json_string_get(ast_json_object_get(chan_blob->blob, "source")); + + if (!(state = ast_manager_build_channel_state_string(chan_blob->snapshot))) { + return NULL; + } + + return ast_manager_event_blob_create(EVENT_FLAG_CALL, "SessionTimeout", "%sSource: %s\r\n", ast_str_buffer(state), source); +} + +/* Sends a session timeout channel blob used to produce SessionTimeout AMI messages */ +void sip_publish_session_timeout(struct ast_channel *channel, const char *source) +{ + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + + if (!(blob = ast_json_pack("{s: s}", "source", source))) { + return; + } + + ast_channel_publish_blob(channel, sip_session_timeout_type(), blob); +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/message.c asterisk-22.6.0/channels/sip/message.c --- asterisk-22.6.0.orig/channels/sip/message.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/message.c 2025-10-21 18:38:17.454218039 +1300 @@ -0,0 +1,1738 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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 + extended + ***/ + +#include "asterisk.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/threadstorage.h" +#include "asterisk/utils.h" +#include "asterisk/channel.h" +#include "asterisk/callerid.h" +#include "asterisk/indications.h" +#include "asterisk/rtp_engine.h" +#include "asterisk/pbx.h" +#include "asterisk/message.h" +#include "asterisk/dnsmgr.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/sdp.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/fax.h" + +enum sip_message_tag_cmp_result { + SIP_MESSAGE_FORKED = -3, /* An outgoing request has been forked as result of receiving two differing + * 200 OK responses */ + SIP_MESSAGE_LOOP_DETECTED = -2, /* Multiple incoming requests with same call-id but different branch parameters + * have been detected */ + SIP_MESSAGE_NO_MATCH = -1, /* From/to tags or branch did not match */ + SIP_MESSAGE_MATCH = 0, /* From/to tags and branch did match */ +}; + +static void sip_message_strip_headers(char *data); +static int sip_message_needs_contact(struct sip_message *response); +static int sip_message_tag_cmp(struct sip_message *message, struct sip_dialog *dialog, int has_authorization); + +AST_THREADSTORAGE(sip_message_get_content_buf); + +/* Parse multiline SIP headers into one header */ +static void sip_message_strip_headers(char *data) +{ + int from_len, to_len, skip_blanks, want_boundary, done; + + skip_blanks = FALSE; + want_boundary = FALSE; + done = FALSE; + + from_len = 0; + to_len = 0; + + while (data[from_len] != '\0') { + /* Eliminate all CRs */ + if (data[from_len] == '\r') { + from_len++; + continue; + } + + /* Check for end-of-line */ + if (data[from_len] == '\n') { + if (want_boundary) { + /* Found the header boundary, all further compressing will be skipped */ + done = TRUE; + } else { + want_boundary = TRUE; + } + + /* Check for end-of-message */ + if (data[from_len + 1] == '\0') { + break; + } + + /* Check for a continuation line */ + if (!done && (data[from_len + 1] == ' ' || data[from_len + 1] == '\t')) { + /* Merge continuation line */ + from_len++; + continue; + } + + /* Propagate LF and start new line */ + data[to_len++] = data[from_len++]; + skip_blanks = FALSE; + continue; + } + + if (!done && (data[from_len] == ' ' || data[from_len] == '\t')) { + if (skip_blanks) { + from_len++; + continue; + } + + data[to_len++] = data[from_len++]; + skip_blanks = TRUE; + continue; + } + + data[to_len++] = data[from_len++]; + + if (!done) { + skip_blanks = FALSE; + want_boundary = FALSE; + } + } + + data[to_len] = '\0'; +} + +/* Parse a SIP message */ +int sip_message_parse(struct sip_message *message, char *data) +{ + char *line, *from, *to; + const char *cseq, *call_id; + int cseq_len, headers; + + sip_message_strip_headers(data); /* Fix multiline headers */ + + if (!strncmp(data, "SIP/2.0 ", 8)) { /* We have a response */ + char *status_line; + + message->response = TRUE; + data += 8; + + status_line = ast_skip_blanks(strsep(&data, "\n")); + ast_trim_blanks(status_line); + + ast_debug(3, "Response status line: %s\n", status_line); + + if (sscanf(status_line, "%30d", &message->code) != 1) { + ast_debug(1, "Invalid response code '%s'\n", status_line); + } + + message->status_line = ast_strdup(status_line); + } else { /* We have a request */ + char *method, *uri, *protocol; + + method = strsep(&data, " "); + uri = ast_skip_blanks(strsep(&data, " ")); + + if (*uri == '<') { /* The spec says it must not be in <> ! */ + ast_debug(3, "Invalid message URI '%s'\n", uri); + } + + protocol = ast_skip_blanks(strsep(&data, "\n")); + ast_trim_blanks(protocol); + + if (strcasecmp(protocol, "SIP/2.0")) { + ast_debug(3, "Invalid message protocol '%s'\n", protocol); + return -1; + } + + ast_debug(3, "Request method: %s, uri: %s, protocol: %s\n", method, uri, protocol); + + if (!(message->method = sip_find_method(method))) { + ast_debug(1, "Invalid method '%s'\n", method); + /* Keep parsing so we can send a 501 Not Implemented response */ + } + + message->uri = ast_strdup(uri); + } + + headers = TRUE; + + while ((line = strsep(&data, "\n"))) { + ast_trim_blanks(line); /* Remove \r if any */ + + if (ast_strlen_zero(line) && headers) { + headers = FALSE; + } else if (headers) { + char *name, *value; + + if (message->header_count == ARRAY_LEN(message->headers)) { + ast_debug(1, "Too many headers, skipping '%s'\n", line); + break; + } + + name = line; + + if (!(value = strchr(name, ':'))) { + ast_debug(1, "Invalid header '%s', missing ':'\n", data); + continue; + } + + *value++ = '\0'; + value = ast_skip_blanks(value); + + ast_trim_blanks(name); + ast_trim_blanks(value); + + ast_debug(3, "Header %d: %s: %s\n", message->header_count, name, value); + + message->headers[message->header_count].name = ast_strdup(name); + message->headers[message->header_count].value = ast_strdup(value); + + message->header_count++; + } else { + if (message->content_count == ARRAY_LEN(message->content)) { + ast_debug(1, "Too many lines, skipping '%s'\n", line); + break; + } + + ast_debug(3, "Line %d: %s\n", message->content_count, line); + + message->content[message->content_count] = ast_strdup(line); + message->content_count++; + } + } + + /* RFC 3261 - 8.1.1 A valid SIP request must contain To, From, CSeq, Call-ID and Via. 8.2.6.2 Response must + * have To, From, Call-ID, CSeq, and Via related to the request, so we can check to make sure these fields exist + * for all requests and responses */ + call_id = sip_message_find_header(message, "Call-ID"); + + if (!ast_strlen_zero(call_id)) { + message->call_id = ast_strdup(call_id); + } else { + ast_debug(1, "Missing 'Call-ID:' header\n"); + } + + from = ast_strdupa(sip_message_find_header(message, "From")); + + if (!ast_strlen_zero(from)) { + message->from_tag = ast_strdup(sip_parse_tag(from)); + } else { + ast_debug(1, "Missing 'From:' header\n"); + } + + to = ast_strdupa(sip_message_find_header(message, "To")); + + if (!ast_strlen_zero(to)) { + message->to_tag = ast_strdup(sip_parse_tag(to)); + } else { + ast_log(LOG_WARNING, "Missing 'To:' header\n"); + } + + cseq = sip_message_find_header(message, "CSeq"); + + if (!ast_strlen_zero(cseq)) { + if (sscanf(cseq, "%30u %n", &message->cseq, &cseq_len) != 1) { + ast_debug(1, "Invalid CSeq: header '%s'\n", cseq); + } + } else { + ast_debug(1, "Missing 'CSeq:' header\n"); + } + + /* If this is a response find the method in CSeq */ + if (message->response) { + if (!(message->method = sip_find_method(cseq + cseq_len))) { + ast_debug(1, "Invalid CSeq method '%s'\n", cseq + cseq_len); + } + } + + sip_parse_via(message); + return 0; +} + +/* Free a SIP message */ +void sip_message_destroy(struct sip_message *message) +{ + int i; + + ast_free((char *) message->uri); + ast_free((char *) message->status_line); + ast_free((char *) message->call_id); + ast_free((char *) message->from_tag); + ast_free((char *) message->to_tag); + + for (i = 0; i < message->header_count; i++) { + ast_free((char *) message->headers[i].name); + ast_free((char *) message->headers[i].value); + } + + for (i = 0; i < message->content_count; i++) { + ast_free((char *) message->content[i]); + } + + ast_free((char *) message->via_sent_by); + ast_free((char *) message->via_branch); + ast_free((char *) message->via_maddr); +} + +/* Send SIP Request to the other part of the dialog */ +int sip_message_send(struct sip_dialog *dialog, struct sip_message *message, int reliable, uint32_t cseq) +{ + int i, res, content_len; + struct ast_str *data; + + if (!(data = ast_str_create(4096))) { + return -1; + } + + if (message->response) { + ast_str_append(&data, 0, "SIP/2.0 %s\r\n", message->status_line); + } else { + ast_str_append(&data, 0, "%s %s SIP/2.0\r\n", sip_method2str(message->method), message->uri); + } + + for (i = 0; i < message->header_count; i++) { + ast_str_append(&data, 0, "%s: %s\r\n", message->headers[i].name, message->headers[i].value); + } + + content_len = 0; + + for (i = 0; i < message->content_count; i++) { + content_len += strlen(message->content[i]) + 2; + } + + /* Content-Length header is mandatory for stream (eg TCP) transports */ + ast_str_append(&data, 0, "Content-Length: %d\r\n", content_len); + ast_str_append(&data, 0, "\r\n"); + + for (i = 0; i < message->content_count; i++) { + ast_str_append(&data, 0, "%s\r\n", message->content[i]); + } + + if (message->response) { + /* If we are sending a final response to an INVITE, stop resendmitting provisional responses */ + if (dialog->initial_request.method == SIP_METHOD_INVITE && reliable == SIP_SEND_CRITICAL) { + sip_dialog_cancel_provisional_keepalive(dialog); + } + } else { + /* If we have an outbound proxy, reset peer address. Only do this once */ + if (dialog->proxy) { + ast_sockaddr_copy(&dialog->address, &dialog->proxy->address); + } + + dialog->method = message->method; + } + + if (reliable) { + /* 'message->code' indicates that this is a response, it must be 0 for requests */ + res = sip_packet_send_reliable(dialog, message->method, cseq, message->code, data, + reliable == SIP_SEND_CRITICAL); + } else { + res = sip_packet_send(dialog, data); + } + + ast_free(data); + sip_message_destroy(message); + return res; +} + +/* Copy SIP message (mostly used to save requests for responses) */ +void sip_message_copy(struct sip_message *to_message, const struct sip_message *from_message) +{ + int i; + + sip_message_destroy(to_message); + + to_message->method = from_message->method; + to_message->uri = ast_strdup(from_message->uri); + to_message->code = from_message->code; + to_message->response = from_message->response; + + to_message->call_id = ast_strdup(from_message->call_id); + to_message->from_tag = ast_strdup(from_message->from_tag); + to_message->to_tag = ast_strdup(from_message->to_tag); + + to_message->cseq = from_message->cseq; + to_message->code = from_message->code; + to_message->status_line = ast_strdup(from_message->status_line); + + to_message->header_count = 0; + + for (i = 0; i < from_message->header_count; i++) { + to_message->headers[i].name = ast_strdup(from_message->headers[i].name); + to_message->headers[i].value = ast_strdup(from_message->headers[i].value); + to_message->header_count++; + } + + to_message->content_count = 0; + + for (i = 0; i < from_message->content_count; i++) { + to_message->content[i] = ast_strdup(from_message->content[i]); + to_message->content_count++; + } + + to_message->via_sent_by = ast_strdup(from_message->via_sent_by); + to_message->via_branch = ast_strdup(from_message->via_branch); + to_message->via_maddr = ast_strdup(from_message->via_maddr); + to_message->via_ttl = from_message->via_ttl; + to_message->via_rport = from_message->via_rport; + + to_message->sdp_start = from_message->sdp_start; + to_message->sdp_end = from_message->sdp_end; +} + +const char *sip_message_next_header(const struct sip_message *message, const char *name, int *iter) +{ + int i; + + for (i = *iter; i < message->header_count; i++) { + if (!strcasecmp(message->headers[i].name, name)) { + *iter = i + 1; + + return message->headers[i].value; + } + } + + /* Don't return NULL, so sip_message_find_header is always a valid pointer */ + return ""; +} + +/* Get header from SIP message */ +const char *sip_message_find_header(const struct sip_message *message, const char *name) +{ + int iter = 0; + + return sip_message_next_header(message, name, &iter); +} + +/* Add header to SIP message */ +void sip_message_add_header(struct sip_message *message, const char *name, const char *value) +{ + if (message->header_count == ARRAY_LEN(message->headers)) { + ast_log(LOG_WARNING, "Unable to add header '%s: %s', too many headers\n", name, value); + return; + } + + message->headers[message->header_count].name = ast_strdup(name); + message->headers[message->header_count].value = ast_strdup(value); + + message->header_count++; +} + +void sip_message_build_header(struct sip_message *message, const char *name, const char *format, ...) +{ + va_list args; + char value[2048]; + + va_start(args, format); + vsnprintf(value, sizeof(value), format, args); + va_end(args); + + sip_message_add_header(message, name, value); +} + +/* Copy one header field from one message to another */ +void sip_message_copy_header(struct sip_message *to_message, const struct sip_message *from_message, const char *name) +{ + const char *value = sip_message_find_header(from_message, name); + + if (!ast_strlen_zero(value)) { + sip_message_add_header(to_message, name, value); + } else { + ast_debug(1, "No header '%s' present to copy\n", name); + } +} + +/* Get message body content */ +char *sip_message_get_content(struct sip_message *message, int start, int end) +{ + struct ast_str *content; + int i; + + if (!(content = ast_str_thread_get(&sip_message_get_content_buf, 2048))) { + return NULL; + } + + ast_str_reset(content); + + for (i = start; i < message->content_count && i <= end; i++) { + if (ast_str_append(&content, 0, "%s\n", message->content[i]) < 0) { + return NULL; + } + } + + return ast_str_buffer(content); +} + +/* Add content (not header) to SIP message */ +void sip_message_add_content(struct sip_message *message, const char *content) +{ + if (message->content_count == ARRAY_LEN(message->content)) { + ast_log(LOG_WARNING, "Unable to add content '%s', too many lines\n", content); + return; + } + + /* Lines need to be trimmed because sip_message_send will re-add them */ + message->content[message->content_count] = ast_trim_blanks(ast_strdup(content)); + message->content_count++; +} + +void sip_message_build_content(struct sip_message *message, const char *format, ...) +{ + va_list args; + char content[4096]; + + va_start(args, format); + vsnprintf(content, sizeof(content), format, args); + va_end(args); + + sip_message_add_content(message, content); +} + +/* Find the content boundary */ +int sip_message_find_boundary(struct sip_message *message, const char *boundary, int start, int *done) +{ + int i, boundary_len; + + *done = FALSE; + boundary_len = strlen(boundary); + + for (i = start; i < message->content_count; i++) { + /* Not the start of a boundary */ + if (strncmp(message->content[i], "--", 2)) { + continue; + } + + if (!strncmp(boundary, message->content[i] + 2, boundary_len)) { + if (!strcmp(message->content[i] + 2 + boundary_len, "--")) { + *done = TRUE; /* Last boundary marker */ + } else if (strcmp(message->content[i] + 2 + boundary_len, "")) { + continue; + } + + return i; + } + } + + return -1; +} + +/* Get the content type from either the Content-Type header or the Content-Type of the first part */ +const char *sip_message_find_content_type(struct sip_message *message) +{ + char *content_type, *boundary; + int start, i, done; + + content_type = ast_strdupa(sip_message_find_header(message, "Content-Type")); + + if (ast_strlen_zero(content_type)) { + return ""; + } + + content_type = sip_parse_content_type(content_type, &boundary); + + if (strcmp(content_type, "multipart/mixed") || ast_strlen_zero(boundary)) { + return content_type; + } + + if ((start = sip_message_find_boundary(message, boundary, 0, &done)) == -1) { + return ""; + } + + for (i = start + 1; i < message->content_count; i++) { + if (!strncasecmp(message->content[i], "Content-Type:", 13)) { + return ast_skip_blanks(message->content[i] + 13); + } else if (ast_strlen_zero(message->content[i])) { + break; + } + } + + return ""; +} + +/* Add date header to SIP message */ +void sip_message_add_date(struct sip_message *message) +{ + char date[512]; + struct tm tm; + time_t now; + + now = time(NULL); + gmtime_r(&now, &tm); + + strftime(date, sizeof(date), "%a, %d %b %Y %T GMT", &tm); + sip_message_add_header(message, "Date", date); +} + +/* Add Authorization or Proxy-Authorization to SIP message */ +void sip_message_add_authorization(struct sip_message *message, struct sip_dialog *dialog) +{ + if (!dialog->authorization_code || ast_strlen_zero(dialog->authorization)) { + return; + } + + sip_message_add_header(message, + dialog->authorization_code == 401 ? "Authorization" : "Proxy-Authorization", dialog->authorization); +} + +/* Add Remote-Party-ID or P-Asserted-Identity header to SIP message */ +void sip_message_add_identity(struct sip_message *message, struct sip_dialog *dialog) +{ + char escaped_name[256], encoded_user[256]; + int connected_presentation, connected_source; + const char *domain, *connected_number, *connected_name, *callback_number; + struct ast_party_id connected_line; + + if (!dialog->channel || !dialog->peer->identity_support) { + return; + } + + connected_line = ast_channel_connected_effective_id(dialog->channel); + connected_source = ast_channel_connected(dialog->channel)->source; + + connected_number = S_COR(connected_line.number.valid, connected_line.number.str, NULL); + connected_name = S_COR(connected_line.name.valid, connected_line.name.str, NULL); + + if (!connected_number && !connected_name) { + return; + } + + connected_presentation = ast_party_id_presentation(&connected_line); + + if ((connected_presentation & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED && + !dialog->peer->trust_identity_outgoing) { + /* If presentation is not allowed and we don't trust the peer, we don't apply a header */ + ast_debug(3, "Not adding identity due to restricted presentation\n"); + return; + } + + domain = dialog->from_domain; + + if (ast_strlen_zero(domain) || + (dialog->peer->trust_identity_outgoing && !strcmp("anonymous.invalid", domain))) { + /* If the fromdomain is NULL or if it was set to anonymous.invalid due to privacy settings and we trust + * the peer, use the host IP address */ + domain = ast_sockaddr_stringify_host_remote(&dialog->our_address); + } + + if (dialog->peer->cisco_mode) { + if (!strcmp(connected_name, "Conference") && + connected_source == AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE) { + /* Phone will translate \2004 into the localised version of Conference and enable + * ConfList/ConfDetails support */ + connected_name = "\2004"; + } else if (!strcmp(connected_name, "Park") && + connected_source == AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL) { + connected_name = "\2005"; + } + } + + if (!ast_strlen_zero(connected_name)) { + ast_escape_quoted(connected_name, escaped_name, sizeof(escaped_name)); + } else { + escaped_name[0] = '\0'; + } + + if (!ast_strlen_zero(connected_number)) { + ast_uri_encode(connected_number, encoded_user, sizeof(encoded_user), ast_uri_sip_user); + } else { + encoded_user[0] = '\0'; + } + + if (dialog->peer->identity_support == SIP_IDENTITY_REMOTE_PARTY) { + struct ast_str *remote_party_id = ast_str_alloca(512); + + if (!ast_strlen_zero(escaped_name)) { + ast_str_append(&remote_party_id, 0, "\"%s\" ", escaped_name); + } + + ast_str_append(&remote_party_id, 0, "channel, "CISCO_CALLBACK_NUMBER"); + + if (!ast_strlen_zero(callback_number)) { + ast_uri_encode(callback_number, encoded_user, sizeof(encoded_user), ast_uri_sip_user); + ast_str_append(&remote_party_id, 0, ";x-cisco-callback-number=%s", encoded_user); + } + + ast_str_append(&remote_party_id, 0, ">;party=%s;privacy=%s;screen=%s", + dialog->originated_call ? "calling" : "called", + (connected_presentation & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED ? "full" : "off", + (connected_presentation & AST_PRES_USER_NUMBER_PASSED_SCREEN) ? "yes" : "no"); + + sip_message_add_header(message, "Remote-Party-ID", ast_str_buffer(remote_party_id)); + } else if (dialog->peer->identity_support == SIP_IDENTITY_P_ASSERTED) { + struct ast_str *p_asserted_identity = ast_str_alloca(512); + + if (!ast_strlen_zero(escaped_name)) { + ast_str_append(&p_asserted_identity, 0, "\"%s\" ", escaped_name); + } + + ast_str_append(&p_asserted_identity, 0, "", + encoded_user, !ast_strlen_zero(encoded_user) ? "@" : "", domain); + + sip_message_add_header(message, "P-Asserted-Identity", ast_str_buffer(p_asserted_identity)); + + if ((connected_presentation & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + sip_message_add_header(message, "Privacy", "id"); + } + } +} + +void sip_message_add_call_info(struct sip_message *message, struct sip_dialog *dialog) +{ + struct ast_str *call_info = ast_str_alloca(512); + + if (!dialog->peer->cisco_mode) { + return; + } + + ast_str_set(&call_info, 0, ";orientation=%s;security=", + dialog->originated_call ? "from" : "to"); + + if (dialog->socket.transport & AST_TRANSPORT_TLS) { + if (dialog->secure_audio_rtp) { + ast_str_append(&call_info, 0, "Encrypted"); + } else { + ast_str_append(&call_info, 0, "Authenticated"); + } + } else { + ast_str_append(&call_info, 0, "NotAuthenticated"); + } + + if (dialog->outgoing && dialog->channel) { + const char *huntpilot = pbx_builtin_getvar_helper(dialog->channel, "CISCO_HUNTPILOT"); + + /* A huntpiloturi parameter tells the phone that the call was to a huntgroup, this is needed because the + * phones no longer support Answered-Elsewhere so this prevents missed call indications */ + if (!ast_strlen_zero(huntpilot)) { + char *name, *number, callerid[128], encoded_user[128]; + + ast_copy_string(callerid, huntpilot, sizeof(callerid)); + + if (!ast_callerid_parse(callerid, &name, &number)) { + ast_str_append(&call_info, 0, ";huntpiloturi=\""); + + if (!ast_strlen_zero(name)) { + char encoded_name[128]; + + ast_uri_encode(name, encoded_name, sizeof(encoded_name), ast_uri_sip_user); + ast_str_append(&call_info, 0, "%%%02X%s%%%02X ", + (unsigned char) '"', encoded_name, (unsigned char) '"'); + } + + ast_uri_encode(number, encoded_user, sizeof(encoded_user), ast_uri_sip_user); + ast_str_append(&call_info, 0, "\"", + encoded_user, ast_sockaddr_stringify_host_remote(&dialog->our_address)); + } + } + } + + sip_message_add_header(message, "Call-Info", ast_str_buffer(call_info)); +} + +/* Add "Supported" header to sip message. Since some options may be disabled in the config, the sip_dialog must be + * inspected to determine what is supported for this dialog */ +void sip_message_add_supported(struct sip_message *message, struct sip_dialog *dialog) +{ + struct ast_str *supported = ast_str_alloca(512); + + ast_str_append(&supported, 0, "replaces"); + + if (dialog->peer) { + if (dialog->peer->session_timer_mode != SIP_SESSION_TIMER_MODE_REFUSE) { + ast_str_append(&supported, 0, ",timer"); + } + + if (dialog->peer->path_support) { + ast_str_append(&supported, 0, ",path"); + } + + if (dialog->peer->cisco_mode) { + ast_str_append(&supported, 0, ",X-cisco-sis-10.0.0"); + } + } + + sip_message_add_header(message, "Supported", ast_str_buffer(supported)); +} + +/* Add "Diversion" header to outgoing message. We need to add a Diversion header if the owner channel of this dialog has + * redirecting information associated with it */ +void sip_message_add_diversion(struct sip_message *message, struct sip_dialog *dialog) +{ + struct ast_party_id redirecting_from; + const char *reason, *quote; + struct ast_str *diversion; + char encoded_user[128]; + int i; + + /* We skip this entirely if the configuration doesn't allow diversion headers */ + if (!dialog->channel || !dialog->peer->diversion_support) { + return; + } + + redirecting_from = ast_channel_redirecting_effective_from(dialog->channel); + + if (!redirecting_from.number.valid || ast_strlen_zero(redirecting_from.number.str)) { + return; + } + + diversion = ast_str_alloca(512); + + ast_uri_encode(redirecting_from.number.str, encoded_user, sizeof(encoded_user), ast_uri_sip_user); + + if (redirecting_from.name.valid && !ast_strlen_zero(redirecting_from.name.str)) { + char escaped_name[128]; + + ast_escape_quoted(redirecting_from.name.str, escaped_name, sizeof(escaped_name)); + ast_str_append(&diversion, 0, "\"%s\" ", escaped_name); + } + + reason = sip_reason2str(&ast_channel_redirecting(dialog->channel)->reason); + /* Reason is either already quoted or it is a token to not need quotes added */ + quote = ""; + + for (i = 0; reason[i]; i++) { + if (!isalnum(reason[i]) || reason[i] != '-') { + quote = "\""; + break; + } + } + + /* We at least have a number to place in the Diversion header, which is enough */ + ast_str_append(&diversion, 0, ";reason=%s%s%s", + encoded_user, ast_sockaddr_stringify_host_remote(&dialog->our_address), quote, reason, quote); + + if (dialog->peer->cisco_mode) { + int presentation; + const char *privacy, *screen; + + presentation = ast_party_id_presentation(&redirecting_from); + + if ((presentation & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + privacy = "full"; + } else { + privacy = "off"; + } + + if (presentation & AST_PRES_USER_NUMBER_PASSED_SCREEN) { + screen = "yes"; + } else { + screen = "no"; + } + + ast_str_append(&diversion, 0, ";privacy=%s;screen=%s", privacy, screen); + } + + sip_message_add_header(message, "Diversion", ast_str_buffer(diversion)); +} + +void sip_message_add_join(struct sip_message *message, struct sip_dialog *dialog) +{ + if (!dialog->sdp_relay_nearend && !dialog->sdp_relay_farend) { + return; + } + + if (ast_strlen_zero(dialog->join_call_id) || + ast_strlen_zero(dialog->join_local_tag) || ast_strlen_zero(dialog->join_remote_tag)) { + return; + } + + sip_message_build_header(message, "Join", "%s;from-tag=%s;to-tag=%s", + dialog->join_call_id, dialog->join_local_tag, dialog->join_remote_tag); +} + +void sip_message_add_via(struct sip_message *message, struct sip_dialog *dialog) +{ + sip_message_build_header(message, "Via", "SIP/2.0/%s %s;branch=%s", + ast_transport2str(dialog->socket.transport), ast_sockaddr_stringify_remote(&dialog->our_address), + dialog->branch); +} + +/* Add route header into message per learned route */ +void sip_message_add_route(struct sip_message *message, struct sip_route *route, int skip) +{ + struct ast_str *path; + + if (sip_route_empty(route)) { + return; + } + + if ((path = sip_route_list(route, skip))) { + if (ast_str_strlen(path)) { + sip_message_add_header(message, "Route", ast_str_buffer(path)); + } + + ast_free(path); + } +} + +/* Test if this response needs a contact header */ +static int sip_message_needs_contact(struct sip_message *response) +{ + /* Requirements for Contact header inclusion in responses generated from the header tables found in the + * following RFCs. Where the Contact header was marked mandatory (m) or optional (o) */ + switch (response->method) { + /* 1xx, 2xx, 3xx, 485 */ + case SIP_METHOD_INVITE: + case SIP_METHOD_UPDATE: + case SIP_METHOD_SUBSCRIBE: + case SIP_METHOD_NOTIFY: + if ((response->code >= 100 && response->code < 400) || response->code == 485) { + return TRUE; + } + + break; + /* 2xx, 3xx, 485 */ + case SIP_METHOD_REGISTER: + case SIP_METHOD_OPTIONS: + if ((response->code >= 200 && response->code < 400) || response->code == 485) { + return TRUE; + } + + break; + /* 3xx, 485 */ + case SIP_METHOD_BYE: + case SIP_METHOD_MESSAGE: + case SIP_METHOD_PUBLISH: + if ((response->code >= 300 && response->code < 400) || response->code == 485) { + return TRUE; + } + + break; + /* 2xx, 3xx, 4xx, 5xx, 6xx */ + case SIP_METHOD_REFER: + if (response->code >= 200 && response->code < 700) { + return TRUE; + } + + break; + /* Contact will not be included for everything else */ + case SIP_METHOD_ACK: + case SIP_METHOD_CANCEL: + case SIP_METHOD_INFO: + default: + break; + } + + return FALSE; +} + +/* Build a new initial SIP request message */ +void sip_message_build_initial_request(struct sip_message *request, struct sip_dialog *dialog, int method, + const char *explicit_uri) +{ + struct ast_str *uri, *from, *to; + char encoded_user[256]; + const char *user, *name, *domain; + int port; + struct ast_party_id connected_id; + + dialog->outgoing_cseq++; + + name = NULL; + user = NULL; + domain = NULL; + + if (ast_strlen_zero(dialog->from_domain)) { + domain = ast_sockaddr_stringify_host_remote(&dialog->our_address); + } + + if (dialog->channel) { + connected_id = ast_channel_connected_effective_id(dialog->channel); + + if ((ast_party_id_presentation(&connected_id) & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) { + if (connected_id.name.valid) { + name = connected_id.name.str; + } + + if (connected_id.number.valid) { + user = connected_id.number.str; + } + } else { + /* Even if we are using RPID, we shouldn't leak information in the From if the user wants + * their callerid restricted */ + name = "Anonymous"; + user = "anonymous"; + domain = "anonymous.invalid"; + } + } + + /* Allow name to be overridden */ + if (!ast_strlen_zero(dialog->from_name)) { + name = dialog->from_name; + } else { /* Save for any further attempts */ + ast_string_field_set(dialog, from_name, name); + } + + /* Allow user to be overridden */ + if (!ast_strlen_zero(dialog->from_user)) { + user = dialog->from_user; + } else { /* Save for any further attempts */ + ast_string_field_set(dialog, from_user, user); + } + + /* Allow domain to be overridden */ + if (!ast_strlen_zero(dialog->from_domain)) { + domain = dialog->from_domain; + } else { /* Save for any further attempts */ + ast_string_field_set(dialog, from_domain, domain); + } + + if (ast_strlen_zero(dialog->to_user)) { + ast_string_field_set(dialog, to_user, user); + } + + uri = ast_str_alloca(512); + + if (!ast_strlen_zero(explicit_uri)) { + ast_str_set(&uri, 0, "%s", explicit_uri); + } else { + /* If we're calling a registered SIP peer, use the contact to dial to the peer */ + if (!ast_strlen_zero(dialog->contact)) { + /* If we have full contact, trust it */ + ast_str_append(&uri, 0, "%s", dialog->contact); + } else { + /* Otherwise, use the username while waiting for registration */ + ast_str_append(&uri, 0, "sip:"); + + if (!ast_strlen_zero(dialog->to_user)) { + ast_uri_encode(dialog->to_user, encoded_user, sizeof(encoded_user), ast_uri_sip_user); + ast_str_append(&uri, 0, "%s@", encoded_user); + } + + ast_str_append(&uri, 0, "%s", dialog->to_host); + + if (dialog->port_in_uri) { + ast_str_append(&uri, 0, ":%d", ast_sockaddr_port(&dialog->address)); + } + + /* If we have only digits, add ;user=phone to the uri */ + if (dialog->peer->user_eq_phone && ast_check_digits(dialog->from_user)) { + ast_str_append(&uri, 0, ";user=phone"); + } + } + } + + /* If custom URI options have been provided, append them */ + if (!ast_strlen_zero(dialog->uri_options)) { + ast_str_append(&uri, 0, ";%s", dialog->uri_options); + } + + /* This is the request URI, which is the next hop of the call which may or may not be the destination of + * the call */ + ast_string_field_set(dialog, uri, ast_str_buffer(uri)); + from = ast_str_alloca(512); + + /* If a caller id name was specified, prefix a display name */ + if (!ast_strlen_zero(name)) { + char escaped_name[256]; + + ast_escape_quoted(name, escaped_name, sizeof(escaped_name)); + ast_str_set(&from, 0, "\"%s\" ", escaped_name); + } + + if (ast_strlen_zero(user)) { + user = ""; + } + + ast_uri_encode(user, encoded_user, sizeof(encoded_user), ast_uri_sip_user); + + if (dialog->from_domain_port && dialog->from_domain_port != SIP_STANDARD_PORT) { + port = dialog->from_domain_port; + } else { + port = ast_sockaddr_port(&dialog->our_address); + } + + if (port != (dialog->socket.transport & AST_TRANSPORT_TLS ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT)) { + ast_str_append(&from, 0, ";tag=%s", encoded_user, + !ast_strlen_zero(encoded_user) ? "@" : "", domain, port, dialog->local_tag); + } else { + ast_str_append(&from, 0, ";tag=%s", encoded_user, + !ast_strlen_zero(encoded_user) ? "@" : "", domain, dialog->local_tag); + } + + to = ast_str_alloca(512); + + if (method == SIP_METHOD_NOTIFY && !ast_strlen_zero(dialog->remote_tag)) { + /* If this is a NOTIFY, use the From: tag in the SUBSCRIBE (RFC 3265) */ + ast_str_set(&to, 0, "<%s%s>;tag=%s", + strncasecmp(dialog->uri, "sip:", 4) ? "sip:" : "", dialog->uri, dialog->remote_tag); + } else { + ast_str_set(&to, 0, "<%s>", dialog->uri); + } + + ast_string_field_build(dialog, branch, "%s%08x", SIP_MAGIC_COOKIE, (unsigned int) ast_random()); + ast_string_field_set(dialog, invite_branch, dialog->branch); + sip_dialog_build_contact(dialog, NULL); + + memset(request, 0, sizeof(*request)); + + request->method = method; + request->uri = ast_strdup(dialog->uri); + + sip_message_add_via(request, dialog); + + sip_message_add_header(request, "From", ast_str_buffer(from)); + sip_message_add_header(request, "To", ast_str_buffer(to)); + sip_message_add_header(request, "Contact", dialog->our_contact); + + sip_message_add_header(request, "Call-ID", dialog->call_id); + sip_message_build_header(request, "CSeq", "%u %s", dialog->outgoing_cseq, sip_method2str(method)); + sip_message_build_header(request, "Max-Forwards", "%d", dialog->max_forwards); + + /* This will be a no-op most of the time. However, under certain circumstances, NOTIFY messages will use this + * function for preparing the request and should have Route headers present */ + sip_message_add_route(request, &dialog->route, 0); + + if (!ast_strlen_zero(sip_config.useragent)) { + sip_message_add_header(request, "User-Agent", sip_config.useragent); + } +} + +/* Build a SIP request message (not the initial one in a dialog) */ +int sip_message_build_request(struct sip_message *request, struct sip_dialog *dialog, int method, uint32_t cseq, + int new_branch) +{ + const char *uri, *to, *from; + struct ast_str *new_to; + int strict; + + if (!cseq) { + dialog->outgoing_cseq++; + cseq = dialog->outgoing_cseq; + } + + /* A CANCEL must have the same branch as the INVITE that it is canceling */ + if (method == SIP_METHOD_CANCEL) { + ast_string_field_set(dialog, branch, dialog->invite_branch); + } else if (new_branch && method == SIP_METHOD_INVITE) { + ast_string_field_build(dialog, branch, "%s%08x", SIP_MAGIC_COOKIE, (unsigned int) ast_random()); + ast_string_field_set(dialog, invite_branch, dialog->branch); + } else if (new_branch) { + ast_string_field_build(dialog, branch, "%s%08x", SIP_MAGIC_COOKIE, (unsigned int) ast_random()); + } + + /* Check for strict or loose router */ + if ((strict = sip_route_is_strict(&dialog->route))) { + ast_debug(1, "Strict routing enforced for call %s'\n", dialog->call_id); + } + + if (method == SIP_METHOD_CANCEL) { + uri = dialog->initial_request.uri; /* Use original URI */ + } else if (method == SIP_METHOD_ACK) { + /* Use URI from Contact: in 200 OK (if INVITE) (we only have the contact uri on INVITEs) */ + if (!ast_strlen_zero(dialog->contact)) { + uri = strict ? sip_route_first_uri(&dialog->route) : dialog->contact; + } else { + uri = dialog->initial_request.uri; + } + } else if (!ast_strlen_zero(dialog->contact)) { + /* Use for BYE or REINVITE */ + uri = strict ? sip_route_first_uri(&dialog->route) : dialog->contact; + } else if (!ast_strlen_zero(dialog->uri)) { + uri = dialog->uri; + } else { + char *contact = ast_strdupa(sip_message_find_header(&dialog->initial_request, + dialog->outgoing ? "To" : "From")); + + /* We have no URI, use To: or From: header as URI (depending on direction) */ + if (!ast_strlen_zero(contact = sip_get_uri(contact))) { + contact = strsep(&contact, ";"); + } + + uri = contact; + } + + memset(request, 0, sizeof(*request)); + + request->method = method; + request->uri = ast_strdup(uri); + sip_message_add_via(request, dialog); + + from = sip_message_find_header(&dialog->initial_request, "From"); + to = sip_message_find_header(&dialog->initial_request, "To"); + + /* Add tag *unless* this is a CANCEL, in which case we need to send it exactly as our original request, + * including tag (or presumably lack thereof) */ + if (ast_strlen_zero(dialog->initial_request.to_tag) && method != SIP_METHOD_CANCEL) { + new_to = ast_str_alloca(512); + ast_str_set(&new_to, 0, "%s", to); + + /* Add the proper tag if we don't have it already. If they have specified their tag, use it. Otherwise, + * use our own tag */ + if (dialog->outgoing && !ast_strlen_zero(dialog->remote_tag)) { + ast_str_append(&new_to, 0, ";tag=%s", dialog->remote_tag); + } else if (!dialog->outgoing && !ast_strlen_zero(dialog->local_tag)) { + ast_str_append(&new_to, 0, ";tag=%s", dialog->local_tag); + } + + to = ast_str_buffer(new_to); + } + + if (dialog->outgoing) { + sip_message_add_header(request, "From", from); + sip_message_add_header(request, "To", to); + } else { + sip_message_add_header(request, "From", to); + sip_message_add_header(request, "To", from); + } + + /* Do not add Contact for MESSAGE, BYE and CANCEL requests */ + if (!(method & (SIP_METHOD_BYE | SIP_METHOD_CANCEL | SIP_METHOD_MESSAGE))) { + sip_message_add_header(request, "Contact", dialog->our_contact); + } + + sip_message_copy_header(request, &dialog->initial_request, "Call-ID"); + sip_message_build_header(request, "CSeq", "%u %s", cseq, sip_method2str(method)); + + /* Use the learned route set unless this is a CANCEL or an ACK for a non-2xx final response. For a CANCEL or ACK, + * we have to send to the same destination as the original INVITE. Send UPDATE to the same destination as CANCEL, + * if call is not in final state */ + if (!sip_route_empty(&dialog->route) && + !(method == SIP_METHOD_CANCEL || (method == SIP_METHOD_ACK && + (dialog->invite_state == SIP_INVITE_COMPLETED || dialog->invite_state == SIP_INVITE_CANCELLED)))) { + if ((dialog->socket.transport & (AST_TRANSPORT_TCP | AST_TRANSPORT_TLS)) && dialog->socket.tcptls_session) { + /* For TCP/TLS sockets that are connected we won't need to do any hostname/IP lookups */ + } else if (dialog->nat_force_rport) { + /* For NATed traffic, we ignore the contact/route and simply send to the received-from address. + * No need for lookups */ + } else if (method == SIP_METHOD_UPDATE && + (dialog->invite_state == SIP_INVITE_PROCEEDING || dialog->invite_state == SIP_INVITE_EARLY_MEDIA)) { + /* Calling sip_dialog_set_address for an UPDATE in early dialog will result in mangling of the + * target for a subsequent CANCEL according to ASTERISK-24628 so do not do it */ + } else { + sip_get_uri_address(sip_route_first_uri(&dialog->route), &dialog->address); + } + + /* Skip the first URL in strict mode */ + sip_message_add_route(request, &dialog->route, strict); + } + + if (!ast_strlen_zero(sip_config.useragent)) { + sip_message_add_header(request, "User-Agent", sip_config.useragent); + } + + sip_message_build_header(request, "Max-Forwards", "%d", dialog->max_forwards); + + if (!ast_strlen_zero(dialog->html_url)) { + sip_message_add_header(request, "Access-URL", dialog->html_url); + ast_string_field_set(dialog, html_url, NULL); + } + + return 0; +} + +int sip_message_build_response(struct sip_message *response, struct sip_dialog *dialog, const char *status_line, + struct sip_message *request) +{ + const char *to, *via; + struct ast_str *new_to, *new_via; + int iter; + + memset(response, 0, sizeof(*response)); + + response->method = request->method; + response->response = TRUE; + + if (sscanf(status_line, "%30d", &response->code) != 1) { + ast_log(LOG_WARNING, "Unable to parse response code from '%s'\n", status_line); + return -1; + } + + response->status_line = ast_strdup(status_line); + new_via = ast_str_alloca(512); + + /* We should *always* add a received= to the top-most via */ + ast_str_set(&new_via, 0, "SIP/2.0/%s %s;branch=%s;received=%s", + ast_transport2str(dialog->socket.transport), request->via_sent_by, request->via_branch, + ast_sockaddr_stringify_addr_remote(&dialog->socket.address)); + + /* We need to add received port */ + if (dialog->nat_force_rport || dialog->rport_present) { + ast_str_append(&new_via, 0, ";rport=%d", ast_sockaddr_port(&dialog->socket.address)); + } + + if (!ast_strlen_zero(request->via_maddr)) { + ast_str_append(&new_via, 0, ";maddr=%s", request->via_maddr); + + if (request->via_ttl) { + ast_str_append(&new_via, 0, ";ttl=%d", request->via_ttl); + } + } + + iter = 0; + via = sip_message_next_header(request, "Via", &iter); + + /* Add any other Via header after the comma */ + if ((via = strchr(via, ','))) { + ast_str_append(&new_via, 0, "%s", via); + } + + sip_message_add_header(response, "Via", ast_str_buffer(new_via)); + + /* Copy any other Via headers untouched */ + for (;;) { + via = sip_message_next_header(request, "Via", &iter); + + if (ast_strlen_zero(via)) { + break; + } + + sip_message_add_header(response, "Via", via); + } + + sip_message_copy_header(response, request, "From"); + to = sip_message_find_header(request, "To"); + + if (ast_strlen_zero(request->to_tag) && response->code != 100) { + new_to = ast_str_alloca(512); + ast_str_set(&new_to, 0, "%s", to); + + /* Add the proper tag if we don't have it already. If they have specified their tag, use it. Otherwise, + * use our own tag */ + if (!ast_strlen_zero(dialog->remote_tag) && dialog->outgoing) { + ast_str_append(&new_to, 0, ";tag=%s", dialog->remote_tag); + } else if (!dialog->outgoing && !ast_strlen_zero(dialog->local_tag)) { + ast_str_append(&new_to, 0, ";tag=%s", dialog->local_tag); + } + + to = ast_str_buffer(new_to); + } + + sip_message_add_header(response, "To", to); + + if (response->code >= 200 && response->code < 300 && + response->method & (SIP_METHOD_SUBSCRIBE | SIP_METHOD_REGISTER | SIP_METHOD_PUBLISH)) { + if (dialog->expires) { /* Only add contact if we have an expires time */ + if (response->method == SIP_METHOD_SUBSCRIBE) { + sip_message_build_header(response, "Contact", "%s;expires=%d", + dialog->our_contact, dialog->expires); + } else { + sip_message_build_header(response, "Contact", "<%s>;expires=%d", + dialog->contact, dialog->expires); + } + } + + sip_message_build_header(response, "Expires", "%d", dialog->expires); + + if (response->method == SIP_METHOD_REGISTER && dialog->peer->path_support) { + sip_message_copy_header(response, request, "Path"); + } + } else if (!ast_strlen_zero(dialog->our_contact) && sip_message_needs_contact(response)) { + sip_message_add_header(response, "Contact", dialog->our_contact); + } + + sip_message_copy_header(response, request, "Call-ID"); + sip_message_copy_header(response, request, "CSeq"); + + if (!ast_strlen_zero(sip_config.useragent)) { + sip_message_add_header(response, "Server", sip_config.useragent); + } + + sip_message_add_header(response, "Allow", SIP_ALLOW_METHODS); + sip_message_add_supported(response, dialog); + + /* If this is an invite, add Session-Timers related headers if the feature is active for this session */ + if (response->method == SIP_METHOD_INVITE && dialog->session_timer_active) { + sip_message_build_header(response, "Session-Expires", "%d;refresher=%s", dialog->session_timer_expires, + dialog->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_UAC ? "uas" : "uac"); + + /* RFC 2048, Section 9 + * If the refresher parameter in the Session-Expires header field in the 2xx response has a value of + * 'uac', the UAS MUST place a Require header field into the response with the value 'timer'. + * ... + * If the refresher parameter in the 2xx response has a value of 'uas' and the Supported header field in + * the request contained the value 'timer', the UAS SHOULD place a Require header field into the + * response with the value 'timer' */ + if (dialog->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_UAS || + (dialog->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_UAC && + dialog->session_timer_remote_active)) { + sip_message_add_header(response, "Require", "timer"); + } + } + + if (response->code >= 100 && response->code < 300) { + int iter = 0; + + for (;;) { + const char *record_route = sip_message_next_header(request, "Record-Route", &iter); + + if (ast_strlen_zero(record_route)) { + break; + } + + /* Add what we're responding to */ + sip_message_add_header(response, "Record-Route", record_route); + } + } + + if (!ast_strlen_zero(dialog->html_url)) { + sip_message_add_header(response, "Access-URL", dialog->html_url); + ast_string_field_set(dialog, html_url, NULL); + } + + /* Default to routing the response to the address where the request came from. Since we don't have a transport + * layer, we do this here. The sip_dialog_set_via() function will update the port to either the port specified + * in the via header or the default port later on (per RFC 3261 section 18.2.2) */ + ast_sockaddr_copy(&dialog->address, &dialog->socket.address); + + if (!ast_strlen_zero(request->via_maddr)) { + if (ast_sockaddr_resolve_first_af(&dialog->address, request->via_maddr, 0, AST_AF_INET)) { + ast_log(LOG_WARNING, + "Error processing Via maddr '%s', will send response to originating address\n", + request->via_maddr); + } else { + if (ast_sockaddr_is_ipv4_multicast(&dialog->address) && request->via_ttl) { + setsockopt(sip_socket_fd, IPPROTO_IP, IP_MULTICAST_TTL, &request->via_ttl, sizeof(request->via_ttl)); + } + + if (!ast_sockaddr_port(&dialog->address)) { + ast_sockaddr_set_port(&dialog->address, SIP_STANDARD_PORT); + } + } + } + + return 0; +} + +/* Find or create a dialog structure for an incoming SIP message. Connect incoming SIP message to current dialog or + * create new dialog structure */ +struct sip_dialog *sip_message_find_dialog(struct sip_message *message, struct sip_socket *socket) +{ + struct sip_dialog *dialog; + + if (!message->method) { + ast_debug(1, "Received a message with an unknown method\n"); + + if (!message->response) { + sip_response_send_using_temp(socket, "501 Method Not Implemented", message); + } + + return NULL; + } + + if (message->response && !message->code) { + ast_debug(1, "Received a response message with no code\n"); + return NULL; + } + + /* All messages must have a Call-ID, From-tag and Via sent-by+branch */ + if (ast_strlen_zero(message->call_id) || ast_strlen_zero(message->from_tag) || !message->cseq || + ast_strlen_zero(message->via_sent_by) || ast_strlen_zero(message->via_branch)) { + ast_debug(1, "Received a message with missing headers\n"); + + if (!message->response) { + sip_response_send_using_temp(socket, "400 Bad Request", message); + } + + return NULL; + } + + ast_debug(5, "Finding %s dialog with Call-ID: '%s' From: tag='%s' To: tag='%s'\n", + message->response ? "response" : "request", message->call_id, message->from_tag, message->to_tag); + + if (ast_strlen_zero(message->from_tag)) { + ast_debug(5, "%s message has no From: tag, dropping Call-ID: '%s' From: '%s'\n", + sip_method2str(message->method), message->call_id, sip_message_find_header(message, "From")); + return NULL; + } + + /* Reject messages that must always have a To: tag */ + if (ast_strlen_zero(message->to_tag) && message->method & (SIP_METHOD_ACK | SIP_METHOD_BYE | SIP_METHOD_INFO)) { + if (message->method != SIP_METHOD_ACK) { + sip_response_send_using_temp(socket, "481 Call/Transaction Does Not Exist", message); + } + + ast_debug(5, "%s must have a To: tag, dropping Call-ID: '%s' From: '%s'\n", + sip_method2str(message->method), message->call_id, sip_message_find_header(message, "To")); + return NULL; + } + + /* Match on Call-ID only for REGISTERs */ + if (message->method == SIP_METHOD_REGISTER) { + if ((dialog = ao2_find(sip_dialogs, (char *) message->call_id, OBJ_SEARCH_KEY))) { + /* Found the call */ + return dialog; + } + } else { + /* If a Outbound forked Request is detected, this dialog will point to the dialog the Request is + * forking off of */ + struct ao2_iterator *iter; + int has_authorization; + + /* Determine if this is a Request with authentication credentials */ + if (!ast_strlen_zero(sip_message_find_header(message, "Authorization")) || + !ast_strlen_zero(sip_message_find_header(message, "Proxy-Authorization"))) { + has_authorization = TRUE; + } else { + has_authorization = FALSE; + } + + iter = ao2_callback(sip_dialogs, OBJ_SEARCH_KEY | OBJ_MULTIPLE, sip_dialog_cmp, (char *) message->call_id); + + /* Iterate a list of dialogs already matched by Call-ID */ + while ((dialog = ao2_iterator_next(iter))) { + int res; + + ao2_lock(dialog); + res = sip_message_tag_cmp(message, dialog, has_authorization); + ao2_unlock(dialog); + + switch (res) { + case SIP_MESSAGE_MATCH: + ao2_lock(dialog); + + if (!message->response && has_authorization && strcmp(message->from_tag, dialog->remote_tag)) { + /* If we have a message that uses athentication and the from tag is different from + * that in the original call dialog, update the from tag in the saved call dialog */ + ast_string_field_set(dialog, remote_tag, message->from_tag); + } + + ao2_unlock(dialog); + ao2_iterator_destroy(iter); + return dialog; /* Return dialog with ref */ + case SIP_MESSAGE_LOOP_DETECTED: + /* This is likely a forked Request that somehow resulted in us receiving multiple parts + * of the fork. RFC 3261 section 8.2.2.2, Indicate that we want to merge messages by + * sending a 482 response */ + sip_response_send_using_temp(socket, "482 Loop Detected", message); + + ao2_ref(dialog, -1); + ao2_iterator_destroy(iter); + return NULL; + case SIP_MESSAGE_FORKED: + /* Right now we only support handling forked INVITE Requests. Any other forked message + * type must be added here */ + if (dialog->method == SIP_METHOD_INVITE) { + struct sip_dialog *fork_dialog; + + if (!(fork_dialog = sip_dialog_alloc(dialog->call_id, socket, SIP_METHOD_INVITE, + message, dialog->logger_callid))) { + break; + } + + sip_dialog_check_via(dialog, message); + sip_dialog_set_rtp_nat(dialog); + + ast_string_field_set(fork_dialog, remote_tag, message->to_tag); + ast_string_field_set(fork_dialog, local_tag, dialog->local_tag); + ast_string_field_set(fork_dialog, uri, dialog->uri); + ast_string_field_set(fork_dialog, our_contact, dialog->our_contact); + ast_string_field_set(fork_dialog, contact, dialog->contact); + ast_string_field_set(fork_dialog, branch, dialog->branch); + + sip_message_copy(&fork_dialog->initial_request, &dialog->initial_request); + + fork_dialog->invite_state = SIP_INVITE_TERMINATED; + fork_dialog->outgoing_cseq = dialog->outgoing_cseq; + + sip_dialog_set_contact(fork_dialog, message); + sip_dialog_build_route(fork_dialog, message, TRUE); + + sip_request_send_ack(fork_dialog, fork_dialog->outgoing_cseq, TRUE); + sip_request_send_bye(fork_dialog); + + /* This dialog will terminate once the BYE is responed to or times out */ + sip_dialog_set_need_destroy(fork_dialog, "forked dialog"); + ao2_ref(fork_dialog, -1); + } + + break; + case SIP_MESSAGE_NO_MATCH: + default: + break; + } + + ao2_ref(dialog, -1); + } + + ao2_iterator_destroy(iter); + } + + if (message->response) { + /* We do not respond to responses for dialogs that we don't know about, we just drop the session quickly */ + ast_debug(2, "Received a %s response for '%s' which we don't know about\n", + sip_method2str(message->method), message->call_id); + } else if (message->method & (SIP_METHOD_OPTIONS | SIP_METHOD_INVITE | SIP_METHOD_REGISTER | SIP_METHOD_SUBSCRIBE | + SIP_METHOD_NOTIFY | SIP_METHOD_REFER | SIP_METHOD_PUBLISH | SIP_METHOD_MESSAGE)) { + struct sip_dialog *dialog; + ast_callid logger_callid; + + if (message->method == SIP_METHOD_INVITE) { + logger_callid = ast_create_callid(); + } else { + logger_callid = 0; + } + + /* Ok, time to create a new SIP dialog object, a dialog */ + if (!(dialog = sip_dialog_alloc(message->call_id, socket, message->method, message, logger_callid))) { + /* We have a memory or file/socket error (can't allocate RTP sockets or something) so we're not + * getting a dialog from sip_dialog_alloc. Without a dialog we can't retransmit and handle ACKs + * and all that, but at least send an error message */ + sip_response_send_using_temp(socket, "500 Internal Server Error", message); + } + + sip_dialog_check_via(dialog, message); + sip_dialog_set_rtp_nat(dialog); + + return dialog; /* Can be NULL */ + } else if (message->method != SIP_METHOD_ACK) { + /* This is a message outside of a dialog that we don't know about */ + ast_debug(2, "Received a %s request for '%s' which we don't know about\n", + sip_method2str(message->method), message->call_id); + sip_response_send_using_temp(socket, "481 Call/Transaction Does Not Exist", message); + } + + return NULL; +} + +/* Match a incoming Request/Response to a dialog. The Call-ID has already been confirmed to match at this point */ +static int sip_message_tag_cmp(struct sip_message *message, struct sip_dialog *dialog, int has_authorization) +{ + if (message->response) { + /* Verify from tag of response matches the tag we gave them */ + if (strcmp(message->from_tag, dialog->local_tag)) { + /* From tag from response does not match our tag */ + return SIP_MESSAGE_NO_MATCH; + } + + /* Verify to-tag if we have one stored for this dialog, but never be strict about this for a response + * until the dialog is established */ + if (!ast_strlen_zero(dialog->remote_tag) && dialog->established) { + if (ast_strlen_zero(message->to_tag)) { + /* Missing to-tag when they already gave us one earlier */ + return SIP_MESSAGE_NO_MATCH; + } + + /* Compare the to-tag of response with the tag we have stored for them */ + if (strcmp(message->to_tag, dialog->remote_tag)) { + /* Forked Request Detection. If this is a 200 OK response and the to-tags do not match, + * this might be a forked response to an outgoing Request. Detection of a forked + * response must meet the criteria below. + * 1. must be a 2xx Response + * 2. call-d equal to call-id of Request. this is done earlier + * 3. from-tag equal to from-tag of Request. this is done earlier + * 4. branch parameter equal to branch of inital Request + * 5. to-tag _NOT_ equal to previous 2xx response that already established the dialog */ + if (message->code == 200 && + !ast_strlen_zero(dialog->invite_branch) && + !ast_strlen_zero(message->via_branch) && + !strcmp(dialog->invite_branch, message->via_branch)) { + return SIP_MESSAGE_FORKED; + } + + /* The to-tag did not match the one we had stored, and this is not a Forked Request */ + return SIP_MESSAGE_NO_MATCH; + } + } + } else { + /* Verify the from tag of Request matches the tag they provided earlier. If this is a Request with + * authentication credentials, forget their old tag as it is not valid after the 401 or 407 response */ + if (!has_authorization && strcmp(message->from_tag, dialog->remote_tag)) { + /* Their tag does not match the one was have stored for them */ + return SIP_MESSAGE_NO_MATCH; + } + + /* Verify if to-tag is present in Request, that it matches what we gave them as our tag earlier */ + if (!ast_strlen_zero(message->to_tag) && strcmp(message->to_tag, dialog->local_tag)) { + /* To-tag from Request does not match our tag */ + return SIP_MESSAGE_NO_MATCH; + } + } + + /* Compare incoming message against initial transaction. This is a best effort attempt at distinguishing forked + * messages from our initial transaction. If all the elements are NOT in place to evaluate this, this block is + * ignored and the dialog match is made regardless. Once the to-tag is established after the dialog is confirmed, + * this is not necessary. + * + * CRITERIA required for initial transaction matching. + * 1. Is a Request + * 2. Callid and remote_tag match (this is done in the dialog matching block) + * 3. to-tag is NOT present + * 4. CSeq matches our initial transaction's cseq number + * 5. dialog has init via branch parameter stored */ + + /* Must be a Request, and must not have a to-tag, the cseq must be the same as this dialogs initial cseq, the + * dialog must have started with a RFC3261 compliant branch tag and the dialog must have an initial message uri + * associated with it */ + if (!message->response && ast_strlen_zero(message->to_tag) && dialog->initial_incoming_cseq == message->cseq && + !ast_strlen_zero(dialog->via_branch) && dialog->initial_request.uri) { + /* This Request matches all the criteria required for Loop/Merge detection. Now we must go down the path + * of comparing VIA's and RURIs */ + if (ast_strlen_zero(message->via_branch) || strcmp(message->via_branch, dialog->via_branch) || + ast_strlen_zero(message->via_sent_by) || strcmp(message->via_sent_by, dialog->via_sent_by)) { + /* At this point, this message does not match this Dialog.if methods are different this is just + * a mismatch */ + if (dialog->method != message->method) { + return SIP_MESSAGE_NO_MATCH; + } + + /* If RUIs are different, this is a forked message to a separate URI. Returning a mismatch + * allows this Request to be processed separately */ + if (sip_cmp_uri(dialog->initial_request.uri, message->uri)) { + /* Not a match, message uris are different */ + return SIP_MESSAGE_NO_MATCH; + } + + /* Loop/Merge Detected + * ---Current Matches to Initial Request--- + * message uri + * call-id + * their-tag + * no to-tag present + * method + * cseq + * + * --- Does not Match Initial Request --- + * Top Via + * + * Without the same Via, this can not match our initial transaction for this dialog, but given + * that this Request matches everything else associated with that initial Request this is most + * certainly a Forked message in which we have already received part of the fork */ + return SIP_MESSAGE_LOOP_DETECTED; + } + } + + /* Match Authentication Request. A Request with an Authentication header must come back with the same Request + * URI. Otherwise it is not a match. Must be a Request type to even begin checking this, no to-tag is present + * to match, Authentication header is present in Request and compare the Request URI of both the last Request + * and this new one */ + if (!message->response && ast_strlen_zero(message->to_tag) && + has_authorization && sip_cmp_uri(dialog->initial_request.uri, message->uri)) { + /* Authentication was provided, but the Request URI did not match the last one on this dialog */ + return SIP_MESSAGE_NO_MATCH; + } + + return SIP_MESSAGE_MATCH; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/monitor.c asterisk-22.6.0/channels/sip/monitor.c --- asterisk-22.6.0.orig/channels/sip/monitor.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/monitor.c 2025-10-21 18:38:17.455218013 +1300 @@ -0,0 +1,174 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/io.h" +#include "asterisk/sched.h" +#include "asterisk/tcptls.h" +#include "asterisk/channel.h" +#include "asterisk/callerid.h" +#include "asterisk/indications.h" +#include "asterisk/pbx.h" +#include "asterisk/devicestate.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/realtime.h" +#include "include/registrations.h" +#include "include/mwi_subscriptions.h" +#include "include/dialog.h" +#include "include/sdp.h" +#include "include/utils.h" +#include "include/config.h" + +/* Protect the monitoring thread, so only one process can kill or start it, and not when it's doing something critical */ +ast_mutex_t sip_monitor_lock; +/* This is the thread for the monitor which checks for input on the channels which are not currently in use */ +pthread_t sip_monitor_threadid = AST_PTHREADT_NULL; +struct ast_sched_context *sip_sched_context; /* The scheduling context */ + +/* SIP monitoring thread. This thread monitors all the SIP sessions and peers that needs notification of mwi and thus do not + * have a separate thread) indefinitely */ +void *sip_monitor_thread(void *data) +{ + int res, reloading; + + /* Add an I/O event to our SIP UDP socket */ + if (sip_socket_fd != -1) { + sip_socket_io_id = ast_io_add(sip_io_context, sip_socket_fd, sip_socket_recv, AST_IO_IN, NULL); + } + + /* From here on out, we die whenever asked */ + for (;;) { + /* Check for a reload request */ + ast_mutex_lock(&sip_config_lock); + + reloading = sip_config_reloading; + sip_config_reloading = FALSE; + + ast_mutex_unlock(&sip_config_lock); + + if (reloading) { + struct timeval reload_start, reload_end; + + reload_start = ast_tvnow(); + ast_verb(1, "SIP reloading\n"); + + sip_config_parse(); + ast_sched_dump(sip_sched_context); + + /* Prune peers who still are supposed to be removed */ + ao2_callback(sip_peers, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, sip_peer_unlink, NULL); + + sip_peer_qualify_all(); /* Send qualify (OPTIONS) to all peers */ + sip_registration_send_all(); /* Register with all services */ + sip_mwi_subscription_send_all(); /* Subscribe to remote mailboxes */ + + reload_end = ast_tvnow(); + ast_debug(2, "SIP reloaded in %ldms\n", ast_tvdiff_ms(reload_end, reload_start)); + + /* Change the I/O fd of our UDP socket */ + if (sip_socket_fd > -1) { + if (sip_socket_io_id) { + sip_socket_io_id = ast_io_change(sip_io_context, sip_socket_io_id, sip_socket_fd, NULL, 0, NULL); + } else { + sip_socket_io_id = ast_io_add(sip_io_context, sip_socket_fd, sip_socket_recv, AST_IO_IN, NULL); + } + } else if (sip_socket_io_id) { + ast_io_remove(sip_io_context, sip_socket_io_id); + sip_socket_io_id = NULL; + } + } + + /* Check dialogs with rtp and rtptimeout. All dialogs which have rtp are in sip_dialogs_with_rtp */ + ao2_callback(sip_dialogs_with_rtp, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, sip_dialog_check_rtp_timeout, NULL); + /* Check dialogs marked to be destroyed. All dialogs with need_destroy set are in sip_dialogs_need_destroy */ + ao2_callback(sip_dialogs_need_destroy, OBJ_NODATA | OBJ_MULTIPLE, sip_dialog_check_need_destroy, NULL); + + /* The scheduler usage in this module does not have sufficient synchronization being done between running the + * scheduler and places scheduling tasks. As it is written, any scheduled item may not run any sooner than about 1 second, + * regardless of whether a sooner time was asked for */ + pthread_testcancel(); + /* Wait for sched or io */ + res = ast_sched_wait(sip_sched_context); + + if (res < 0 || res > 1000) { + res = 1000; /* 1s */ + } + + ast_io_wait(sip_io_context, res); + + ast_mutex_lock(&sip_monitor_lock); + ast_sched_runq(sip_sched_context); + ast_mutex_unlock(&sip_monitor_lock); + } + + /* Never reached */ + return NULL; +} + +/* Start the channel monitor thread */ +int sip_monitor_restart(void) +{ + /* If we're supposed to be stopped -- stay stopped */ + if (sip_monitor_threadid == AST_PTHREADT_STOP) { + return 0; + } + + ast_mutex_lock(&sip_monitor_lock); + + if (sip_monitor_threadid == pthread_self()) { + ast_mutex_unlock(&sip_monitor_lock); + return -1; + } + + if (sip_monitor_threadid != AST_PTHREADT_NULL && sip_monitor_threadid != AST_PTHREADT_STOP) { + /* Wake up the thread */ + pthread_kill(sip_monitor_threadid, SIGURG); + } else { + /* Start a new monitor */ + if (ast_pthread_create_background(&sip_monitor_threadid, NULL, sip_monitor_thread, NULL) < 0) { + ast_mutex_unlock(&sip_monitor_lock); + ast_log(LOG_ERROR, "Unable to start monitor thread\n"); + return -1; + } + } + + ast_mutex_unlock(&sip_monitor_lock); + return 0; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/mwi_subscriptions.c asterisk-22.6.0/channels/sip/mwi_subscriptions.c --- asterisk-22.6.0.orig/channels/sip/mwi_subscriptions.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/mwi_subscriptions.c 2025-10-21 18:38:17.456217986 +1300 @@ -0,0 +1,422 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/sched.h" +#include "asterisk/netsock2.h" +#include "asterisk/channel.h" +#include "asterisk/indications.h" +#include "asterisk/stasis.h" +#include "asterisk/pbx.h" +#include "asterisk/dnsmgr.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/mwi_subscriptions.h" + +static void sip_mwi_subscription_destroy(void *data); +static void sip_mwi_subscription_dnsmgr_lookup(struct ast_sockaddr *old_address, struct ast_sockaddr *new_address, void *data); + +static int __sip_mwi_subscription_resend(struct sip_mwi_subscription *mwi_subscription); +static int __sip_mwi_subscription_start(const void *data); +static void sip_mwi_subscription_stop(struct sip_mwi_subscription *mwi_subscription); +static int __sip_mwi_subscription_stop(const void *data); + +/* The MWI subscription list */ +struct ao2_container *sip_mwi_subscriptions = NULL; + +int sip_mwi_subscription_cmp(void *data, void *arg, int flags) +{ + struct sip_mwi_subscription *mwi_subscription; + const char *config; + + mwi_subscription = (struct sip_mwi_subscription *) data; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + struct sip_mwi_subscription *mwi_subscription = (struct sip_mwi_subscription *) arg; + + config = mwi_subscription->config; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + config = (const char *) arg; + } else { + return 0; + } + + if (!strcmp(mwi_subscription->config, config)) { + return CMP_MATCH | CMP_STOP; + } + + return 0; +} + +/* Parse mwi line */ +int sip_mwi_subscription_build(const char *config, int lineno) +{ + struct sip_mwi_subscription *mwi_subscription; + enum ast_transport transport; + char *user, *host, *secret, *md5_secret, *authorization_user, *mailbox, *context, *protocol; + int port; + + if (ast_strlen_zero(config)) { + return -1; + } + + protocol = ast_strdupa(config); + + if (!strncmp(protocol, "udp://", 6)) { + transport = AST_TRANSPORT_UDP; + user = protocol + 6; + } else if (!strncmp(protocol, "tcp://", 6)) { + transport = AST_TRANSPORT_TCP; + user = protocol + 6; + } else if (!strncmp(protocol, "tls://", 6)) { + transport = AST_TRANSPORT_TLS; + user = protocol + 6; + } else { + if (strstr(protocol, "://")) { + ast_log(LOG_WARNING, "Invalid transport for '%s' at line %d\n", config, lineno); + return -1; + } + + transport = AST_TRANSPORT_UDP; + user = protocol; + } + + if (!(host = strchr(user, '@'))) { + ast_log(LOG_WARNING, "Missing host for '%s' at line %d\n", config, lineno); + return -1; + } + + *host++ = '\0'; + + if (sip_parse_port(host, &port)) { + ast_log(LOG_WARNING, "Invalid port for '%s' at line %d\n", config, lineno); + return -1; + } + + if ((secret = strchr(user, ':'))) { + *secret++ = '\0'; + } + + if (secret && (md5_secret = strchr(secret, ':'))) { + *md5_secret++ = '\0'; + } else { + md5_secret = NULL; + } + + if (md5_secret && (authorization_user = strchr(md5_secret, ':'))) { + *authorization_user++ = '\0'; + } else { + authorization_user = NULL; + } + + if ((mailbox = strchr(host, '/'))) { + *mailbox++ = '\0'; + + if ((context = strchr(mailbox, '@'))) { + *context++ = '\0'; + } else { + context = "SIP_Remote"; + } + } + + if (ast_strlen_zero(user) || ast_strlen_zero(host) || ast_strlen_zero(mailbox)) { + ast_log(LOG_WARNING, + "Format for 'mwi' is [transport://]user[:secret[:md5secret[:authuser]]]@host[:port]/mailbox[@context] at line %d\n", + lineno); + return -1; + } + + if ((mwi_subscription = ao2_find(sip_mwi_subscriptions, config, OBJ_SEARCH_KEY))) { + ao2_ref(mwi_subscription, -1); + return 0; + } + + if (!(mwi_subscription = ao2_alloc(sizeof(*mwi_subscription), sip_mwi_subscription_destroy))) { + return -1; + } + + if (ast_string_field_init(mwi_subscription, 256)) { + ao2_ref(mwi_subscription, -1); + return -1; + } + + ast_string_field_set(mwi_subscription, config, config); + ast_string_field_set(mwi_subscription, user, user); + ast_string_field_set(mwi_subscription, secret, secret); + ast_string_field_set(mwi_subscription, md5_secret, md5_secret); + ast_string_field_set(mwi_subscription, authorization_user, authorization_user); + + ast_string_field_set(mwi_subscription, host, host); + ast_string_field_set(mwi_subscription, mailbox, mailbox); + ast_string_field_set(mwi_subscription, context, context); + + mwi_subscription->port = port; + mwi_subscription->transport = transport; + mwi_subscription->expires_sched_id = -1; + + ao2_link(sip_mwi_subscriptions, mwi_subscription); + ao2_ref(mwi_subscription, -1); + + ast_debug(3, "Added mwi %s@%s %s\n", mwi_subscription->user, mwi_subscription->host, mwi_subscription->mailbox); + return 0; +} + +/* Destroy MWI subscription object */ +static void sip_mwi_subscription_destroy(void *data) +{ + struct sip_mwi_subscription *mwi_subscription = (struct sip_mwi_subscription *) data; + + ast_debug(3, "Destroying mwi %s@%s %s\n", mwi_subscription->user, mwi_subscription->host, mwi_subscription->mailbox); + + if (mwi_subscription->dialog) { + mwi_subscription->dialog->mwi_subscription = NULL; + ao2_ref(mwi_subscription->dialog, -1); + } + + ast_string_field_free_memory(mwi_subscription); +} + +int sip_mwi_subscription_unlink(void *data, void *arg, int flags) +{ + struct sip_mwi_subscription *mwi_subscription = (struct sip_mwi_subscription *) data; + + sip_mwi_subscription_stop(mwi_subscription); + return CMP_MATCH; +} + +static void sip_mwi_subscription_dnsmgr_lookup(struct ast_sockaddr *old_address, struct ast_sockaddr *new_address, void *data) +{ + struct sip_mwi_subscription *mwi_subscription; + const char *old_host, *new_host; + + mwi_subscription = (struct sip_mwi_subscription *) data; + + /* This shouldn't happen, but just in case */ + if (ast_sockaddr_isnull(new_address)) { + return; + } + + old_host = ast_strdupa(ast_sockaddr_stringify(old_address)); + new_host = ast_strdupa(ast_sockaddr_stringify(new_address)); + + ast_debug(1, "Changing mwi %s from %s to %s\n", mwi_subscription->host, old_host, new_host); + ast_sockaddr_copy(&mwi_subscription->address, new_address); +} + +/* Actually setup an MWI subscription or resubscribe */ +static int __sip_mwi_subscription_resend(struct sip_mwi_subscription *mwi_subscription) +{ + /* If we have no DNS manager let's do a lookup */ + if (!mwi_subscription->dnsmgr) { + char service[MAXHOSTNAMELEN]; + + mwi_subscription->address.ss.ss_family = AF_INET; + snprintf(service, sizeof(service), "_%s._%s", + sip_srv_service(mwi_subscription->transport), sip_srv_protocol(mwi_subscription->transport)); + + ast_dnsmgr_lookup_cb(mwi_subscription->host, &mwi_subscription->address, &mwi_subscription->dnsmgr, + sip_config.srv_lookup && !mwi_subscription->port ? service : NULL, sip_mwi_subscription_dnsmgr_lookup, + ao2_bump(mwi_subscription)); + + if (!mwi_subscription->dnsmgr) { + ao2_ref(mwi_subscription, -1); + } + } + + /* If we already have a subscription up simply send a resubscription */ + if (mwi_subscription->dialog) { + /* Clear out existing authorization details */ + mwi_subscription->dialog->authorization_code = 0; + ast_string_field_set(mwi_subscription->dialog, authorization, NULL); + + sip_request_send_subscribe(mwi_subscription->dialog, SIP_INIT_NONE); + return 0; + } + + /* Create a dialog that we will use for the subscription */ + if (!(mwi_subscription->dialog = sip_dialog_alloc(NULL, NULL, SIP_METHOD_SUBSCRIBE, NULL, 0))) { + return -1; + } + + sip_proxy_set(mwi_subscription->dialog, sip_proxy_get(mwi_subscription->dialog, NULL)); + + /* Setup the destination of our subscription */ + if (sip_dialog_build(mwi_subscription->dialog, mwi_subscription->host, &mwi_subscription->address, FALSE)) { + sip_dialog_unlink(mwi_subscription->dialog); + + ao2_ref(mwi_subscription->dialog, -1); + mwi_subscription->dialog = NULL; + + ast_log(LOG_WARNING, "DNS lookup error for MWI subscription '%s@%s/%s'\n", + mwi_subscription->user, mwi_subscription->host, mwi_subscription->mailbox); + return 0; + } + + mwi_subscription->dialog->expires = sip_config.subscribe_max_expires; + + if (!mwi_subscription->dnsmgr && mwi_subscription->port) { + ast_sockaddr_set_port(&mwi_subscription->dialog->address, mwi_subscription->port); + } else { + mwi_subscription->port = ast_sockaddr_port(&mwi_subscription->dialog->address); + } + + sip_socket_set_transport(&mwi_subscription->dialog->socket, mwi_subscription->transport); + sip_dialog_set_our_address(mwi_subscription->dialog); + + /* Set various other information */ + if (!ast_strlen_zero(mwi_subscription->authorization_user)) { + ast_string_field_set(mwi_subscription->dialog->peer, authorization_user, mwi_subscription->authorization_user); + } else { + ast_string_field_set(mwi_subscription->dialog->peer, authorization_user, mwi_subscription->user); + } + + ast_string_field_set(mwi_subscription->dialog->peer, secret, mwi_subscription->secret); + ast_string_field_set(mwi_subscription->dialog->peer, md5_secret, mwi_subscription->md5_secret); + + ast_string_field_set(mwi_subscription->dialog, from_user, mwi_subscription->user); + ast_string_field_set(mwi_subscription->dialog, from_domain, mwi_subscription->host); + + ast_string_field_set(mwi_subscription->dialog, to_user, mwi_subscription->user); + ast_string_field_set(mwi_subscription->dialog, to_host, mwi_subscription->host); + + /* Associate the call with us */ + mwi_subscription->dialog->mwi_subscription = ao2_bump(mwi_subscription); + mwi_subscription->dialog->outgoing = TRUE; + mwi_subscription->dialog->subscribe_event = SIP_SUBSCRIBE_MESSAGE_SUMMARY; + + /* Actually send the packet */ + sip_request_send_subscribe(mwi_subscription->dialog, SIP_INIT_REQUEST); + return 0; +} + +/* Send a subscription or resubscription for MWI */ +int sip_mwi_subscription_resend(const void *data) +{ + struct sip_mwi_subscription *mwi_subscription = (struct sip_mwi_subscription *) data; + + mwi_subscription->expires_sched_id = -1; + __sip_mwi_subscription_resend(mwi_subscription); + ao2_ref(mwi_subscription, -1); + return 0; +} + +/* Send all MWI subscriptions */ +void sip_mwi_subscription_send_all(void) +{ + struct ao2_iterator iter; + struct sip_mwi_subscription *mwi_subscription; + + if (ao2_container_count(sip_mwi_subscriptions)) { + return; + } + + iter = ao2_iterator_init(sip_mwi_subscriptions, 0); + + while ((mwi_subscription = ao2_iterator_next(&iter))) { + sip_mwi_subscription_start(mwi_subscription, 1); + ao2_ref(mwi_subscription, -1); + } + + ao2_iterator_destroy(&iter); +} + +/* Run by the sched thread */ +static int __sip_mwi_subscription_start(const void *data) +{ + struct sip_sched_data *sched_data; + struct sip_mwi_subscription *mwi_subscription; + int when; + + sched_data = (struct sip_sched_data *) data; + + mwi_subscription = sched_data->mwi_subscription; + when = sched_data->when; + ast_free(sched_data); + + AST_SCHED_DEL_UNREF(sip_sched_context, mwi_subscription->expires_sched_id, ao2_cleanup(mwi_subscription)); + + if ((mwi_subscription->expires_sched_id = ast_sched_add(sip_sched_context, when, + sip_mwi_subscription_resend, ao2_bump(mwi_subscription))) == -1) { + ao2_ref(mwi_subscription, -1); + } + + ao2_ref(mwi_subscription, -1); + return 0; +} + +void sip_mwi_subscription_start(struct sip_mwi_subscription *mwi_subscription, int when) +{ + struct sip_sched_data *sched_data; + + if (!(sched_data = ast_malloc(sizeof(*sched_data)))) { + return; + } + + sched_data->mwi_subscription = ao2_bump(mwi_subscription); + sched_data->when = when; + + if (ast_sched_add(sip_sched_context, 0, __sip_mwi_subscription_start, sched_data) == -1) { + ao2_ref(sched_data->mwi_subscription, -1); + ast_free(sched_data); + } +} + +/* Run by the sched thread */ +static int __sip_mwi_subscription_stop(const void *data) +{ + struct sip_mwi_subscription *mwi_subscription = (struct sip_mwi_subscription *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, mwi_subscription->expires_sched_id, ao2_cleanup(mwi_subscription)); + + if (mwi_subscription->dnsmgr) { + ast_dnsmgr_release(mwi_subscription->dnsmgr); + mwi_subscription->dnsmgr = NULL; + ao2_ref(mwi_subscription, -1); + } + + ao2_ref(mwi_subscription, -1); + return 0; +} + +static void sip_mwi_subscription_stop(struct sip_mwi_subscription *mwi_subscription) +{ + if (ast_sched_add(sip_sched_context, 0, __sip_mwi_subscription_stop, ao2_bump(mwi_subscription)) == -1) { + ao2_ref(mwi_subscription, -1); + } +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/netsock.c asterisk-22.6.0/channels/sip/netsock.c --- asterisk-22.6.0.orig/channels/sip/netsock.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/netsock.c 2025-10-21 18:38:17.457217959 +1300 @@ -0,0 +1,1267 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/logger.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/io.h" +#include "asterisk/sched.h" +#include "asterisk/netsock2.h" +#include "asterisk/tcptls.h" +#include "asterisk/channel.h" +#include "asterisk/causes.h" +#include "asterisk/callerid.h" +#include "asterisk/indications.h" +#include "asterisk/pbx.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/config.h" +#include "include/utils.h" +#include "include/dialog.h" +#include "include/handlers.h" + +#define SIP_PACKET_MAX_SIZE 20480 /* Max SIP packet size */ + +static int sip_socket_build(struct sip_dialog *dialog); + +static int sip_tcptls_thread_address_cmp(void *data, void *arg, int flags); +static struct sip_tcptls_thread *sip_tcptls_thread_alloc(struct ast_tcptls_session_instance *tcptls_session); +static void sip_tcptls_thread_destroy(void *data); + +static void sip_tcptls_session_args_destroy(void *data); +static struct ast_tcptls_session_instance *sip_tcptls_session_find(struct ast_sockaddr *address); +static int sip_tcptls_session_read_done(struct ast_tcptls_session_instance *tcptls_session, struct ast_str **read_buf); +static int sip_tcptls_session_read(struct ast_tcptls_session_instance *tcptls_session, struct ast_str **read_buf, + int timeout); +static void *sip_tcptls_session_thread(void *data); + +static void sip_packet_destroy(void *data); +static int __sip_dialog_cancel_resend(const void *data); + +/* Main socket for UDP SIP communication. sip_socket_fd is shared between the SIP manager thread (which handles reload + * requests), the udp io handler sip_socket_recv and the user routines that issue udp writes using sip_packet_send. + * The socket is -1 only when opening fails (this is a permanent condition), or when we are handling a reload that + * changes its address (this is a transient situation during which we might have a harmless race, see below). Because + * the conditions for the race to be possible are extremely rare, we don't want to pay the cost of locking on every I/O. + * Rather, we remember that when the race may occur, communication is bound to fail anyways, so we just live with this + * event and let the protocol handle this above us */ +int sip_socket_fd = -1; +int *sip_socket_io_id = NULL; + +struct io_context *sip_io_context = NULL; /* The IO context */ +ast_mutex_t sip_netsock_lock; +struct ast_tls_config sip_tls_session_config; /* Working TLS connection configuration */ + +/* The TCP server definition */ +struct ast_tcptls_session_args sip_tcp_session = { + .accept_fd = -1, + .master = AST_PTHREADT_NULL, + .tls_cfg = NULL, + .poll_timeout = -1, + .name = "SIP TCP server", + .accept_fn = ast_tcptls_server_root, + .worker_fn = sip_tcptls_session_thread, +}; + +/* The TCP/TLS server definition */ +struct ast_tcptls_session_args sip_tls_session = { + .accept_fd = -1, + .master = AST_PTHREADT_NULL, + .tls_cfg = &sip_tls_session_config, + .poll_timeout = -1, + .name = "SIP TLS server", + .accept_fn = ast_tcptls_server_root, + .worker_fn = sip_tcptls_session_thread, +}; + +struct ao2_container *sip_tcptls_threads = NULL; /* The table of TCP threads */ + +/* Read data from SIP UDP socket */ +int sip_socket_recv(int *id, int fd, short events, void *ignore) +{ + struct sip_socket socket; + struct ast_sockaddr address; + static char data[SIP_PACKET_MAX_SIZE]; + int res; + + if ((res = ast_recvfrom(fd, data, sizeof(data) - 1, 0, &address)) == -1) { +#if !defined(__FreeBSD__) + if (errno == EAGAIN) { + ast_log(LOG_NOTICE, "Received packet with bad UDP checksum\n"); + } +#endif + if (errno != ECONNREFUSED) { + ast_log(LOG_WARNING, "Recvfrom error: %s\n", strerror(errno)); + } + + return 1; + } + + data[res] = '\0'; + ast_sockaddr_copy(&socket.address, &address); + + socket.transport = AST_TRANSPORT_UDP; + socket.fd = fd; + socket.tcptls_session = NULL; + + sip_handle_incoming(&socket, data); + return 1; +} + +void sip_socket_set_transport(struct sip_socket *socket, int transport) +{ + /* If the transport type changes, clear all socket data */ + if (socket->transport != transport) { + ast_sockaddr_setnull(&socket->address); + + socket->transport = transport; + socket->fd = -1; + + ao2_cleanup(socket->tcptls_session); + socket->tcptls_session = NULL; + } +} + +void sip_socket_copy(struct sip_socket *to_socket, const struct sip_socket *from_socket) +{ + ast_sockaddr_copy(&to_socket->address, &from_socket->address); + + to_socket->transport = from_socket->transport; + to_socket->fd = from_socket->fd; + + ao2_cleanup(to_socket->tcptls_session); + to_socket->tcptls_session = ao2_bump(from_socket->tcptls_session); +} + +/* Get socket for dialog, prepare if needed, and return file handle */ +static int sip_socket_build(struct sip_dialog *dialog) +{ + struct sip_tcptls_thread *thread; + struct ast_tcptls_session_instance *tcptls_session; + struct ast_tcptls_session_args *session_args; + pthread_t threadid; + + /* Check to see if a socket is already active */ + if (dialog->socket.transport == AST_TRANSPORT_UDP && dialog->socket.fd != -1) { + return 0; + } else if (dialog->socket.transport & (AST_TRANSPORT_TCP | AST_TRANSPORT_TLS) && + dialog->socket.tcptls_session && dialog->socket.tcptls_session->stream) { + return 0; + } + + if (dialog->proxy && dialog->proxy->transport) { + dialog->socket.transport = dialog->proxy->transport; + } + + if (dialog->socket.transport == AST_TRANSPORT_UDP) { + dialog->socket.fd = sip_socket_fd; + return 0; + } else if (!(dialog->socket.transport & (AST_TRANSPORT_TCP | AST_TRANSPORT_TLS))) { + ast_debug(1, "Invalid transport %s\n", ast_transport2str(dialog->socket.transport)); + return -1; + } + + thread = NULL; + + /* At this point we are dealing with a TCP/TLS connection + * 1. We need to check to see if a connection thread exists for this address, if so use that. + * 2. If a thread does not exist for this address, but the tcptls_session exists on the socket, the connection + * was closed. + * 3. If no tcptls_session thread exists for the address, and no tcptls_session already exists on the socket, + * create a new one and launch a new thread */ + if ((tcptls_session = sip_tcptls_session_find(&dialog->address))) { + dialog->socket.fd = ast_iostream_get_fd(tcptls_session->stream); + + ao2_cleanup(dialog->socket.tcptls_session); + dialog->socket.tcptls_session = tcptls_session; + return dialog->socket.fd; + } + + /* Create new session arguments for the client connection */ + if (!(session_args = ao2_alloc(sizeof(*session_args), sip_tcptls_session_args_destroy)) || + !(session_args->name = ast_strdup("SIP socket"))) { + goto cleanup; + } + + session_args->accept_fd = -1; + ast_sockaddr_setnull(&session_args->local_address); + ast_sockaddr_copy(&session_args->remote_address, &dialog->address); + + if (dialog->socket.transport == AST_TRANSPORT_TCP) { + /* If a bind address has been specified, use it */ + if (!ast_sockaddr_isnull(&sip_tcp_session.local_address)) { + ast_sockaddr_copy(&session_args->local_address, &sip_tcp_session.local_address); + } + } else if (dialog->socket.transport == AST_TRANSPORT_TLS) { + /* If a bind address has been specified, use it */ + if (!ast_sockaddr_isnull(&sip_tls_session.local_address)) { + ast_sockaddr_copy(&session_args->local_address, &sip_tls_session.local_address); + } + + /* If type is TLS, we need to create a tls_cfg for this session arg */ + if (!(session_args->tls_cfg = ast_calloc(1, sizeof(*session_args->tls_cfg)))) { + goto cleanup; + } + + memcpy(session_args->tls_cfg, &sip_tls_config, sizeof(*session_args->tls_cfg)); + + if (!(session_args->tls_cfg->certfile = ast_strdup(sip_tls_config.certfile)) || + !(session_args->tls_cfg->pvtfile = ast_strdup(sip_tls_config.pvtfile)) || + !(session_args->tls_cfg->cipher = ast_strdup(sip_tls_config.cipher)) || + !(session_args->tls_cfg->cafile = ast_strdup(sip_tls_config.cafile)) || + !(session_args->tls_cfg->capath = ast_strdup(sip_tls_config.capath))) { + goto cleanup; + } + + /* This host is used as the common name in ssl/tls */ + if (!ast_strlen_zero(dialog->to_host)) { + ast_copy_string(session_args->hostname, dialog->to_host, sizeof(session_args->hostname)); + } + } + + /* Reset tcp source port to zero to let system pick a random one */ + if (!ast_sockaddr_isnull(&session_args->local_address)) { + ast_sockaddr_set_port(&session_args->local_address, 0); + } + + ao2_cleanup(dialog->socket.tcptls_session); + + /* Create a client connection for address, this does not start the connection, just sets it up */ + if (!(dialog->socket.tcptls_session = ast_tcptls_client_create(session_args))) { + goto cleanup; + } + + dialog->socket.fd = ast_iostream_get_fd(dialog->socket.tcptls_session->stream); + + /* Client connections need to have the sip_tcptls_thread object created before the thread is detached. This + * ensures the alert_pipe is up before it will be used. Note that this function links the new threadinfo object + * into the sip_tcptls_threads container */ + if (!(thread = sip_tcptls_thread_alloc(dialog->socket.tcptls_session))) { + goto cleanup; + } + + /* Give the new thread a reference to the tcptls_session */ + ao2_ref(dialog->socket.tcptls_session, +1); + + if (ast_pthread_create_detached_background(&threadid, NULL, sip_tcptls_session_thread, dialog->socket.tcptls_session)) { + ao2_ref(dialog->socket.tcptls_session, -1); /* Take away the thread ref we just gave it */ + ast_log(LOG_WARNING, "Unable to launch thread '%s' for '%s'\n", + session_args->name, ast_sockaddr_stringify(&session_args->remote_address)); + goto cleanup; + } + + ast_set_qos(dialog->socket.fd, sip_config.tos_sip, sip_config.cos_sip, "SIP"); + return 0; + +cleanup: + ao2_cleanup(session_args); + + if (dialog->socket.tcptls_session) { + ast_tcptls_close_session_file(dialog->socket.tcptls_session); + dialog->socket.fd = -1; + + ao2_ref(dialog->socket.tcptls_session, -1); + dialog->socket.tcptls_session = NULL; + } + + if (thread) { + ao2_unlink(sip_tcptls_threads, thread); + } + + return -1; +} + +int sip_tcptls_thread_hash(const void *data, const int flags) +{ + const struct ast_tcptls_session_instance *tcptls_session; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + const struct sip_tcptls_thread *thread = (struct sip_tcptls_thread *) data; + + tcptls_session = thread->tcptls_session; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + tcptls_session = (const struct ast_tcptls_session_instance *) data; + } else { + return 0; + } + + return ast_sockaddr_hash(&tcptls_session->remote_address); +} + +int sip_tcptls_thread_cmp(void *data, void *arg, int flags) +{ + struct sip_tcptls_thread *thread; + struct ast_tcptls_session_instance *tcptls_session; + + thread = (struct sip_tcptls_thread *) data; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + struct sip_tcptls_thread *thread = (struct sip_tcptls_thread *) arg; + + tcptls_session = thread->tcptls_session; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + tcptls_session = (struct ast_tcptls_session_instance *) arg; + } else { + return 0; + } + + if (thread->tcptls_session == tcptls_session) { + return CMP_MATCH | CMP_STOP; + } + + return 0; +} + +static int sip_tcptls_thread_address_cmp(void *data, void *arg, int flags) +{ + struct sip_tcptls_thread *thread; + struct ast_sockaddr *address; + + thread = (struct sip_tcptls_thread *) data; + address = (struct ast_sockaddr *) arg; + + if (!ast_sockaddr_cmp(&thread->tcptls_session->remote_address, address)) { + return CMP_MATCH | CMP_STOP; + } + + return 0; +} + +/* Creates a sip_tcptls_thread object and links it into the sip_tcptls_threads table */ +static struct sip_tcptls_thread *sip_tcptls_thread_alloc(struct ast_tcptls_session_instance *tcptls_session) +{ + struct sip_tcptls_thread *thread; + + if (!tcptls_session || !(thread = ao2_alloc(sizeof(*thread), sip_tcptls_thread_destroy))) { + return NULL; + } + + thread->alert_pipe[0] = -1; + thread->alert_pipe[1] = -1; + + if (pipe(thread->alert_pipe)) { + ao2_ref(thread, -1); + ast_log(LOG_ERROR, "Could not create alert pipe: %s\n", strerror(errno)); + return NULL; + } + + thread->tcptls_session = ao2_bump(tcptls_session); + + ao2_link(sip_tcptls_threads, thread); + ao2_ref(thread, -1); + return thread; +} + +static void sip_tcptls_thread_destroy(void *data) +{ + struct sip_tcptls_thread *thread; + struct sip_tcptls_packet *packet; + + thread = (struct sip_tcptls_thread *) data; + + ast_debug(2, "Destroying TCP/TLS thread to '%s'\n", + ast_sockaddr_stringify(&thread->tcptls_session->remote_address)); + + if (thread->alert_pipe[0] != -1) { + close(thread->alert_pipe[0]); + + thread->alert_pipe[0] = -1; + } + + if (thread->alert_pipe[1] != -1) { + close(thread->alert_pipe[1]); + + thread->alert_pipe[1] = -1; + } + + while ((packet = AST_LIST_REMOVE_HEAD(&thread->packet_queue, next))) { + ao2_ref(packet, -1); + } + + ao2_cleanup(thread->tcptls_session); +} + +static void sip_tcptls_packet_destroy(void *data) +{ + struct sip_tcptls_packet *packet = (struct sip_tcptls_packet *) data; + + ast_free(packet->data); +} + +static void sip_tcptls_session_args_destroy(void *data) +{ + struct ast_tcptls_session_args *session_args = (struct ast_tcptls_session_args *) data; + + if (session_args->tls_cfg) { + ast_free(session_args->tls_cfg->certfile); + ast_free(session_args->tls_cfg->pvtfile); + ast_free(session_args->tls_cfg->cipher); + ast_free(session_args->tls_cfg->cafile); + ast_free(session_args->tls_cfg->capath); + + ast_ssl_teardown(session_args->tls_cfg); + } + + ast_free(session_args->tls_cfg); + ast_free((char *) session_args->name); +} + +/* Find thread for TCP/TLS session (based on IP/Port */ +static struct ast_tcptls_session_instance *sip_tcptls_session_find(struct ast_sockaddr *address) +{ + struct sip_tcptls_thread *thread; + struct ast_tcptls_session_instance *tcptls_session; + + if (!(thread = ao2_callback(sip_tcptls_threads, 0, sip_tcptls_thread_address_cmp, address))) { + return NULL; + } + + tcptls_session = ao2_bump(thread->tcptls_session); + ao2_ref(thread, -1); + return tcptls_session; +} + +/* Used to indicate to a tcptls thread that data is ready to be written */ +int sip_tcptls_session_write(struct ast_tcptls_session_instance *tcptls_session, const char *data) +{ + struct sip_tcptls_thread *thread; + struct sip_tcptls_packet *packet; + int alert; + + if (!tcptls_session || !tcptls_session->stream) { + return -1; + } + + ao2_lock(tcptls_session); + packet = NULL; + + if (!(thread = ao2_find(sip_tcptls_threads, tcptls_session, OBJ_SEARCH_KEY))) { + ast_log(LOG_ERROR, "Unable to find tcptls_session thread for '%s'\n", + ast_sockaddr_stringify(&tcptls_session->remote_address)); + goto error; + } + + if (!(packet = ao2_alloc(sizeof(*packet), sip_tcptls_packet_destroy))) { + goto error; + } + + if (!(packet->data = ast_str_create(strlen(data)))) { + goto error; + } + + ast_str_set(&packet->data, 0, "%s", data); + + /* Alert the tcptls thread handler that there is a packet to be sent. must lock the thread info object to + * guarantee control of the packet queue */ + ao2_lock(thread); + alert = TRUE; + + if (write(thread->alert_pipe[1], &alert, sizeof(alert)) == -1) { + ast_log(LOG_ERROR, "Write to alert pipe failed: %s\n", strerror(errno)); + ao2_unlock(thread); + goto error; + } + + /* It is safe to queue the frame after issuing the alert when we hold the threadinfo lock */ + AST_LIST_INSERT_TAIL(&thread->packet_queue, packet, next); + + ao2_unlock(thread); + ao2_unlock(tcptls_session); + + ao2_ref(thread, -1); + return 0; + +error: + ao2_cleanup(thread); + ao2_cleanup(packet); + + ao2_unlock(tcptls_session); + return -1; +} + +/* Check that a message received over TCP is complete */ +static int sip_tcptls_session_read_done(struct ast_tcptls_session_instance *tcptls_session, struct ast_str **read_buf) +{ + int read_len, content_len; + char *boundary, *header; + + /* Important pieces to search for in a SIP message are \r\n\r\n. This marks either The division between the + * headers and content or the end of the SIP message */ + if (!(boundary = strstr(ast_str_buffer(*read_buf), "\r\n\r\n"))) { + /* This is clearly a partial message since we haven't reached an end yet */ + return FALSE; + } + + boundary += 4; + + /* RFC 3261 18.3 - In the case of stream-oriented transports such as TCP, the Content-Length header field + * indicates the size of the body. The Content-Length header field MUST be used with stream oriented transports. + * Note: It is fine to prefix the search with a newline as a valid SIP message must have a request line as well */ + if (!(header = strcasestr(ast_str_buffer(*read_buf), "\nContent-Length:"))) { + return FALSE; + } + + /* If the header is after the boundary then there is no Content-Length in this message, delete it */ + if (header > boundary) { + ast_str_append(&tcptls_session->overflow_buf, 0, "%s", boundary); + ast_str_truncate(*read_buf, 0); + return FALSE; + } + + header += 16; + + /* Check that this is a complete header */ + if (!strchr(header, '\n') || sscanf(header, "%30d", &content_len) != 1) { + return FALSE; + } + + /* How much content is there after the boundary */ + read_len = ast_str_strlen(*read_buf) - (boundary - ast_str_buffer(*read_buf)); + + if (content_len < 0) { + return FALSE; + } else if (content_len == 0) { + /* We've definitely received an entire message. We need to check if there's also a fragment of another + * message in addition */ + if (read_len > 0) { + ast_str_append(&tcptls_session->overflow_buf, 0, "%s", boundary); + ast_str_truncate(*read_buf, ast_str_strlen(*read_buf) - read_len); + } + + return TRUE; + } + + /* Positive content length. Let's see what sort of message boundary we're dealing with */ + if (read_len < content_len) { + /* We don't have the full message boundary yet */ + return FALSE; + } + + /* We have the full message plus a fragment of a further message */ + if (read_len > content_len) { + ast_str_append(&tcptls_session->overflow_buf, 0, "%s", boundary + content_len); + ast_str_truncate(*read_buf, ast_str_strlen(*read_buf) - (read_len - content_len)); + } + + return TRUE; +} + +/* Read SIP request or response from a TCP/TLS connection */ +static int sip_tcptls_session_read(struct ast_tcptls_session_instance *tcptls_session, struct ast_str **read_buf, + int timeout) +{ + do { + if (!ast_str_strlen(tcptls_session->overflow_buf)) { + char data[4096]; + int res; + + res = ast_wait_for_input(ast_iostream_get_fd(tcptls_session->stream), timeout); + + if (res == -1) { + ast_debug(2, "TCP/TLS server wait for input returned: %d\n", res); + return -1; + } else if (res == 0) { + ast_debug(2, "TCP/TLS server timed out\n"); + return -1; + } + + res = ast_iostream_read(tcptls_session->stream, data, sizeof(data) - 1); + + if (res == -1) { + if (errno == EAGAIN || errno == EINTR) { + continue; + } + + ast_debug(2, "TCP/TLS server error when receiving data\n"); + return -1; + } else if (res == 0) { + ast_debug(2, "TCP/TLS server has shut down\n"); + return -1; + } + + data[res] = '\0'; + + ast_str_append(read_buf, 0, "%s", data); + } else { + ast_str_append(read_buf, 0, "%s", ast_str_buffer(tcptls_session->overflow_buf)); + ast_str_reset(tcptls_session->overflow_buf); + } + + if (ast_str_strlen(*read_buf) > SIP_PACKET_MAX_SIZE) { + ast_log(LOG_WARNING, + "Rejecting TCP/TLS packet from '%s' because it is way too large at %zu bytes\n", + ast_sockaddr_stringify(&tcptls_session->remote_address), ast_str_strlen(*read_buf)); + return -1; + } + } while (!sip_tcptls_session_read_done(tcptls_session, read_buf)); + + return 0; +} + +/* SIP TCP connection handler */ +static void *sip_tcptls_session_thread(void *data) +{ + static int unauthenticated_count = 0; /* Unauthenticated TCP sessions */ + struct ast_tcptls_session_instance *tcptls_session; + int keepalive, timeout_enabled, authenticated; + struct sip_tcptls_thread *thread; + struct pollfd fds[2]; + struct ast_tcptls_session_args *session_args; + struct sip_socket socket; + time_t connection_start; + RAII_VAR(struct ast_str *, read_buf, NULL, ast_free_ptr); + + tcptls_session = (struct ast_tcptls_session_instance *) data; + + session_args = NULL; + thread = NULL; + + timeout_enabled = TRUE; + authenticated = FALSE; + + /* If this is a server session, then the connection has already been setup. Check if the authlimit has been + * reached and if not create the threadinfo object so we can access this thread for writing, If this is a client + * connection more work must be done. + * 1. We own the parent session args for a client connection. This pointer needs to be held on to so we can + * decrement it's ref count on thread destruction. + * 2. The threadinfo object was created before this thread was launched, however it must be found within the + * sip_tcptls_threads table. + * 3. Last, the tcptls_session must be started */ + if (!tcptls_session->client) { + /* Server, incoming connections */ + if (ast_atomic_fetchadd_int(&unauthenticated_count, +1) >= sip_config.tcp_authentication_limit) { + ast_verb(3, "Too many unauthenticated TCP/TLS clients %d, limit is %d\n", + unauthenticated_count, sip_config.tcp_authentication_limit); + /* 'unauthenticated_count' is decremented in the cleanup code */ + goto cleanup; + } + + ast_debug(2, "Increasing unauthenticated TCP/TLS clients count to %d\n", unauthenticated_count); + ast_iostream_nonblock(tcptls_session->stream); + + if (!(thread = sip_tcptls_thread_alloc(tcptls_session))) { + goto cleanup; + } + + thread->threadid = pthread_self(); + + ao2_ref(thread, +1); + } else { + /* Client, outgoing connction */ + if (!(session_args = tcptls_session->parent) || + !(thread = ao2_find(sip_tcptls_threads, tcptls_session, OBJ_SEARCH_KEY))) { + goto cleanup; + } + + thread->threadid = pthread_self(); + + if (!(tcptls_session = ast_tcptls_client_start(tcptls_session))) { + goto cleanup; + } + } + + ast_sockaddr_copy(&socket.address, &tcptls_session->remote_address); + + socket.fd = ast_iostream_get_fd(tcptls_session->stream); + socket.transport = ast_iostream_get_ssl(tcptls_session->stream) ? AST_TRANSPORT_TLS : AST_TRANSPORT_TCP; + socket.tcptls_session = tcptls_session; + + keepalive = TRUE; + + if (setsockopt(ast_iostream_get_fd(tcptls_session->stream), SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive))) { + ast_log(LOG_ERROR, "Error enabling TCP keep-alive on socket: %s\n", strerror(errno)); + goto cleanup; + } + + /* We cannot let the stream exclusively wait for data to arrive. We have to wake up the task to send outgoing + * messages */ + ast_iostream_set_exclusive_input(tcptls_session->stream, FALSE); + ast_iostream_set_timeout_sequence(tcptls_session->stream, + ast_tvnow(), tcptls_session->client ? -1 : (sip_config.tcp_authentication_timeout * 1000)); + + ast_debug(2, "Starting thread for %s server\n", ast_iostream_get_ssl(tcptls_session->stream) ? "TLS" : "TCP"); + + /* Set up pollfd to watch for reads on both the socket and the alert_pipe */ + fds[0].fd = ast_iostream_get_fd(tcptls_session->stream); + fds[0].events = POLLIN | POLLPRI; + + fds[1].fd = thread->alert_pipe[0]; + fds[1].events = POLLIN | POLLPRI; + + if (!(read_buf = ast_str_create(4096))) { + goto cleanup; + } + + connection_start = time(NULL); + + for (;;) { + int timeout; + + if (!tcptls_session->client && authenticated && timeout_enabled) { + timeout_enabled = FALSE; + + ast_debug(2, "Disabling TCP/TLS timeout as client has authenticated\n"); + + ast_iostream_set_timeout_disable(tcptls_session->stream); + ast_atomic_fetchadd_int(&unauthenticated_count, -1); + + ast_debug(2, "Decreasing unauthenticated TCP/TLS clients count to %d\n", unauthenticated_count); + } + + /* Calculate the timeout for unauthenticated server sessions */ + if (!tcptls_session->client && timeout_enabled) { + timeout = (sip_config.tcp_authentication_timeout - (time(NULL) - connection_start)) * 1000; + + if (timeout < 0) { + ast_debug(2, "SIP %s server timed out\n", + ast_iostream_get_ssl(tcptls_session->stream) ? "TLS" : "TCP"); + goto cleanup; + } + } else { + timeout = -1; + } + + if (!ast_str_strlen(tcptls_session->overflow_buf)) { + int res = ast_poll(fds, ARRAY_LEN(fds), timeout); /* Polls for both socket and alert_pipe */ + + if (res == -1) { + ast_debug(2, "SIP %s server wait for input returned %d\n", + ast_iostream_get_ssl(tcptls_session->stream) ? "TLS" : "TCP", res); + goto cleanup; + } else if (res == 0) { + /* Timeout */ + ast_debug(2, "SIP %s server timed out\n", + ast_iostream_get_ssl(tcptls_session->stream) ? "TLS": "TCP"); + goto cleanup; + } + } + + /* Handle the socket event, check for both reads from the socket fd or TCP overflow buffer, and writes + * from alert_pipe fd */ + /* There is data on the socket to be read or we have a previous message */ + if (fds[0].revents || ast_str_strlen(tcptls_session->overflow_buf)) { + fds[0].revents = 0; + + if (sip_tcptls_session_read(tcptls_session, &read_buf, timeout)) { + goto cleanup; + } + + if (sip_handle_incoming(&socket, ast_str_buffer(read_buf))) { + authenticated = TRUE; + } + + ast_str_reset(read_buf); + } + + /* An event on the alert_pipe fd indicates there is data in the send queue to be sent */ + if (fds[1].revents) { + struct sip_tcptls_packet *packet; + int alert; + + fds[1].revents = 0; + + if (read(thread->alert_pipe[0], &alert, sizeof(alert)) == -1) { + ast_log(LOG_ERROR, "Read from alert pipe read failed: %s\n", strerror(errno)); + goto cleanup; + } + + ao2_lock(thread); + + if (!(packet = AST_LIST_REMOVE_HEAD(&thread->packet_queue, next))) { + ast_log(LOG_WARNING, + "Alert pipe indicated packet should be sent, but packet list is empty\n"); + } + + ao2_unlock(thread); + + if (!packet) { + goto cleanup; + } + + if (ast_iostream_write(tcptls_session->stream, + ast_str_buffer(packet->data), ast_str_strlen(packet->data)) == -1) { + ast_log(LOG_WARNING, "Write to TCP/TLS socket failed: %s\n", strerror(errno)); + ao2_ref(packet, -1); + + goto cleanup; + } + + ao2_ref(packet, -1); + } + } + + ast_debug(2, "Shutting down thread for %s server\n", + ast_iostream_get_ssl(tcptls_session->stream) ? "TLS" : "TCP"); + +cleanup: + if (tcptls_session && !tcptls_session->client && !authenticated) { + ast_atomic_fetchadd_int(&unauthenticated_count, -1); + + ast_debug(2, "Decreasing unauthenticated TCP/TLS clients count to %d\n", unauthenticated_count); + } + + if (thread) { + ao2_unlink(sip_tcptls_threads, thread); + ao2_ref(thread, -1); + } + + /* If client, we own the parent session arguments and must decrement ref */ + ao2_cleanup(session_args); + + if (tcptls_session) { + ao2_lock(tcptls_session); + ast_tcptls_close_session_file(tcptls_session); + tcptls_session->parent = NULL; + ao2_unlock(tcptls_session); + + ao2_ref(tcptls_session, -1); + } + + return NULL; +} + +/* Transmit SIP message */ +int sip_packet_send(struct sip_dialog *dialog, struct ast_str *data) +{ + const struct ast_sockaddr *address; + + if (sip_socket_build(dialog)) { + ast_debug(1, "Unable to create socket\n"); + return -1; + } + + if (dialog->socket.transport == AST_TRANSPORT_UDP && (dialog->nat_force_rport || dialog->rport_present)) { + address = &dialog->socket.address; + } else { + address = &dialog->address; + } + + if (sip_dialog_debug(dialog)) { + ast_verbose("<--- SIP write to %s://%s --->\n%s<------------->\n", + ast_transport2str(dialog->socket.transport), ast_sockaddr_stringify(address), + ast_str_buffer(data)); + } + + if (dialog->socket.transport == AST_TRANSPORT_UDP) { + if (ast_sendto(dialog->socket.fd, ast_str_buffer(data), ast_str_strlen(data), 0, address) == -1) { + switch (errno) { + case EBADF: /* Bad file descriptor, seems like this is generated when the host exist, but + * doesn't accept the UDP packet */ + case EHOSTUNREACH: /* Host unreachable */ + case ENETDOWN: /* Interface down */ + case ENETUNREACH: /* Network failure */ + case ECONNREFUSED: /* ICMP port unreachable */ + ast_debug(1, "Fatal network error sending packet: %s\n", strerror(errno)); + return -1; /* Don't bother with trying to send again */ + default: + ast_debug(1, "Non-fatal network error sending packet: %s\n", strerror(errno)); + return 0; /* Packet will be rescheduled */ + } + } + + return 0; + } else if (dialog->socket.transport & (AST_TRANSPORT_TCP | AST_TRANSPORT_TLS) && + dialog->socket.tcptls_session && dialog->socket.tcptls_session->stream) { + /* This only queues the packet to be sent, no write actually occurs */ + return sip_tcptls_session_write(dialog->socket.tcptls_session, ast_str_buffer(data)); + } else { + ast_debug(1, "Unable to send packet as dialog '%s' has transport %s\n", + dialog->call_id, ast_transport2str(dialog->socket.transport)); + } + + return -1; +} + +/* Transmit packet with retransmits */ +int sip_packet_send_reliable(struct sip_dialog *dialog, int method, uint32_t cseq, int code, struct ast_str *data, + int critical) +{ + struct sip_packet *packet; + int timer_a; + + if (method == SIP_METHOD_INVITE) { + /* Note this is a pending invite */ + dialog->pending_invite_cseq = cseq; + } + + if (!(packet = ao2_alloc_options(sizeof(*packet), sip_packet_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK))) { + return -1; + } + + if (!(packet->data = ast_str_create(ast_str_strlen(data)))) { + ao2_ref(packet, -1); + return -1; + } + + ast_str_set(&packet->data, 0, "%s", ast_str_buffer(data)); + + packet->method = method; + packet->cseq = cseq; + + /* If a code is supplied, then this is a response */ + if (code) { + packet->response = TRUE; + packet->code = code; + } else { + packet->response = FALSE; + } + + packet->critical = critical; + packet->dialog = ao2_bump(dialog); + packet->timer_t1 = dialog->timer_t1; /* Set SIP timer T1 */ + + packet->send_start = ast_tvnow(); /* Time packet was sent */ + packet->send_end = packet->timer_t1 * 64; /* Time in ms after packet->send_start to stop retransmission */ + + if (dialog->socket.transport & (AST_TRANSPORT_TCP | AST_TRANSPORT_TLS)) { + /* TCP does not need retransmits as that's built in, but with timeout set, we must give it the full + * timer_a treatment */ + packet->timeout = TRUE; + timer_a = packet->send_end; + } else { + timer_a = packet->timer_t1; + } + + /* Schedule retransmission */ + ao2_ref(packet, +1); + + if (sip_packet_send(packet->dialog, packet->data)) { /* Send packet */ + /* Serious network trouble, no need to try again */ + ast_log(LOG_ERROR, "Unable to send %s %s '%s'\n", + sip_method2str(packet->method), packet->response ? "response" : "request", + packet->dialog->call_id); + + /* Unlink and destroy the packet object */ + ao2_ref(packet, -1); + return -1; + } + + ast_debug(2, "Starting resend timer on %s %s '%s' in %dms\n", + sip_method2str(packet->method), packet->response ? "response" : "request", + packet->dialog->call_id, timer_a); + + if ((packet->resend_sched_id = ast_sched_add_variable(sip_sched_context, timer_a, sip_packet_resend, + packet, TRUE)) == -1) { + ao2_ref(packet, -1); + return -1; + } + + /* The retransmission list owns a packet ref */ + AST_LIST_INSERT_TAIL(&dialog->packet_queue, packet, next); + + /* This is odd, but since the retransmit timer starts at 500ms and the sip_monitor_thread thread only wakes up + * every 1000ms by default, we have to qualify the thread here to make sure it successfully detects this must be + * retransmitted in less time than it usually sleeps for. Otherwise it might not retransmit this packet for + * 1000ms */ + if (sip_monitor_threadid != AST_PTHREADT_NULL) { + pthread_kill(sip_monitor_threadid, SIGURG); + } + + return 0; +} + +/* Retransmit SIP message if no answer. Run by the sched thread */ +int sip_packet_resend(const void *data) +{ + struct sip_packet *packet; + struct ast_channel *channel; + int64_t send_time; /* How many ms until resend timeout is reached */ + + packet = (struct sip_packet *) data; + + /* Do not retransmit if time out is reached. This will be negative if the time between the first transmission + * and now is larger than our timeout period. This is a fail safe check in case the scheduler gets behind or + * the clock is changed */ + send_time = packet->send_end - ast_tvdiff_ms(ast_tvnow(), packet->send_start); + + if (send_time <= 0 || send_time > packet->send_end) { + packet->timeout = TRUE; + } + + /* Lock dialog */ + ao2_lock(packet->dialog); + + if (!packet->timeout) { + int timer_a; + + if (!packet->timer_a) { + packet->timer_a = 2; + } else { + packet->timer_a = 2 * packet->timer_a; + } + + /* For non-invites, a maximum of 4 secs */ + if (INT_MAX / packet->timer_a < packet->timer_t1) { + /* Uh Oh, we will have an integer overflow. Recalculate previous timeout time instead */ + packet->timer_a = packet->timer_a / 2; + } + + timer_a = packet->timer_t1 * packet->timer_a; /* Double each time */ + + if (packet->method != SIP_METHOD_INVITE && timer_a > 4000) { + timer_a = 4000; + } + + packet->attempts++; + + /* Reschedule re-transmit */ + ast_debug(2, "Rescheduling send attempt %d on %s %s '%s' to %dms (timer T1 is %dms)\n", + packet->attempts, sip_method2str(packet->method), packet->response ? "response" : "request", + packet->dialog->call_id, timer_a, packet->timer_t1); + + /* If there was no error during the network transmission, schedule the next retransmission, but if the + * next retransmission is going to be beyond our timeout period, mark the packet's stop_retrans value + * and set the next retransmit to be the exact time of timeout. This will allow any responses to the + * packet to be processed before the packet is destroyed on the next call to this function by the + * scheduler */ + if (sip_packet_send(packet->dialog, packet->data) != -1) { + if (timer_a >= send_time) { + packet->timeout = TRUE; + timer_a = send_time; + } + + ao2_unlock(packet->dialog); + return timer_a; + } + } + + /* At this point, either the packet's retransmission timed out, or there was a transmission error, either way + * destroy the scheduler item and this packet */ + packet->resend_sched_id = -1; /* Kill this scheduler item */ + + if (packet->method != SIP_METHOD_OPTIONS && packet->timeout) { + if (packet->critical || sip_debug) { /* Tell us if it's critical or if we're debugging */ + ast_log(LOG_WARNING, "Resend timeout on %s %s %s '%s' timed out after %dms with no response\n", + packet->critical ? "critical" : "non-critical", sip_method2str(packet->method), + packet->response ? "response" : "request", packet->dialog->call_id, + (int) ast_tvdiff_ms(ast_tvnow(), packet->send_start)); + } + } else if (!packet->timeout) { + ast_debug(1, "Cancelling resend on '%s' due to error\n", packet->dialog->call_id); + } + + ao2_unlock(packet->dialog); + channel = sip_dialog_lock_with_channel(packet->dialog); + + if (packet->critical) { + if (channel) { + ast_log(LOG_WARNING, "Hanging up %s '%s' no reply to our critical %s\n", + sip_method2str(packet->method), packet->dialog->call_id, + packet->response ? "response" : "request"); + + if (packet->response && packet->code >= 200 && packet->code < 300 && + packet->dialog->pending_invite_cseq && packet->dialog->established) { + /* This is a timeout of the 2XX response to a pending INVITE. In this case terminate the + * INVITE transaction just as if we received the ACK, but immediately hangup with a BYE + * (sip_hangup will send the BYE as long as the dialog is not set as "already_gone") + * RFC 3261 section 13.3.1.4. + * "If the server retransmits the 2xx response for 64*T1 seconds without receiving an + * ACK, the dialog is confirmed, but the session SHOULD be terminated. This is + * accomplished with a BYE, as described in Section 15." */ + packet->dialog->invite_state = SIP_INVITE_TERMINATED; + packet->dialog->pending_invite_cseq = 0; + } else { + /* There is nothing left to do, mark the dialog as gone */ + sip_dialog_set_already_gone(packet->dialog); + } + + if (!ast_channel_hangupcause(channel)) { + ast_channel_hangupcause_set(channel, AST_CAUSE_NO_USER_RESPONSE); + } + + ast_queue_hangup_with_cause(channel, AST_CAUSE_NO_USER_RESPONSE); + } else { + /* If no channel owner, destroy now */ + /* Let the sip_qualify_peer system expire packets when the timer expires */ + if (packet->method != SIP_METHOD_OPTIONS && packet->method != SIP_METHOD_REGISTER) { + sip_dialog_set_need_destroy(packet->dialog, "no response to critical packet"); + sip_dialog_set_already_gone(packet->dialog); + } + } + } else if (packet->dialog->pending_invite_cseq == packet->cseq) { + ast_log(LOG_WARNING, "Timeout on non-critical %s for '%s'\n", + sip_method2str(packet->method), packet->dialog->call_id); + + packet->dialog->invite_state = SIP_INVITE_TERMINATED; + packet->dialog->pending_invite_cseq = 0; + + sip_dialog_check_pending(packet->dialog); + } + + if (channel) { + ast_channel_unlock(channel); + ast_channel_unref(channel); + } + + if (packet->method == SIP_METHOD_BYE) { + /* We're not getting answers on SIP BYE's. Tear down the call anyway */ + sip_dialog_set_already_gone(packet->dialog); + sip_dialog_set_need_destroy(packet->dialog, "no response to BYE"); + } + + /* Unlink and destroy the packet object if it was in the queue */ + if (packet == AST_LIST_REMOVE(&packet->dialog->packet_queue, packet, next)) { + ao2_ref(packet, -1); + } + + /* If the packet was not in the list then we were in the process of stopping retransmisions while we were + * sending this retransmission */ + ao2_unlock(packet->dialog); + ao2_ref(packet, -1); + return 0; +} + +/* Run by the sched thread */ +static int __sip_dialog_cancel_resend(const void *data) +{ + struct sip_packet *packet = (struct sip_packet *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, packet->resend_sched_id, ao2_cleanup(packet)); + ao2_ref(packet, -1); + return 0; +} + +void sip_dialog_cancel_resend(struct sip_packet *packet) +{ + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_cancel_resend, ao2_bump(packet)) == -1) { + ao2_ref(packet, -1); + } +} + +static void sip_packet_destroy(void *data) +{ + struct sip_packet *packet = (struct sip_packet *) data; + + ao2_cleanup(packet->dialog); + ast_free(packet->data); +} + +/* Acknowledges receipt of a packet and stops retransmission, called with dialog locked */ +int sip_packet_ack(struct sip_dialog *dialog, int method, uint32_t cseq, int response) +{ + struct sip_packet *packet; + int acked; + + /* If we have an outbound proxy for this dialog, then delete it now since the rest of the requests in this + * dialog needs to follow the routing. If force is set, we will keep the outbound proxy during the whole dialog, + * regardless of what the SIP RFC says */ + if (dialog->proxy && !dialog->proxy->force) { + sip_proxy_set(dialog, NULL); + } + + acked = FALSE; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&dialog->packet_queue, packet, next) { + if (packet->cseq != cseq || packet->response != response) { + continue; + } + + if (packet->response || packet->method == method) { + if (!response && cseq == dialog->pending_invite_cseq) { + ast_debug(1, "Acked pending invite %u\n", dialog->pending_invite_cseq); + dialog->pending_invite_cseq = 0; + } + + if (packet->resend_sched_id != -1) { + ast_debug(2, "Cancelling resend of packet on '%s' because we received a reply\n", + packet->dialog->call_id); + sip_dialog_cancel_resend(packet); + } + + /* Unlink and destroy the packet object */ + AST_LIST_REMOVE_CURRENT(next); + ao2_ref(packet, -1); + + acked = TRUE; + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + ast_debug(1, "Match for '%s' of %s '%d %s' %s\n", + dialog->call_id, response ? "response" : "request", cseq, sip_method2str(method), + acked ? "found" : "not found"); + return acked; +} + +/* Acks receipt of packet, keep it around (used for provisional responses) */ +int sip_packet_semi_ack(struct sip_dialog *dialog, int method, uint32_t cseq, int response) +{ + struct sip_packet *packet; + int acked; + + acked = FALSE; + + AST_LIST_TRAVERSE(&dialog->packet_queue, packet, next) { + if (packet->cseq != cseq || packet->response != response) { + continue; + } + + if (packet->response || packet->method == method) { + if (packet->resend_sched_id != -1) { + ast_debug(2, "Cancelling resend of packet on '%s' because we received a reply\n", + packet->dialog->call_id); + } + + sip_dialog_cancel_resend(packet); + acked = TRUE; + break; + } + } + + ast_debug(1, "Stopping provisional resend of '%s' but retaining packet for %s '%d %s', match %s\n", + dialog->call_id, response ? "response" : "request", cseq, sip_method2str(method), + acked ? "found" : "not found"); + return acked; +} + +/* Pretend to ack all packets, called with dialog locked */ +void sip_packet_pretend_ack(struct sip_dialog *dialog) +{ + struct sip_packet *packet = NULL; + + while (!AST_LIST_EMPTY(&dialog->packet_queue)) { + if (packet == AST_LIST_FIRST(&dialog->packet_queue)) { + ast_log(LOG_WARNING, "Packet for '%s' is stuck in the queue\n", packet->dialog->call_id); + return; + } + + packet = AST_LIST_FIRST(&dialog->packet_queue); + sip_packet_ack(dialog, packet->method, packet->cseq, packet->response); + } +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/notifications.c asterisk-22.6.0/channels/sip/notifications.c --- asterisk-22.6.0.orig/channels/sip/notifications.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/notifications.c 2025-10-21 18:38:17.458217933 +1300 @@ -0,0 +1,217 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/astobj2.h" +#include "asterisk/tcptls.h" +#include "asterisk/channel.h" +#include "asterisk/callerid.h" +#include "asterisk/indications.h" +#include "asterisk/rtp_engine.h" +#include "asterisk/pbx.h" +#include "asterisk/udptl.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/session_timer.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/peers.h" +#include "include/registrations.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/mwi_subscriptions.h" +#include "include/notifications.h" +#include "include/fax.h" + +static void sip_notification_destroy(void *data); + +/* The SIP notification list */ +struct ao2_container *sip_notifications = NULL; + +int sip_notification_cmp(void *data, void *arg, int flags) +{ + struct sip_notification *notification; + const char *type; + + notification = (struct sip_notification *) data; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + struct sip_notification *notification = (struct sip_notification *) arg; + + type = notification->type; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + type = (const char *) arg; + } else { + return 0; + } + + if (!strcmp(notification->type, type)) { + return CMP_MATCH | CMP_STOP; + } + + return 0; +} +/* Add SIP notification to list of notifications we are responsible for */ +int sip_notification_build(const char *type, struct ast_variable *variables, int deprecated) +{ + struct sip_notification *notification; + struct ast_variable *variable; + + if ((notification = ao2_find(sip_notifications, type, OBJ_SEARCH_KEY))) { + ao2_ref(notification, -1); + return 0; + } + + if (!(notification = ao2_alloc(sizeof(*notification), sip_notification_destroy))) { + return -1; + } + + if (!(notification->type = ast_strdup(type))) { + ao2_ref(notification, -1); + return -1; + } + + for (variable = variables; variable; variable = variable->next) { + if (!deprecated && !strcasecmp(variable->name, "type")) { + continue; + } else if (!strcasecmp(variable->name, "content")) { + if (!notification->content) { + if (!(notification->content = ast_str_create(512))) { + ao2_ref(notification, -1); + return -1; + } + } + + if (ast_str_strlen(notification->content)) { + ast_str_append(¬ification->content, 0, "\r\n"); + } + + ast_str_append(¬ification->content, 0, "%s", variable->value); + } else { + char *name, *value; + + /* Old style has the variable name as the header */ + if (deprecated) { + name = ast_strdupa(variable->name); + value = ast_strdupa(variable->value); + } else if (!strcasecmp(variable->name, "header")) { + name = ast_strdupa(variable->value); + + if (!(value = strchr(name, ':'))) { + ast_log(LOG_WARNING, "Header '%s' is missing ':' at line %d\n", + name, variable->lineno); + continue; + } + + *value++ = '\0'; + value = ast_skip_blanks(value); + } else { + ast_log(LOG_WARNING, "Unknown option '%s' at line %d\n", + variable->name, variable->lineno); + continue; + } + + ast_unescape_semicolon(value); + + if (!strcasecmp(name, "Content-Length")) { + ast_log(LOG_WARNING, "Ignoring '%s' header at line %d\n", name, variable->lineno); + continue; + } + + ast_variable_list_append(¬ification->headers, ast_variable_new(name, value, "")); + } + } + + ao2_link(sip_notifications, notification); + ao2_ref(notification, -1); + + ast_debug(3, "Added notification type '%s'\n", notification->type); + return 0; +} + +static void sip_notification_destroy(void *data) +{ + struct sip_notification *notification; + + notification = (struct sip_notification *) data; + + ast_debug(3, "Destroying notification type '%s'\n", notification->type); + + ast_free(notification->type); + ast_variables_destroy(notification->headers); + ast_free(notification->content); +} + +int sip_notification_send(struct sip_notification *notification, struct sip_peer *peer) +{ + RAII_VAR(struct ast_channel *, dummy_channel, NULL, ast_channel_cleanup); + RAII_VAR(struct sip_dialog *, dialog, NULL, ao2_cleanup); + struct ast_variable *header; + + if (!(dummy_channel = ast_dummy_channel_alloc())) { + ast_log(LOG_WARNING, "Cannot allocate dummy channel for variables substitution\n"); + return -1; + } + + pbx_builtin_setvar_helper(dummy_channel, "PEERNAME", peer->name); + + if (!(dialog = sip_dialog_alloc(NULL, NULL, SIP_METHOD_NOTIFY, NULL, 0))) { + return -1; + } + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + return -1; + } + + sip_dialog_set_our_address(dialog); + dialog->outgoing = TRUE; + + for (header = notification->headers; header; header = header->next) { + char value[1024]; + + pbx_substitute_variables_helper(dummy_channel, header->value, value, sizeof(value)); + ast_variable_list_append(&dialog->notify_headers, ast_variable_new(header->name, value, "")); + } + + if (notification->content) { + char content[4096]; + + pbx_substitute_variables_helper(dummy_channel, ast_str_buffer(notification->content), + content, sizeof(content)); + ast_string_field_set(dialog, notify_content, content); + } + + sip_dialog_sched_destroy(dialog, dialog->timer_b); + sip_request_send_notify(dialog, SIP_INIT_REQUEST); + return 0; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/parking.c asterisk-22.6.0/channels/sip/parking.c --- asterisk-22.6.0.orig/channels/sip/parking.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/parking.c 2025-10-21 18:38:17.459217906 +1300 @@ -0,0 +1,339 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/channel.h" +#include "asterisk/indications.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_channels.h" +#include "asterisk/pbx.h" +#include "asterisk/parking.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/parking.h" + +/* Informtion required to park a call */ +struct sip_park_data { + struct sip_dialog *dialog; + struct ast_channel *channel; + unsigned int monitor:1; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(call_id); + AST_STRING_FIELD(local_tag); + AST_STRING_FIELD(remote_tag); + AST_STRING_FIELD(uniqueid); + ); +}; + +static void *sip_park_thread(void *data); +static void sip_park_event(void *data, struct stasis_subscription *sub, struct stasis_message *msg); +static void sip_park_send_notify(struct sip_park_data *park_data, enum ast_parked_call_event_type event_type, + int parking_exten, long unsigned int timeout); + +int sip_park_call(struct sip_dialog *dialog, struct sip_message *request, + const char *call_id, const char *local_tag, const char *remote_tag, int monitor) +{ + pthread_t threadid; + struct sip_park_data *park_data; + + if (!(park_data = ast_calloc_with_stringfields(1, struct sip_park_data, 512))) { + return -1; + } + + park_data->dialog = ao2_bump(dialog); + park_data->monitor = monitor; + + sip_message_copy(&dialog->initial_request, request); + + ast_string_field_set(park_data, call_id, call_id); + ast_string_field_set(park_data, local_tag, remote_tag); + ast_string_field_set(park_data, remote_tag, local_tag); + + if (ast_pthread_create_detached_background(&threadid, NULL, sip_park_thread, park_data)) { + ao2_ref(park_data->dialog, -1); + + ast_string_field_free_memory(park_data); + ast_free(park_data); + return -1; + } + + return 0; +} + +static void *sip_park_thread(void *data) +{ + struct sip_park_data *park_data; + struct sip_dialog *dialog; + struct ast_channel *channel, *bridge_channel; + struct stasis_subscription *subscription; + struct ast_exten *exten; + struct pbx_find_info find_info = {.stacklen = 0}; + int res; + + park_data = (struct sip_park_data *) data; + res = -1; + subscription = NULL; + + if (!(dialog = sip_dialog_find(park_data->call_id, park_data->local_tag, park_data->remote_tag))) { + ast_debug(1, "No dialog with Call-ID '%s' From: tag='%s' To: tag='%s'\n", + park_data->call_id, park_data->local_tag, park_data->remote_tag); + goto cleanup; + } + + ao2_lock(dialog); + + if (!(channel = dialog->channel)) { + ast_debug(1, "no owner channel\n"); + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + goto cleanup; + } + + ast_channel_ref(channel); + ao2_unlock(dialog); + ao2_ref(dialog, -1); + + if (!(bridge_channel = ast_channel_bridge_peer(channel))) { + ast_debug(1, "No bridge channel"); + ast_channel_unref(channel); + goto cleanup; + } + + /* Needed so that comebacktoorigin=yes will work */ + pbx_builtin_setvar_helper(bridge_channel, "BLINDTRANSFER", ast_channel_name(channel)); + pbx_builtin_setvar_helper(bridge_channel, "PARKINGLOT", ast_channel_parkinglot(channel)); + + park_data->channel = channel; + ast_channel_ref(channel); + ast_string_field_set(park_data, uniqueid, ast_channel_uniqueid(bridge_channel)); + + subscription = stasis_subscribe(ast_parking_topic(), sip_park_event, park_data); + exten = pbx_find_extension(NULL, NULL, &find_info, park_data->dialog->peer->context, "park", 1, NULL, NULL, + E_MATCH); + + ast_bridge_channel_write_park(ast_channel_internal_bridge_channel(channel), + ast_channel_uniqueid(bridge_channel), ast_channel_uniqueid(channel), + exten ? ast_get_extension_app_data(exten) : NULL); + + ast_channel_unref(channel); + ast_channel_unref(bridge_channel); + + sip_response_send(park_data->dialog, "202 Accepted", &park_data->dialog->initial_request); + res = 0; + +cleanup: + if (res) { + sip_response_send(park_data->dialog, "503 Service Unavailable", &park_data->dialog->initial_request); + sip_park_send_notify(park_data, PARKED_CALL_FAILED, 0, 0); + + if (subscription) { + stasis_unsubscribe(subscription); + } + + if (park_data->channel) { + ast_channel_unref(park_data->channel); + } + + ao2_ref(park_data->dialog, -1); + + ast_string_field_free_memory(park_data); + ast_free(park_data); + } + + return NULL; +} + +static void sip_park_event(void *data, struct stasis_subscription *subscription, struct stasis_message *message) +{ + struct sip_park_data *park_data = (struct sip_park_data *) data; + + if (stasis_message_type(message) != ast_parked_call_type()) { + return; + } + + if (!stasis_subscription_final_message(subscription, message)) { + struct ast_parked_call_payload *payload = stasis_message_data(message); + + if (strcmp(park_data->uniqueid, payload->parkee->base->uniqueid)) { + return; + } + + /* Send notification before hanging up the call so the dialog still exists on the phone */ + sip_park_send_notify(park_data, payload->event_type, payload->parkingspace, payload->timeout); + + if (payload->event_type == PARKED_CALL) { + ast_softhangup(park_data->channel, AST_SOFTHANGUP_EXPLICIT); + + ast_channel_unref(park_data->channel); + park_data->channel = NULL; + } + + /* If we are monitoring the park and it is still in use we keep the subscription instead of removing + * it below */ + if (park_data->monitor && + (payload->event_type == PARKED_CALL || payload->event_type == PARKED_CALL_REMINDER)) { + return; + } + } + + stasis_unsubscribe(subscription); + ast_channel_cleanup(park_data->channel); + ao2_ref(park_data->dialog, -1); + + ast_string_field_free_memory(park_data); + ast_free(park_data); +} + +static void sip_park_send_notify(struct sip_park_data *park_data, enum ast_parked_call_event_type event_type, + int parking_exten, long unsigned int timeout) +{ + struct ast_str *content = ast_str_alloca(4096); + + if (park_data->monitor) { + struct sip_message request; + const char *event, *from_domain; + + if (event_type == PARKED_CALL) { + event = "parked"; + } else if (event_type == PARKED_CALL_REMINDER) { + event = "reminder"; + } else if (event_type == PARKED_CALL_UNPARKED) { + event = "retrieved"; + } else if (event_type == PARKED_CALL_TIMEOUT) { + event = "forwarded"; + } else if (event_type == PARKED_CALL_GIVEUP) { + event = "abandoned"; + } else if (event_type == PARKED_CALL_FAILED) { + event = "error"; + } else { + return; + } + + ao2_lock(park_data->dialog); + + if (!park_data->dialog->established) { + park_data->dialog->outgoing = TRUE; + park_data->dialog->established = TRUE; + park_data->dialog->subscribe_event = SIP_SUBSCRIBE_DIALOG; + park_data->dialog->expires = timeout; + + sip_message_build_initial_request(&request, park_data->dialog, SIP_METHOD_NOTIFY, NULL); + } else { + sip_message_build_request(&request, park_data->dialog, SIP_METHOD_NOTIFY, 0, TRUE); + } + + sip_message_add_header(&request, "Event", "refer"); /* Shouldn't this be dialog? */ + + if (event_type == PARKED_CALL || event_type == PARKED_CALL_REMINDER) { + sip_message_build_header(&request, "Subscription-State", "active;expires=%d", + park_data->dialog->expires); + } else { + sip_message_add_header(&request, "Subscription-State", "terminated;reason=noresource"); + } + + sip_message_add_header(&request, "Content-Type", "application/dialog-info+xml"); + from_domain = S_OR(park_data->dialog->from_domain, + ast_sockaddr_stringify_host_remote(&park_data->dialog->our_address)); + + /* "parmams" is a typo in the the Cisco API, duh */ + ast_str_append(&content, 0, "\n" + "\n", + park_data->dialog->outgoing_cseq, parking_exten, from_domain); + ast_str_append(&content, 0, " \n", parking_exten); + + if (event_type == PARKED_CALL || event_type == PARKED_CALL_REMINDER) { + ast_str_append(&content, 0, " confirmed\n"); + } else { + ast_str_append(&content, 0, " terminated\n"); + } + + ast_str_append(&content, 0, " %s\n", event); + ast_str_append(&content, 0, " sip:%d@%s\n", + parking_exten, from_domain); + ast_str_append(&content, 0, " sip:%d@%s\n", + parking_exten, from_domain); + ast_str_append(&content, 0, " \n" + "\n"); + + sip_message_add_content(&request, ast_str_buffer(content)); + sip_message_send(park_data->dialog, &request, SIP_SEND_RELIABLE, park_data->dialog->outgoing_cseq++); + + ao2_unlock(park_data->dialog); + } else { + struct sip_dialog *dialog; + + if (!(dialog = sip_dialog_alloc(NULL, &park_data->dialog->socket, SIP_METHOD_REFER, NULL, 0))) { + return; + } + + sip_dialog_copy(dialog, park_data->dialog); + + ast_str_append(&content, 0, "\n" + "\n" + " \n" + " notify_display\n" + " \n"); + ast_str_append(&content, 0, " %s\n", park_data->call_id); + ast_str_append(&content, 0, " %s\n", park_data->remote_tag); + ast_str_append(&content, 0, " %s\n", park_data->local_tag); + ast_str_append(&content, 0, " \n"); + + if (event_type == PARKED_CALL) { + ast_str_append(&content, 0, " \200! %d\n", parking_exten); + } else { + ast_str_append(&content, 0, " \200^\n"); + } + + ast_str_append(&content, 0, " %d\n", + park_data->dialog->peer->park_notify_timer); + ast_str_append(&content, 0, " 0\n" + " 1\n" + " \n" + "\n"); + + sip_request_send_refer_with_content(dialog, "application/x-cisco-remotecc-request+xml", + ast_str_buffer(content)); + ao2_ref(dialog, -1); + } +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/peers.c asterisk-22.6.0/channels/sip/peers.c --- asterisk-22.6.0.orig/channels/sip/peers.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/peers.c 2025-10-21 18:38:17.460217879 +1300 @@ -0,0 +1,3379 @@ + /* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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 + extended + ***/ + +#include "asterisk.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/sched.h" +#include "asterisk/netsock2.h" +#include "asterisk/tcptls.h" +#include "asterisk/channel.h" +#include "asterisk/callerid.h" +#include "asterisk/indications.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_endpoints.h" +#include "asterisk/pbx.h" +#include "asterisk/devicestate.h" +#include "asterisk/astdb.h" +#include "asterisk/acl.h" +#include "asterisk/mwi.h" +#include "asterisk/app.h" +#include "asterisk/dnsmgr.h" +#include "asterisk/udptl.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/authentication_realms.h" +#include "include/peers.h" +#include "include/realtime.h" +#include "include/registrations.h" +#include "include/mwi_subscriptions.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/events.h" +#include "include/conference.h" +#include "include/callback.h" + +static void sip_peer_mwi_event(void *data, struct stasis_subscription *subscription, struct stasis_message *message); +static void sip_peer_dnsmgr_lookup(struct ast_sockaddr *old_address, struct ast_sockaddr *new_address, void *data); + +static void sip_peer_astdb_delete(struct sip_peer *peer); +static void sip_peer_astdb_load(struct sip_peer *peer); + +static int sip_peer_get_messages(struct sip_peer *peer, int *new_messages, int *old_messages); +static void sip_peer_set_defaults(struct sip_peer *peer); + +static void sip_variable_build(struct sip_peer *peer, const char *config, int lineno); + +static void sip_mailbox_build(struct sip_peer *peer, const char *config, int lineno); +static void sip_mailbox_destroy(struct sip_mailbox *mailbox); + +static void sip_alias_build(struct sip_peer *peer, const char *config, int lineno, int *line_index); +static void sip_alias_destroy(struct sip_alias *alias); + +static void sip_subscription_build(struct sip_peer *peer, const char *config, int lineno); +static void sip_subscription_destroy(struct sip_subscription *subscription); + +/* The peer list */ +struct ao2_container *sip_peers = NULL; +struct ao2_container *sip_peer_addresses = NULL; + +/* These counters are not handled in a thread-safe way ast_atomic_fetchadd_int should be used to modify these values */ +int sip_peer_static_count = 0; /* Static peers */ +int sip_peer_realtime_count = 0; /* Realtime peers */ + +/* An invalid peer to be used when authentication should fail */ +struct sip_peer *sip_invalid_peer = NULL; + +/* A per-thread buffer for options to string conversion */ +AST_THREADSTORAGE(sip_peer_status2str_buf); + +int sip_peer_hash(const void *data, const int flags) +{ + const char *name; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + const struct sip_peer *peer = (struct sip_peer *) data; + + name = peer->name; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + name = (const char *) data; + } else { + return 0; + } + + return ast_str_case_hash(name); +} + +int sip_peer_cmp(void *data, void *arg, int flags) +{ + struct sip_peer *peer; + const char *name; + + peer = (struct sip_peer *) data; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + struct sip_peer *peer = (struct sip_peer *) arg; + + name = peer->name; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + name = (const char *) arg; + } else { + return 0; + } + + if (!strcasecmp(peer->name, name)) { + return CMP_MATCH | CMP_STOP; + } + + return 0; +} + +int sip_peer_address_hash(const void *data, const int flags) +{ + const struct ast_sockaddr *address; + int hash; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + struct sip_peer *peer = (struct sip_peer *) data; + + address = &peer->address; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + address = (const struct ast_sockaddr *) data; + } else { + return 0; + } + + if ((hash = ast_sockaddr_hash(address)) < 0) { + hash = -hash; + } + + return hash; +} + +int sip_peer_address_cmp(void *data, void *arg, int flags) +{ + struct sip_peer *peer; + const struct ast_sockaddr *address; + + peer = (struct sip_peer *) data; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + struct sip_peer *peer = (struct sip_peer *) arg; + + address = &peer->address; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + address = (const struct ast_sockaddr *) arg; + } else { + return 0; + } + + if (!ast_sockaddr_cmp(&peer->address, address)) { + return CMP_MATCH | CMP_STOP; + } + + return 0; +} + +struct sip_peer *sip_peer_find(const char *name, int realtime, int devicestate_only) +{ + struct sip_peer *peer = ao2_find(sip_peers, name, OBJ_SEARCH_KEY); + + if (!peer && (realtime || devicestate_only)) { + peer = sip_realtime_load(name, NULL, devicestate_only); + } + + return peer; +} + +struct sip_peer *sip_peer_address_find(const struct ast_sockaddr *address, int transport, int realtime, + int devicestate_only) +{ + struct sip_peer *peer; + + if ((peer = ao2_find(sip_peer_addresses, address, OBJ_SEARCH_KEY))) { + /* Check that transport matches */ + if (!(peer->transports & transport)) { + ao2_ref(peer, -1); + return NULL; + } + } + + if (!peer && (realtime || devicestate_only)) { + peer = sip_realtime_load(NULL, address, devicestate_only); + } + + return peer; +} + +int sip_peer_set_removed(void *data, void *arg, int flags) +{ + struct sip_peer *peer = (struct sip_peer *) data; + + peer->removed = TRUE; + return CMP_MATCH; +} + +/* Used with ao2_callback to unlink/delete peers that have been removed */ +int sip_peer_unlink(void *data, void *arg, int flags) +{ + struct sip_peer *peer = (struct sip_peer *) data; + + if (!peer->removed) { + return 0; + } + + AST_SCHED_DEL_UNREF(sip_sched_context, peer->qualify_sched_id, ao2_cleanup(peer)); + AST_SCHED_DEL_UNREF(sip_sched_context, peer->register_expires_sched_id, ao2_cleanup(peer)); + + if (peer->dnsmgr) { + ast_dnsmgr_release(peer->dnsmgr); + peer->dnsmgr = NULL; + ao2_ref(peer, -1); + } + + if (!ast_sockaddr_isnull(&peer->address)) { + ao2_unlink(sip_peer_addresses, peer); + } + + return CMP_MATCH; +} + +/* Destroy peer object from memory */ +static void sip_peer_destroy(void *data) +{ + struct sip_peer *peer; + struct sip_mailbox *mailbox; + struct sip_subscription *subscription; + struct sip_alias *alias; + struct sip_selected *selected; + + peer = (struct sip_peer *) data; + + ast_debug(1, "Destroying peer '%s'\n", peer->name); + + /* Remove any mailbox event subscriptions for this peer before we destroy anything. An event subscription + * callback may be happening right now */ + while ((mailbox = AST_LIST_REMOVE_HEAD(&peer->mailboxes, next))) { + sip_mailbox_destroy(mailbox); + } + + while ((subscription = AST_LIST_REMOVE_HEAD(&peer->subscriptions, next))) { + sip_subscription_destroy(subscription); + } + + while ((alias = AST_LIST_REMOVE_HEAD(&peer->aliases, next))) { + sip_alias_destroy(alias); + } + + while ((selected = AST_LIST_REMOVE_HEAD(&peer->selected, next))) { + sip_selected_destroy(selected); + } + + ao2_cleanup(peer->proxy); + + if (peer->qualify_dialog) { + sip_dialog_unlink(peer->qualify_dialog); + ao2_ref(peer->qualify_dialog, -1); + } + + if (peer->mwi_dialog) { /* We have an active subscription, delete it */ + sip_dialog_unlink(peer->mwi_dialog); + ao2_ref(peer->mwi_dialog, -1); + } + + if (peer->feature_events_dialog) { /* We have an active subscription, delete it */ + sip_dialog_unlink(peer->feature_events_dialog); + ao2_ref(peer->feature_events_dialog, -1); + } + + if (peer->callback) { + sip_callback_destroy(peer->callback); + } + + if (peer->channel_variables) { + ast_variables_destroy(peer->channel_variables); + } + + sip_route_destroy(&peer->path); + + ast_free_acl_list(peer->address_acl); + ast_free_acl_list(peer->contact_acl); + ast_free_acl_list(peer->direct_media_acl); + + if (peer->realtime && !sip_config.realtime_cache_peer) { + ast_atomic_fetchadd_int(&sip_peer_realtime_count, -1); + } else { + ast_atomic_fetchadd_int(&sip_peer_static_count, -1); + } + + ao2_cleanup(peer->authentication_realms); + ao2_cleanup(peer->socket.tcptls_session); + + peer->named_callgroups = ast_unref_namedgroups(peer->named_callgroups); + peer->named_pickupgroups = ast_unref_namedgroups(peer->named_pickupgroups); + + ast_string_field_free_memory(peer); + ao2_cleanup(peer->format_cap); + + ast_endpoint_shutdown(peer->endpoint); +} + +/* Build peer from configuration (file or realtime static/dynamic) */ +struct sip_peer *sip_peer_build(const char *name, struct ast_variable *variables, int realtime, int devicestate_only) +{ + struct ast_variable *variable; + struct sip_peer *peer; + char *host; + int found, port, subscribe_acl_change, header_count, line_index; + time_t expires; + + found = FALSE; + + if (!realtime || sip_config.realtime_cache_peer) { + /* Note we do NOT use sip_peer_find here, to avoid realtime recursion */ + if ((peer = ao2_find(sip_peers, name, OBJ_SEARCH_KEY | OBJ_UNLINK))) { + /* We've unlinked the peer from the peers container but not unlinked from the sip_peer_addresses + * container yet this leads to a wrong refcounter and the peer object is never destroyed */ + if (!ast_sockaddr_isnull(&peer->address)) { + ao2_unlink(sip_peer_addresses, peer); + } + + ast_format_cap_remove_by_type(peer->format_cap, AST_MEDIA_TYPE_UNKNOWN); + + ast_free_acl_list(peer->address_acl); + peer->address_acl = NULL; + + ast_free_acl_list(peer->contact_acl); + peer->contact_acl = NULL; + + ast_free_acl_list(peer->direct_media_acl); + peer->direct_media_acl = NULL; + + /* If we have channel variables, remove them (reload) */ + if (peer->channel_variables) { + ast_variables_destroy(peer->channel_variables); + peer->channel_variables = NULL; + } + + /* If we have realm authentication information, remove them (reload) */ + if (peer->authentication_realms) { + ao2_ref(peer->authentication_realms, -1); + peer->authentication_realms = NULL; + } + + found = TRUE; + } + } else { + peer = NULL; + } + + if (!peer) { + if (!(peer = ao2_alloc(sizeof(*peer), sip_peer_destroy))) { + return NULL; + } + + if (!(peer->endpoint = ast_endpoint_create("SIP", name))) { + ao2_ref(peer, -1); + return NULL; + } + + if (!(peer->format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + ao2_ref(peer, -1); + return NULL; + } + + if (ast_string_field_init(peer, 512)) { + ao2_ref(peer, -1); + return NULL; + } + + if (realtime && !sip_config.realtime_cache_peer) { + ast_atomic_fetchadd_int(&sip_peer_realtime_count, +1); + } else { + ast_atomic_fetchadd_int(&sip_peer_static_count, +1); + } + + peer->register_expires_sched_id = -1; + peer->qualify_sched_id = -1; + } + + /* Note that our peer HAS had its reference count increased */ + ast_string_field_set(peer, name, name); /* In case the case of the peer name has changed, update the name */ + sip_peer_set_defaults(peer); + + /* Clear the transport information. We will detect if a default value is required after parsing the config */ + peer->default_transport = 0; + peer->transports = 0; + peer->port_in_uri = FALSE; + + if (!devicestate_only) { + struct sip_mailbox *mailbox; + struct sip_alias *alias; + struct sip_subscription *subscription; + + /* Clear named callgroup and named pickup group container */ + peer->named_callgroups = ast_unref_namedgroups(peer->named_callgroups); + peer->named_pickupgroups = ast_unref_namedgroups(peer->named_pickupgroups); + + AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, next) { + mailbox->removed = TRUE; + } + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + alias->removed = TRUE; + } + + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, next) { + subscription->removed = TRUE; + } + } + + host = NULL; + port = 0; + expires = 0; + subscribe_acl_change = FALSE; + header_count = 0; + line_index = 2; /* First non-primary line */ + + for (variable = variables; variable; variable = variable->next) { + int unknown = FALSE; + + if (!devicestate_only) { + if (!strcasecmp(variable->name, "type")) { + if (strcasecmp(variable->value, "peer")) { + ast_log(LOG_WARNING, "Option '%s=%s' is deprecated, use '%s' instead at line %d\n", + variable->name, variable->value, "peer", variable->lineno); + } + } else if (!strcasecmp(variable->name, "transport")) { + char *transport, *option; + + peer->transports = 0; + peer->default_transport = 0; + + transport = ast_strdupa(variable->value); + + while ((option = strsep(&transport, ","))) { + if (!strcasecmp(option, "udp")) { + peer->transports |= AST_TRANSPORT_UDP; + } else if (!strcasecmp(option, "tcp")) { + if (!sip_config.tcp_enabled) { + ast_log(LOG_NOTICE, "Invalid transport '%s' when '%senable=no' at line %d\n", + option, option, variable->lineno); + } + + peer->transports |= AST_TRANSPORT_TCP; + } else if (sip_tls_config.enabled && !strcasecmp(option, "tls")) { + if (!sip_tls_config.enabled) { + ast_log(LOG_NOTICE, "Invalid transport '%s' when '%senable=no' at line %d\n", + option, option, variable->lineno); + } + + peer->transports |= AST_TRANSPORT_TLS; + } else { + ast_log(LOG_NOTICE, + "Invalid option '%s=%s' at line %d\n", + variable->name, option, variable->lineno); + } + + /* The first transport listed should be default outbound */ + if (!peer->default_transport) { + peer->default_transport = peer->transports; + } + } + } else if (!strcasecmp(variable->name, "nat")) { + char *nat, *option; + + peer->nat_force_rport = FALSE; + peer->nat_auto_rport = FALSE; + peer->nat_rtp = FALSE; + peer->nat_auto_rtp = FALSE; + + nat = ast_strdupa(variable->value); + + while ((option = strsep(&nat, ","))) { + if (ast_false(option)) { + peer->nat_force_rport = FALSE; + peer->nat_auto_rport = FALSE; + peer->nat_rtp = FALSE; + peer->nat_auto_rtp = FALSE; + } else if (ast_true(option)) { + ast_log(LOG_WARNING, + "Option '%s=%s' is no longer supported, use 'force_rport,rtp' instead at line %d\n", + variable->name, option, variable->lineno); + } else if (!strcasecmp(option, "force_rport") && !peer->nat_auto_rport) { + peer->nat_force_rport = TRUE; + } else if (!strcasecmp(option, "auto_force_rport")) { + peer->nat_auto_rport = TRUE; + peer->nat_force_rport = FALSE; /* In case someone did something dumb + * like nat=force_rport,auto_force_rport */ + } else if ((!strcasecmp(option, "comedia") || !strcasecmp(option, "rtp")) + && !peer->nat_auto_rtp) { + if (strcasecmp(option, "rtp")) { + ast_log(LOG_WARNING, + "Option '%s=%s' is deprecated, use '%s' instead at line %d\n", + variable->name, option, "rtp", variable->lineno); + } + + peer->nat_rtp = TRUE; + } else if (!strcasecmp(option, "auto_comedia") || !strcasecmp(option, "auto_rtp")) { + if (strcasecmp(option, "auto_rtp")) { + ast_log(LOG_WARNING, + "Option '%s=%s' is deprecated, use '%s' instead at line %d\n", + variable->name, option, "auto_rtp", variable->lineno); + } + + peer->nat_auto_rtp = TRUE; + peer->nat_rtp = FALSE; /* In case someone did something dumb + * like nat=rtp,auto_rtp */ + } else { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, option, variable->lineno); + } + } + } else if (!strcasecmp(variable->name, "remotesecret")) { + ast_string_field_set(peer, remote_secret, variable->value); + } else if (!strcasecmp(variable->name, "secret")) { + ast_string_field_set(peer, secret, variable->value); + } else if (!strcasecmp(variable->name, "description")) { + ast_string_field_set(peer, description, variable->value); + } else if (!strcasecmp(variable->name, "md5secret")) { + ast_string_field_set(peer, md5_secret, variable->value); + } else if (!strcasecmp(variable->name, "auth") || !strcasecmp(variable->name, "authentication")) { + if (strcasecmp(variable->name, "authentication")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "authentication", variable->lineno); + } + + if (!peer->authentication_realms) { + if (!(peer->authentication_realms = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, + 0, NULL, sip_authentication_realm_cmp))) { + continue; + } + } + + sip_authentication_realm_build(peer->authentication_realms, variable->value, variable->lineno); + } else if (!strcasecmp(variable->name, "callerid")) { + char caller_name[AST_MAX_EXTENSION], caller_number[AST_MAX_EXTENSION]; + + ast_callerid_split(variable->value, caller_name, sizeof(caller_name), + caller_number, sizeof(caller_number)); + + ast_string_field_set(peer, caller_name, caller_name); + ast_string_field_set(peer, caller_number, caller_number); + } else if (!strcasecmp(variable->name, "cid_name") || + !strcasecmp(variable->name, "callername")) { + if (strcasecmp(variable->name, "callername")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "callername", variable->lineno); + } + + ast_string_field_set(peer, caller_name, variable->value); + } else if (!strcasecmp(variable->name, "cid_number") || + !strcasecmp(variable->name, "callernumber")) { + if (strcasecmp(variable->name, "callernumber")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "callernumber", variable->lineno); + } + + ast_string_field_set(peer, caller_number, variable->value); + } else if (!strcasecmp(variable->name, "cid_tag") || !strcasecmp(variable->name, "callertag")) { + if (strcasecmp(variable->name, "callertag")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "callertag", variable->lineno); + } + + ast_string_field_set(peer, caller_tag, variable->value); + } else if (!strcasecmp(variable->name, "context")) { + ast_string_field_set(peer, context, variable->value); + } else if (!strcasecmp(variable->name, "subscribecontext")) { + ast_string_field_set(peer, subscribe_context, variable->value); + } else if (!strcasecmp(variable->name, "messagecontext")) { + ast_string_field_set(peer, message_context, variable->value); + } else if (!strcasecmp(variable->name, "fromuser")) { + ast_string_field_set(peer, from_user, variable->value); + } else if (!strcasecmp(variable->name, "fromdomain")) { + char *from_domain = ast_strdupa(variable->value); + + if (sip_parse_port(from_domain, &peer->from_domain_port)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } else { + ast_string_field_set(peer, from_domain, from_domain); + + if (!peer->from_domain_port) { + peer->from_domain_port = SIP_STANDARD_PORT; + } + } + } else if (!strcasecmp(variable->name, "usereqphone")) { + peer->user_eq_phone = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "outboundproxy") || + !strcasecmp(variable->name, "proxy")) { + struct sip_proxy *proxy; + + if (strcasecmp(variable->name, "proxy")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "proxy", variable->lineno); + } + + if (!(proxy = sip_proxy_build(variable->value, variable->lineno, peer->proxy))) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + continue; + } + + ao2_cleanup(peer->proxy); + peer->proxy = proxy; + } else if (!strcasecmp(variable->name, "host")) { + if (!strcasecmp(variable->value, "dynamic")) { + /* They'll register with us */ + if ((!found && !sip_config.realtime_cache_peer) || !peer->host_dynamic) { + /* Initialize stuff if this is a new peer, or if it used to + * not be dynamic before the reload */ + ast_string_field_set(peer, host, NULL); + ast_sockaddr_setnull(&peer->address); + } + + peer->host_dynamic = TRUE; + } else { + /* Non-dynamic. Make sure we become that way if we're not */ + AST_SCHED_DEL_UNREF(sip_sched_context, peer->register_expires_sched_id, + ao2_cleanup(peer)); + + peer->host_dynamic = FALSE; + host = ast_strdupa(variable->value); + } + } else if (!strcasecmp(variable->name, "port")) { + peer->port_in_uri = TRUE; + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, &port, 0, USHRT_MAX)) { + if (realtime) { + peer->port_in_uri = FALSE; + } else { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } + } else if (!strcasecmp(variable->name, "permit") || !strcasecmp(variable->name, "deny") || + !strcasecmp(variable->name, "acl")) { + int ha_error = 0; + + ast_append_acl(variable->name, variable->value, &peer->address_acl, &ha_error, + &subscribe_acl_change); + + if (ha_error) { + ast_log(LOG_ERROR, "Invalid option '%s=%s', removing peer at line %d\n", + variable->name, variable->value, variable->lineno); + ao2_ref(peer, -1); + return NULL; + } + } else if (!strcasecmp(variable->name, "contactpermit") || + !strcasecmp(variable->name, "contactdeny") || + !strcasecmp(variable->name, "contactacl")) { + int ha_error = 0; + + ast_append_acl(variable->name + 7, variable->value, &peer->contact_acl, &ha_error, + &subscribe_acl_change); + + if (ha_error) { + ast_log(LOG_ERROR, "Invalid option '%s=%s', removing peer at line %d\n", + variable->name, variable->value, variable->lineno); + ao2_ref(peer, -1); + return NULL; + } + } else if (!strcasecmp(variable->name, "directmediapermit") || + !strcasecmp(variable->name, "directmediadeny") || + !strcasecmp(variable->name, "directmediaacl")) { + int ha_error = 0; + + ast_append_acl(variable->name + 11, + variable->value, &peer->direct_media_acl, &ha_error, &subscribe_acl_change); + + if (ha_error) { + ast_log(LOG_ERROR, "Invalid option '%s=%s', removing peer at line %d\n", + variable->name, variable->value, variable->lineno); + ao2_ref(peer, -1); + return NULL; + } + } else if (!strcasecmp(variable->name, "callingpres") || + !strcasecmp(variable->name, "callerpresentation")) { + int caller_presentation; + + if (strcasecmp(variable->name, "callerpresentation")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "callerpresentation", variable->lineno); + } + + if ((caller_presentation = ast_parse_caller_presentation(variable->value)) == -1) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } else { + peer->caller_presentation = caller_presentation; + } + } else if (!strcasecmp(variable->name, "defaultuser") || + !strcasecmp(variable->name, "username")) { + if (strcasecmp(variable->name, "username")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "username", variable->lineno); + } + + ast_string_field_set(peer, authorization_user, variable->value); + } else if (!strcasecmp(variable->name, "tonezone")) { + struct ast_tone_zone *tone_zone; + + if ((tone_zone = ast_get_indication_zone(variable->value))) { + ast_tone_zone_unref(tone_zone); + ast_string_field_set(peer, tone_zone, variable->value); + } else { + ast_log(LOG_ERROR, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "language")) { + ast_string_field_set(peer, language, variable->value); + } else if (!strcasecmp(variable->name, "amaflags")) { + if ((peer->amaflags = ast_channel_string2amaflag(variable->value)) == -1) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + peer->amaflags = 0; + } + } else if (!strcasecmp(variable->name, "maxforwards")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &peer->max_forwards, 1, 255)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "accountcode")) { + ast_string_field_set(peer, accountcode, variable->value); + } else if (!strcasecmp(variable->name, "mohinterpret")) { + ast_string_field_set(peer, moh_interpret, variable->value); + } else if (!strcasecmp(variable->name, "mohsuggest")) { + ast_string_field_set(peer, moh_suggest, variable->value); + } else if (!strcasecmp(variable->name, "parkinglot")) { + ast_string_field_set(peer, parkinglot, variable->value); + } else if (!strcasecmp(variable->name, "mailbox")) { + sip_mailbox_build(peer, variable->value, variable->lineno); + } else if (!strcasecmp(variable->name, "subscribemwi")) { + peer->subscribe_mwi_only = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "vmexten") || !strcasecmp(variable->name, "mwiexten")) { + if (strcasecmp(variable->name, "mwiexten")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "mwiexten", variable->lineno); + } + + ast_string_field_set(peer, mwi_exten, variable->value); + } else if (!strcasecmp(variable->name, "callgroup")) { + peer->callgroup = ast_get_group(variable->value); + } else if (!strcasecmp(variable->name, "allowtransfer")) { + peer->allow_transfer = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "allowsubscribe")) { + peer->allow_subscribe = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "pickupgroup")) { + peer->pickupgroup = ast_get_group(variable->value); + } else if (!strcasecmp(variable->name, "namedcallgroup")) { + peer->named_callgroups = ast_get_namedgroups(variable->value); + } else if (!strcasecmp(variable->name, "namedpickupgroup")) { + peer->named_pickupgroups = ast_get_namedgroups(variable->value); + } else if (!strcasecmp(variable->name, "allow")) { + if (ast_format_cap_update_by_allow_disallow(peer->format_cap, variable->value, TRUE)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "disallow")) { + if (ast_format_cap_update_by_allow_disallow(peer->format_cap, variable->value, FALSE)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "preferred_codec_only") || + !strcasecmp(variable->name, "preferredcodeconly")) { + if (strcasecmp(variable->name, "preferredcodeconly")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "preferredcodeconly", variable->lineno); + } + + peer->preferred_codec_only = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "autoframing")) { + peer->auto_framing = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "rtptimeout")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &peer->rtp_timeout, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "rtpholdtimeout")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &peer->rtp_hold_timeout, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "rtpkeepalive")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &peer->rtp_keepalive, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "timert1")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &peer->timer_t1, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "timerb")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &peer->timer_b, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "setvar") || !strcasecmp(variable->name, "variable")) { + if (strcasecmp(variable->name, "variable")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "variable", variable->lineno); + } + + sip_variable_build(peer, variable->value, variable->lineno); + } else if (!strcasecmp(variable->name, "header")) { + char header[2048]; + + snprintf(header, sizeof(header), "__SIP_ADD_HEADER%.2d=%s", + ++header_count, variable->value); + sip_variable_build(peer, header, variable->lineno); + } else if (!strcasecmp(variable->name, "qualifyfreq") || + !strcasecmp(variable->name, "qualifyexpires")) { + if (strcasecmp(variable->name, "qualifyexpires")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "qualifyexpires", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &peer->qualify_expires, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "maxcallbitrate")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &peer->max_call_bitrate, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "directmedia") || + !strcasecmp(variable->name, "canreinvite")) { + char *direct_media, *option; + + if (strcasecmp(variable->name, "directmedia")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "directmedia", variable->lineno); + } + + peer->direct_media = 0; + direct_media = ast_strdupa(variable->value); + + while ((option = strsep(&direct_media, ","))) { + if (ast_true(option)) { + peer->direct_media = SIP_DIRECT_MEDIA_NAT | SIP_DIRECT_MEDIA_NO_NAT; + } else if (ast_false(option)) { + peer->direct_media = 0; + } else if (!strcasecmp(option, "update")) { + peer->direct_media |= SIP_DIRECT_MEDIA_NO_NAT | SIP_DIRECT_MEDIA_UPDATE; + } else if (!strcasecmp(option, "nonat") || !strcasecmp(option, "no_nat")) { + if (strcasecmp(option, "no_nat")) { + ast_log(LOG_WARNING, + "Option '%s=%s' is deprecated, use '%s' instead at line %d\n", + variable->name, option, "no_nat", variable->lineno); + } + peer->direct_media &= ~SIP_DIRECT_MEDIA_NAT; + peer->direct_media |= SIP_DIRECT_MEDIA_NO_NAT; + } else if (!strcasecmp(option, "outgoing")) { + peer->direct_media = SIP_DIRECT_MEDIA_NO_NAT | SIP_DIRECT_MEDIA_OUTGOING; + } else { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, option, variable->lineno); + } + } + } else if (!strcasecmp(variable->name, "session-timers") || + !strcasecmp(variable->name, "timer")) { + if (strcasecmp(variable->name, "timer")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "timer", variable->lineno); + } + + if (!strcasecmp(variable->value, "accept")) { + peer->session_timer_mode = SIP_SESSION_TIMER_MODE_ACCEPT; + } else if (!strcasecmp(variable->value, "refuse")) { + peer->session_timer_mode = SIP_SESSION_TIMER_MODE_REFUSE; + } else if (!strcasecmp(variable->value, "originate")) { + peer->session_timer_mode = SIP_SESSION_TIMER_MODE_ORIGINATE; + } else { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "session-expires") || + !strcasecmp(variable->name, "timermaxexpiries")) { + if (strcasecmp(variable->name, "timermaxexpires")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "timermaxexpires", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &peer->session_timer_max_expires, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "session-minse") || + !strcasecmp(variable->name, "timerminexpires")) { + if (strcasecmp(variable->name, "timerminexpires")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "timerminexpires", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &peer->session_timer_min_expires, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "session-refresher") || + !strcasecmp(variable->name, "timerrefresher")) { + if (strcasecmp(variable->name, "timerrefresher")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "timerrefresher", variable->lineno); + } + + if (!strcasecmp(variable->value, "uac")) { + peer->session_timer_refresher = SIP_SESSION_TIMER_REFRESHER_UAC; + } else if (!strcasecmp(variable->value, "uas")) { + peer->session_timer_refresher = SIP_SESSION_TIMER_REFRESHER_UAS; + } else { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "unsolicited_mailbox") || + !strcasecmp(variable->name, "unsolictedmailbox")) { + if (strcasecmp(variable->name, "unsolictedmailbox")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "unsolictedmailbox", variable->lineno); + } + + ast_string_field_set(peer, unsolicited_mailbox, variable->value); + } else if (!strcasecmp(variable->name, "use_q850_reason") || + !strcasecmp(variable->name, "reason")) { + if (strcasecmp(variable->name, "reason")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "reason", variable->lineno); + } + + peer->reason_support = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "encryption")) { + peer->secure_media = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "icesupport") || !strcasecmp(variable->name, "ice")) { + if (strcasecmp(variable->name, "ice")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "ice", variable->lineno); + } + + peer->ice_support = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "ignore_requested_pref") || + !strcasecmp(variable->name, "ignoreoutboundcodec")) { + if (strcasecmp(variable->name, "ignoreoutboundcodec")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "ignoreoutboundcodec", variable->lineno); + } + + peer->ignore_outgoing_format = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "videosupport") || !strcasecmp(variable->name, "video")) { + if (strcasecmp(variable->name, "video")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "video", variable->lineno); + } + + if (!strcasecmp(variable->value, "always")) { + ast_log(LOG_WARNING, "Option '%s=%s' is no longer supported at line %d\n", + variable->name, variable->value, variable->lineno); + } else { + peer->video_support = ast_true(variable->value); + } + } else if (!strcasecmp(variable->name, "textsupport") || !strcasecmp(variable->name, "text")) { + if (strcasecmp(variable->name, "text")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "text", variable->lineno); + } + + peer->text_support = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "supportpath") || !strcasecmp(variable->name, "path")) { + if (strcasecmp(variable->name, "path")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "path", variable->lineno); + } + + peer->path_support = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "sendrpid") || !strcasecmp(variable->name, "identity")) { + char *identity, *option; + + if (strcasecmp(variable->name, "identity")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "identity", variable->lineno); + } + + peer->identity_support = SIP_IDENTITY_NONE; + peer->allow_identity = FALSE; + peer->trust_identity_outgoing = FALSE; + + identity = ast_strdupa(variable->value); + + while ((option = strsep(&identity, ","))) { + if (ast_false(option)) { + peer->identity_support = SIP_IDENTITY_NONE; + peer->allow_identity = FALSE; + peer->trust_identity_outgoing = FALSE; + } else if (ast_true(option) || !strcasecmp(option, "remote_party")) { + if (strcasecmp(option, "remote_party")) { + ast_log(LOG_WARNING, "Option '%s=%s' is deprecated, use '%s' instead at line %d\n", + variable->name, option, "remote_party", variable->lineno); + } + + peer->identity_support = SIP_IDENTITY_REMOTE_PARTY; + } else if (!strcasecmp(option, "pai") || !strcasecmp(option, "p_asserted")) { + if (strcasecmp(option, "p_asserted")) { + ast_log(LOG_WARNING, "Option '%s=%s' is deprecated, use '%s' instead at line %d\n", + variable->name, option, "p_asserted", variable->lineno); + } + + peer->identity_support = SIP_IDENTITY_P_ASSERTED; + } else if (!strcasecmp(option, "allow")) { + peer->allow_identity = TRUE; + } else if (!strcasecmp(option, "trusted")) { + peer->trust_identity_outgoing = TRUE; + } else { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, option, variable->lineno); + } + } + } else if (!strcasecmp(variable->name, "trustrpid")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "identity", variable->lineno); + + peer->allow_identity = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "trust_id_outbound")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "identity", variable->lineno); + + peer->trust_identity_outgoing = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "send_diversion") || !strcasecmp(variable->name, "diversion")) { + if (strcasecmp(variable->name, "diversion")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "diversion", variable->lineno); + } + + peer->diversion_support = TRUE; + } else if (!strcasecmp(variable->name, "dtmfmode")) { + if (!strcasecmp(variable->value, "inband")) { + peer->dtmf_mode = SIP_DTMF_MODE_INBAND; + } else if (!strcasecmp(variable->value, "rfc2833")) { + peer->dtmf_mode = SIP_DTMF_MODE_RFC2833; + } else { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "relaxdtmf")) { + peer->relax_dtmf = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "allowoverlap")) { + if (ast_false(variable->value)) { + peer->allow_overlap = SIP_ALLOW_OVERLAP_NONE; + } else if (ast_true(variable->value) || !strcasecmp(variable->value, "invite")) { + peer->allow_overlap = SIP_ALLOW_OVERLAP_INVITE; + } else if (!strcasecmp(variable->value, "dtmf")){ + peer->allow_overlap = SIP_ALLOW_OVERLAP_DTMF; + } else { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "register")) { + if (!strcasecmp(peer->name, variable->value)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s', same name as peer at line %d\n", + variable->name, variable->value, variable->lineno); + } else { + sip_alias_build(peer, variable->value, variable->lineno, &line_index); + } + } else if (!strcasecmp(variable->name, "subscribe")) { + sip_subscription_build(peer, variable->value, variable->lineno); + } else if (!strcasecmp(variable->name, "dndbusy") || + !strcasecmp(variable->name, "busywhendnd")) { + if (strcasecmp(variable->name, "busywhendnd")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "busywhendnd", variable->lineno); + } + + peer->busy_when_dnd = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "huntgroup_default") || + !strcasecmp(variable->name, "huntgroupdefault")) { + if (strcasecmp(variable->name, "huntgroupdefault")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "huntgroupdefault", variable->lineno); + } + + peer->hunt_group_default = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "cisco_usecallmanager") || + !strcasecmp(variable->name, "cisco")) { + if (strcasecmp(variable->name, "cisco")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "cisco", variable->lineno); + } + + peer->cisco_mode = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "cisco_keep_conference") || + !strcasecmp(variable->name, "keepconference")) { + if (strcasecmp(variable->name, "keepconference")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "keepconference", variable->lineno); + } + + peer->keep_conference = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "cisco_multiadmin_conference") || + !strcasecmp(variable->name, "multiadminconference")) { + if (strcasecmp(variable->name, "multiadminconference")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "multiadminconference", variable->lineno); + } + + peer->multi_admin_conference = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "cisco_pickupnotify_alert") || + !strcasecmp(variable->name, "pickupnotify")) { + char *pickup_notify, *option; + + if (strcasecmp(variable->name, "pickupnotify")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "pickupnotify", variable->lineno); + } + + peer->pickup_notify = 0; + pickup_notify = ast_strdupa(variable->value); + + while ((option = strsep(&pickup_notify, ","))) { + if (!strcasecmp(option, "none") || !strcasecmp(option, "no")) { + if (strcasecmp(option, "no")) { + ast_log(LOG_WARNING, + "Option '%s=%s' is deprecated, use '%s' instead at line %d\n", + variable->name, option, "no", variable->lineno); + } + + peer->pickup_notify = 0; + } else if (!strcasecmp(option, "from")) { + peer->pickup_notify |= SIP_PICKUP_NOTIFY_FROM; + } else if (!strcasecmp(option, "to")) { + peer->pickup_notify |= SIP_PICKUP_NOTIFY_TO; + } else if (!strcasecmp(option, "beep")) { + peer->pickup_notify |= SIP_PICKUP_NOTIFY_BEEP; + } else { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, option, variable->lineno); + } + } + } else if (!strcasecmp(variable->name, "cisco_pickupnotify_timer") || + !strcasecmp(variable->name, "pickupnotifytimer")) { + if (strcasecmp(variable->name, "pickupnotifytimer")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "pickupnotifytimer", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &peer->pickup_notify_timer, 0, 60)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "parknotifytimer")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &peer->park_notify_timer, 0, 60)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "cisco_qrt_url") || + !strcasecmp(variable->name, "qrturl")) { + if (strcasecmp(variable->name, "qrturl")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "qrturl", variable->lineno); + } + + ast_string_field_set(peer, qrt_url, variable->value); + } else if (!strcasecmp(variable->name, "rtcp_mux") || !strcasecmp(variable->name, "rtcpmux")) { + if (strcasecmp(variable->name, "rtcpmux")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "rtcpmux", variable->lineno); + } + + peer->rtcp_mux = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "t38pt_udptl") || !strcasecmp(variable->name, "fax")) { + char *fax, *option; + + if (strcasecmp(variable->name, "fax")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "fax", variable->lineno); + } + + peer->fax_support = FALSE; + peer->udptl_error_correction = UDPTL_ERROR_CORRECTION_NONE; + + fax = ast_strdupa(variable->value); + + while ((option = strsep(&fax, ","))) { + if (ast_false(option)) { + peer->fax_support = FALSE; + } else if (ast_true(option)) { + peer->fax_support = TRUE; + } else if (!strcasecmp(option, "fec")) { + peer->udptl_error_correction = UDPTL_ERROR_CORRECTION_FEC; + } else if (!strcasecmp(option, "redundancy")) { + peer->udptl_error_correction = UDPTL_ERROR_CORRECTION_REDUNDANCY; + } else if (!strcasecmp(option, "none")) { + peer->udptl_error_correction = UDPTL_ERROR_CORRECTION_NONE; + } else if (!strncasecmp(option, "maxdatagram=", 12) || + !strncasecmp(option, "max_datagram=", 13)) { + if (strncasecmp(option, "maxdatagram", 11)) { + ast_log(LOG_WARNING, + "Option '%s=%s' is deprecated, use '%s' instead at line %d\n", + variable->name, option, "max_datagram", variable->lineno); + } + + strsep(&option, "="); + + if (ast_parse_arg(option, PARSE_INT32 | PARSE_IN_RANGE, + &peer->fax_max_datagram, -1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(option, "usertpsource") || !strcasecmp(option, "nat")) { + if (strcasecmp(option, "nat")) { + ast_log(LOG_WARNING, + "Option '%s=%s' is deprecated, use '%s' instead at line %d\n", + variable->name, option, "nat", variable->lineno); + } + + peer->udptl_nat = TRUE; + } else { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, option, variable->lineno); + } + } + } else if (!strcasecmp(variable->name, "faxdetect")) { + char *fax_detect, *option; + + peer->fax_detect = 0; + fax_detect = ast_strdupa(variable->value); + + while ((option = strsep(&fax_detect, ","))) { + if (ast_false(option)) { + peer->fax_detect = 0; + } else if (ast_true(option)) { + peer->fax_detect = SIP_FAX_DETECT_CNG | SIP_FAX_DETECT_T38; + } else if (!strcasecmp(option, "cng")) { + peer->fax_detect |= SIP_FAX_DETECT_CNG; + } else if (!strcasecmp(option, "t38")) { + peer->fax_detect |= SIP_FAX_DETECT_T38; + } else { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, option, variable->lineno); + } + } + } else if (realtime && !strcasecmp(variable->name, "name")) { + ast_string_field_set(peer, name, variable->value); + } else if (realtime && !strcasecmp(variable->name, "useragent")) { + ast_string_field_set(peer, useragent, variable->value); + } else if (realtime && !strcasecmp(variable->name, "donotdisturb")) { + peer->do_not_disturb = ast_true(variable->value); + } else if (realtime && !strcasecmp(variable->name, "callforward")) { + ast_string_field_set(peer, call_forward, variable->value); + } else if (realtime && !strcasecmp(variable->name, "huntgroup")) { + peer->hunt_group = ast_true(variable->value); + } else if (realtime && (!strcasecmp(variable->name, "regseconds") + || !strcasecmp(variable->name, "expires"))) { + ast_get_time_t(variable->value, &expires, 0, NULL); + } else if (!strcasecmp(variable->name, "rpid_update")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always enabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "rpid_immediate")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always enabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "progressinband")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always enabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "callcounter")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always enabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "keepalive")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "discard_remote_hold_retrieval")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "force_avp")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "promiscredir")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "ignoresdpversion")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "insecure")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "g726nonstandard")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "rfc2833compensate")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "avpf")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "rtp_engine")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always asterisk)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "encryption_taglen")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d (always 80)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "t38pt_usertpsource")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported use 'faxsupport' instead at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "defaultip") || + !strcasecmp(variable->name, "defaultaddr")) { + ast_log(LOG_NOTICE, "Option '%s' is no longer supported at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "regexten")) { + ast_log(LOG_NOTICE, "Option '%s' is no longer supported at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "callbackextension")) { + ast_log(LOG_NOTICE, "Option '%s' is no longer supported at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "mwi_from")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "disallow_methods")) { + ast_log(LOG_WARNING, "Option '%s' is no longer supported at line %d\n", + variable->name, variable->lineno); + } else { + unknown = TRUE; + } + } + + /* These apply to device state lookups */ + if (realtime && (!strcasecmp(variable->name, "lastms") || !strcasecmp(variable->name, "lastqualify"))) { + sscanf(variable->value, "%30d", &peer->qualify); + } else if (realtime && !strcasecmp(variable->name, "ipaddr") && !ast_strlen_zero(variable->value)) { + ast_sockaddr_parse(&peer->address, variable->value, PARSE_PORT_FORBID); + } else if (realtime && !strcasecmp(variable->name, "contact")) { + if (strcasecmp(variable->name, "contact")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "contact", variable->lineno); + } + + ast_string_field_set(peer, contact, variable->value); + } else if (realtime && !strcasecmp(variable->name, "lastpath")) { + if (peer->path_support) { + sip_route_destroy(&peer->path); + sip_route_parse(&peer->path, variable->value, FALSE); + sip_route_is_strict(&peer->path); + } + } else if (!strcasecmp(variable->name, "qualify") || !strcasecmp(variable->name, "maxqualify")) { + if (strcasecmp(variable->name, "maxqualify")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "maxqualify", variable->lineno); + } + + if (!strcasecmp(variable->value, "no")) { + peer->qualify_max = 0; + } else if (!strcasecmp(variable->value, "yes")) { + peer->qualify_max = sip_config.qualify_max; + } else if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &peer->qualify_max, 0, UINT_MAX)) { + ast_log(LOG_WARNING, + "Invalid option '%s=%s' should be 'yes', 'no', or a number of milliseconds at line %d\n", + variable->name, variable->value, variable->lineno); + } + + if (realtime && !sip_config.realtime_cache_peer && peer->qualify_max > 0) { + /* This would otherwise cause a network storm, where the qualify response refreshes the + * peer from the database, which in turn causes another qualify to be sent, ad infinitum */ + ast_log(LOG_WARNING, + "Option '%s=%s' at line %d is incompatible with dynamic uncached realtime. Please either enable 'realtimecachepeers' disable qualify on peer '%s'\n", + variable->name, variable->value, variable->lineno, peer->name); + peer->qualify_max = 0; + } + } else if (!strcasecmp(variable->name, "call-limit") || !strcasecmp(variable->name, "maxcalls")) { + if (strcasecmp(variable->name, "maxcalls")) { + ast_log(LOG_WARNING, "Option '%s' is deprecated, use '%s' instead at line %d\n", + variable->name, "maxcalls", variable->lineno); + } + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &peer->max_calls, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "busylevel")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_IN_RANGE, + &peer->busy_level, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid option '%s=%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else { + if (unknown) { + ast_log(LOG_WARNING, "Unknown option '%s' at line %d\n", + variable->name, variable->lineno); + } + } + } + + if (!devicestate_only) { + struct sip_mailbox *mailbox; + struct sip_alias *alias; + struct sip_subscription *subscription; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->mailboxes, mailbox, next) { + if (mailbox->removed) { + AST_LIST_REMOVE_CURRENT(next); + sip_mailbox_destroy(mailbox); + } + } + AST_LIST_TRAVERSE_SAFE_END; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->subscriptions, subscription, next) { + if (subscription->removed) { + AST_LIST_REMOVE_CURRENT(next); + sip_subscription_destroy(subscription); + } + } + AST_LIST_TRAVERSE_SAFE_END; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->aliases, alias, next) { + if (alias->removed) { + AST_LIST_REMOVE_CURRENT(next); + sip_alias_destroy(alias); + } + } + AST_LIST_TRAVERSE_SAFE_END; + } + + if (peer->timer_t1 < sip_config.min_timer_t1) { + ast_log(LOG_WARNING, + "Option 'timert1' for '%s' cannot be lower than global 'timert1min', setting to %d\n", + peer->name, sip_config.min_timer_t1); + peer->timer_t1 = sip_config.min_timer_t1; + } + + /* Note that Timer B is dependent upon T1 and MUST NOT be lower than T1 * 64, according to RFC 3261, Section + * 17.1.1.2 */ + if (peer->timer_b < peer->timer_t1 * 64) { + ast_log(LOG_WARNING, "Option 'timerb' for '%s' set lower than recommended value of %d\n", + peer->name, peer->timer_t1 * 64); + } + + if (realtime && !ast_strlen_zero(peer->contact)) { + peer->realtime_contact = TRUE; + + /* We have a hostname in the full contact, but if we don't have an address listed on the entry (or if + * it's 'dynamic'), then we need to parse the entry to obtain the IP address, so a dynamic host can be + * contacted immediately after reload (as opposed to waiting for it to register once again). But if we + * have an address for this peer and NAT was specified, use that address instead */ + /* May need to revisit the final argument; does the realtime DB store whether the original contact was + * over TLS or not? */ + if ((!peer->nat_auto_rport && !peer->nat_force_rport) || ast_sockaddr_isnull(&peer->address)) { + sip_get_uri_address(peer->contact, &peer->address); + } + } + + if (!ast_strlen_zero(host) && !peer->dnsmgr) { + char service[MAXHOSTNAMELEN]; + + peer->address.ss.ss_family = AF_INET; + snprintf(service, sizeof(service), "_%s._%s", + sip_srv_service(peer->socket.transport), sip_srv_protocol(peer->socket.transport)); + + if (ast_dnsmgr_lookup_cb(host, &peer->address, &peer->dnsmgr, + sip_config.srv_lookup && !peer->port_in_uri ? service : NULL, + sip_peer_dnsmgr_lookup, ao2_bump(peer))) { + ast_log(LOG_ERROR, "DNS lookup failed for host '%s' on '%s', removing peer\n", host, peer->name); + + ao2_ref(peer, -2); /* Ref for both ast_dnsmgr_lookup and ao2_alloc */ + return NULL; + } + + if (!peer->dnsmgr) { + ao2_ref(peer, -1); /* 'dnsmgr' refresh disabled */ + } + + ast_string_field_set(peer, host, host); + } else if (peer->dnsmgr && !peer->host_dynamic) { + /* Force a refresh here on reload if dnsmgr already exists and host is set */ + ast_dnsmgr_refresh(peer->dnsmgr); + } + + if (!peer->default_transport) { + peer->transports = AST_TRANSPORT_UDP; /* Set default set of transports */ + peer->default_transport = AST_TRANSPORT_UDP; /* Set default primary transport */ + } + + /* The default transport type set during sip_peer_build should only replace the socket.transport when + * 1. Registration is not present and the socket.transport and default transport types are different. + * 2. The socket.transport is not an acceptable transport type after rebuilding peer. + * 3. The socket.transport is not set yet */ + if ((peer->socket.transport != peer->default_transport && peer->register_expires_sched_id == -1) || + !(peer->socket.transport & peer->transports) || !peer->socket.transport) { + sip_socket_set_transport(&peer->socket, peer->default_transport); + } + + if (port) { + ast_sockaddr_set_port(&peer->address, port); + } + + if (!ast_sockaddr_port(&peer->address)) { + ast_sockaddr_set_port(&peer->address, + (peer->socket.transport & AST_TRANSPORT_TLS) ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + } + + if (ast_sockaddr_isnull(&peer->socket.address)) { + ast_sockaddr_copy(&peer->socket.address, &peer->address); + } + + /* If read-only RT backend, then refresh from local DB cache */ + if (peer->host_dynamic && (!peer->realtime || !sip_config.realtime_update_peer)) { + sip_peer_astdb_load(peer); + } + + if (!realtime) { + char data[1024]; + + if (!ast_db_get("SIP/DoNotDisturb", peer->name, data, sizeof(data))) { + peer->do_not_disturb = ast_true(data); + } + + if (!ast_db_get("SIP/CallForward", peer->name, data, sizeof(data))) { + ast_string_field_set(peer, call_forward, data); + } + + if (!ast_db_get("SIP/HuntGroup", peer->name, data, sizeof(data))) { + peer->hunt_group = ast_true(data); + } else { + peer->hunt_group = peer->hunt_group_default; + } + } + + if (!devicestate_only && !ast_sockaddr_isnull(&peer->address) && peer->qualify_max) { + if (ast_sockaddr_isnull(&peer->socket.address)) { + ast_sockaddr_copy(&peer->socket.address, &peer->address); + } + + if (realtime) { + if (!sip_config.realtime_ignore_expires && peer->host_dynamic && (time(NULL) - expires) > 0) { + sip_peer_astdb_delete(peer); + ast_sockaddr_setnull(&peer->address); + + peer->qualify = -1; + ast_debug(1, "Peer '%s' has already expired\n", peer->name); + } else { + /* We cannot qualify the peer now in this thread without a lock inversion so pass it + * off to the scheduler thread, but with a when of 0 so it is qualified ASAP */ + AST_SCHED_REPLACE_UNREF(peer->qualify_sched_id, sip_sched_context, 0, sip_peer_qualify, + peer, ao2_cleanup(_data), ao2_cleanup(peer), ao2_bump(peer)); + } + } else { + /* Don't qualify peer immediately, just schedule it within qualifyfreq */ + AST_SCHED_REPLACE_UNREF(peer->qualify_sched_id, sip_sched_context, + (ast_random() % (peer->qualify_expires + 1)) * 1000, sip_peer_qualify, peer, + ao2_cleanup(_data), ao2_cleanup(peer), ao2_bump(peer)); + } + } + + /* If they didn't request that MWI is sent *only* on subscribe, go ahead and subscribe to it now */ + if (!devicestate_only && !peer->subscribe_mwi_only && !AST_LIST_EMPTY(&peer->mailboxes)) { + sip_peer_update_mailboxes(peer); + /* Send MWI from the event cache only. This is so we can send initial MWI if app_voicemail got loaded + * before chan_sip. If it is the other way, then we will get events when app_voicemail gets loaded */ + sip_peer_send_mwi(peer, TRUE); + } + + if (!devicestate_only && peer->cisco_mode && !AST_LIST_EMPTY(&peer->subscriptions)) { + sip_peer_update_subscriptions(peer); + } + + peer->removed = FALSE; + + /* If an ACL change subscription is needed and doesn't exist, we need one */ + if (subscribe_acl_change) { + sip_acl_change_subscribe(); + } + + return peer; +} + +/* Create temporary peer */ +struct sip_peer *sip_peer_temp_alloc(const char *name) +{ + struct sip_peer *peer; + + if (!(peer = ao2_alloc(sizeof(*peer), sip_peer_destroy))) { + return NULL; + } + + if (ast_string_field_init(peer, 512)) { + ao2_ref(peer, -1); + return NULL; + } + + ast_string_field_set(peer, name, name); + + if (!(peer->format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + ao2_ref(peer, -1); + return NULL; + } + + sip_peer_set_defaults(peer); + + peer->transports = AST_TRANSPORT_UDP | AST_TRANSPORT_TCP | AST_TRANSPORT_TLS; + peer->host_dynamic = TRUE; + peer->register_expires_sched_id = -1; + peer->qualify_sched_id = -1; + + return peer; +} + +/* Set peer defaults before configuring specific configurations */ +static void sip_peer_set_defaults(struct sip_peer *peer) +{ + if (peer->register_expires_sched_id == -1) { + AST_SCHED_DEL_UNREF(sip_sched_context, peer->qualify_sched_id, ao2_cleanup(peer)); + + /* Don't reset expire or port time during reload if we have an active registration */ + sip_socket_set_transport(&peer->socket, AST_TRANSPORT_UDP); + ast_sockaddr_setnull(&peer->address); + } + + ast_format_cap_append_from_cap(peer->format_cap, sip_config.format_cap, AST_MEDIA_TYPE_UNKNOWN); + + peer->transports = AST_TRANSPORT_UDP; + peer->default_transport = AST_TRANSPORT_UDP; + + ast_string_field_set(peer, context, "default"); + ast_string_field_set(peer, language, sip_config.language); + ast_string_field_set(peer, moh_interpret, sip_config.moh_interpret); + ast_string_field_set(peer, moh_suggest, sip_config.moh_suggest); + + ast_string_field_set(peer, description, NULL); + ast_string_field_set(peer, mwi_exten, "vm"); + ast_string_field_set(peer, tone_zone, sip_config.tone_zone); + + ast_string_field_set(peer, secret, NULL); + ast_string_field_set(peer, remote_secret, NULL); + ast_string_field_set(peer, md5_secret, NULL); + + ast_string_field_set(peer, caller_number, NULL); + ast_string_field_set(peer, caller_name, NULL); + ast_string_field_set(peer, caller_tag, NULL); + + ast_string_field_set(peer, from_domain, NULL); + ast_string_field_set(peer, from_user, NULL); + ast_string_field_set(peer, host, NULL); + + peer->max_calls = -1; + + peer->dtmf_mode = SIP_DTMF_MODE_RFC2833; + peer->allow_overlap = SIP_ALLOW_OVERLAP_INVITE; + peer->caller_presentation = AST_PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN; + + peer->callgroup = 0; + peer->pickupgroup = 0; + + peer->allow_transfer = FALSE; + peer->allow_subscribe = FALSE; + peer->secure_media = FALSE; + peer->user_eq_phone = FALSE; + + peer->reason_support = FALSE; + peer->path_support = FALSE; + + peer->max_call_bitrate = 384; + peer->max_forwards = 70; + peer->auto_framing = FALSE; + + peer->rtcp_mux = FALSE; + peer->subscribe_mwi_only = FALSE; + + peer->video_support = FALSE; + peer->text_support = FALSE; + peer->ice_support = FALSE; + peer->fax_support = FALSE; + + peer->identity_support = SIP_IDENTITY_NONE; + peer->allow_identity = FALSE; + peer->trust_identity_outgoing = FALSE; + peer->diversion_support = FALSE; + + peer->ignore_outgoing_format = FALSE; + peer->preferred_codec_only = FALSE; + + peer->busy_when_dnd = FALSE; + peer->hunt_group_default = FALSE; + + peer->cisco_mode = FALSE; + peer->pickup_notify = 0; + peer->pickup_notify_timer = 5; + peer->park_notify_timer = 5; + peer->multi_admin_conference = FALSE; + peer->keep_conference = FALSE; + peer->line_index = 1; + + peer->qualify_max = sip_config.qualify_max; + peer->qualify_expires = sip_config.qualify_expires; + + peer->timer_t1 = sip_config.timer_t1; + peer->timer_b = sip_config.timer_b; + + peer->rtp_timeout = sip_config.rtp_timeout; + peer->rtp_hold_timeout = sip_config.rtp_hold_timeout; + peer->rtp_keepalive = sip_config.rtp_keepalive; + + peer->nat_force_rport = sip_config.nat_force_rport; + peer->nat_auto_rport = sip_config.nat_auto_rport; + peer->nat_rtp = sip_config.nat_rtp; + peer->nat_auto_rtp = sip_config.nat_auto_rtp; + peer->direct_media = 0; + + peer->fax_max_datagram = -1; + peer->udptl_nat = FALSE; + + peer->session_timer_mode = SIP_SESSION_TIMER_MODE_REFUSE; + peer->session_timer_refresher = SIP_SESSION_TIMER_REFRESHER_AUTO; + peer->session_timer_max_expires = 1800; /* Default Session-Expires period (RFC 4028) */ + peer->session_timer_min_expires = 90; /* Default Min-SE period (RFC 4028) */ + + if (peer->proxy) { + ao2_ref(peer->proxy, -1); + peer->proxy = NULL; + } +} + +/* Set the peers nat flags if they are using auto_* settings */ +static void sip_peer_dnsmgr_lookup(struct ast_sockaddr *old_address, struct ast_sockaddr *new_address, void *data) +{ + struct sip_peer *peer; + const char *old_host, *new_host; + + peer = (struct sip_peer *) data; + + /* This shouldn't happen, but just in case */ + if (ast_sockaddr_isnull(new_address)) { + return; + } + + if (!ast_sockaddr_isnull(&peer->address)) { + ao2_unlink(sip_peer_addresses, peer); + } + + if (!ast_sockaddr_port(new_address)) { + ast_sockaddr_set_port(new_address, + peer->socket.transport == AST_TRANSPORT_TLS ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + } + + old_host = ast_strdupa(ast_sockaddr_stringify(old_address)); + new_host = ast_strdupa(ast_sockaddr_stringify(new_address)); + + ast_debug(1, "Changing peer %s address from %s to %s\n", peer->name, old_host, new_host); + + ao2_lock(peer); + ast_sockaddr_copy(&peer->address, new_address); + ao2_unlock(peer); + + ao2_link(sip_peer_addresses, peer); +} + +/* Subscribe to MWI events for the specified peer. The peer cannot be locked during this method. sip_peer_send_mwi_peer + * will attempt to lock the peer after the event subscription lock is held; if the peer is locked during this method + * then we will attempt to lock the event subscription lock but after the peer, creating a locking inversion */ +void sip_peer_update_mailboxes(struct sip_peer *peer) +{ + struct sip_mailbox *mailbox; + + AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, next) { + if (mailbox->mwi_subscription) { + continue; + } + + mailbox->mwi_subscription = ast_mwi_subscribe_pool(mailbox->name, sip_peer_mwi_event, peer); + + if (mailbox->mwi_subscription) { + stasis_subscription_accept_message_type(ast_mwi_subscriber_subscription(mailbox->mwi_subscription), + stasis_subscription_change_type()); + } + } +} + +/* Send initial subscription state updates to peer */ +void sip_peer_update_subscriptions(struct sip_peer *peer) +{ + struct sip_subscription *subscription; + + if (peer->register_expires_sched_id == -1) { + ast_debug(1, "Not updating subscriptions as peer '%s' is not registered\n", peer->name); + return; + } + + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, next) { + struct ast_state_cb_info state_info; + RAII_VAR(struct ao2_container *, device_state_info, NULL, ao2_cleanup); + RAII_VAR(char *, subtype, NULL, ast_free_ptr); + RAII_VAR(char *, message, NULL, ast_free_ptr); + + if (subscription->dialog) { + /* Peer hasn't changed, keep original dialog */ + if (!ast_sockaddr_cmp(&peer->address, &subscription->dialog->address)) { + continue; + } + + sip_dialog_unlink(subscription->dialog); + ao2_ref(subscription->dialog, -1); + + subscription->dialog = NULL; + } + + if (!subscription->dialog) { + if (!(subscription->dialog = sip_dialog_alloc(NULL, &peer->socket, SIP_METHOD_NOTIFY, NULL, 0))) { + return; + } + + subscription->dialog->peer = ao2_bump(peer); + } + + /* Don't use sip_dialog_build_from_peer here as it may fail due to the peer not having responded to an + * OPTIONS request yet */ + if (!ast_strlen_zero(peer->host)) { + ast_string_field_set(subscription->dialog, to_host, peer->host); + } else { + ast_string_field_set(subscription->dialog, to_host, + ast_sockaddr_stringify_host_remote(&peer->address)); + } + + subscription->dialog->port_in_uri = peer->port_in_uri; + subscription->dialog->from_domain_port = peer->from_domain_port; + + ast_string_field_set(subscription->dialog, from_user, subscription->exten); + ast_string_field_set(subscription->dialog, from_name, NULL); + ast_string_field_set(subscription->dialog, from_domain, peer->from_domain); + + ast_string_field_set(subscription->dialog, to_user, subscription->exten); + ast_string_field_set(subscription->dialog, contact, peer->contact); + + subscription->dialog->outgoing = TRUE; /* Notify is outgoing call */ + subscription->dialog->established = TRUE; + subscription->dialog->subscribe_event = SIP_SUBSCRIBE_PRESENCE; /* Needs to be configurable */ + + /* Pretend expires just so that the Subscripton-State will be marked as active */ + subscription->dialog->expires = sip_config.subscribe_max_expires; + subscription->dialog->force_state_change = TRUE; + + sip_message_build_initial_request(&subscription->dialog->initial_request, subscription->dialog, + SIP_METHOD_NOTIFY, NULL); + + /* Reset CSeq to as sip_message_build_initial has incremented it */ + subscription->dialog->outgoing_cseq = 0; + + ast_debug(1, "Set initial SUBSCRIBE request for '%s'\n", subscription->dialog->call_id); + + subscription->dialog->extension_state_id = ast_extension_state_add_extended(subscription->context, + subscription->exten, sip_extension_state_event, subscription->dialog); + + if (subscription->dialog->extension_state_id == -1) { + sip_dialog_unlink(subscription->dialog); + ao2_ref(subscription->dialog, -1); + + subscription->dialog = NULL; + continue; + } + + ast_debug(1, "Adding subscription for %s@%s (%s)\n", + subscription->exten, subscription->context, subscription->dialog->call_id); + + state_info.reason = 0; + state_info.exten_state = ast_extension_state_extended(NULL, subscription->context, subscription->exten, + &device_state_info); + state_info.device_state_info = device_state_info; + + state_info.presence_state = ast_hint_presence_state(NULL, subscription->context, subscription->exten, + &subtype, &message); + state_info.presence_subtype = subtype; + state_info.presence_message = message; + + if (state_info.exten_state & AST_EXTENSION_RINGING) { + RAII_VAR(struct ast_channel *, channel, NULL, ast_channel_cleanup); + + if ((channel = sip_find_ringing_channel(state_info.device_state_info))) { + subscription->dialog->last_ringing_time = ast_channel_creationtime(channel); + } + } + + sip_extension_state_event(subscription->context, subscription->exten, &state_info, subscription->dialog); + } +} + +/* Update bulk-register aliases */ +void sip_peer_update_aliases(struct sip_peer *peer) +{ + struct sip_alias *alias; + char *scheme, *domain, *contact; + + if (AST_LIST_EMPTY(&peer->aliases)) { + return; + } + + if (ast_sockaddr_isnull(&peer->address)) { + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (alias->peer) { + ast_sockaddr_setnull(&alias->peer->address); + sip_socket_set_transport(&alias->peer->socket, 0); + } + } + + return; + } + + contact = ast_strdupa(peer->contact); + + if (sip_parse_uri(contact, &scheme, NULL, &domain, NULL, NULL)) { + return; + } + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (!alias->peer) { + if (!(alias->peer = sip_peer_find(alias->name, TRUE, FALSE))) { + ast_log(LOG_WARNING, "No such register peer '%s'\n", alias->name); + continue; + } + + /* Remove any scheduler entries that may have been created */ + AST_SCHED_DEL_UNREF(sip_sched_context, alias->peer->register_expires_sched_id, ao2_cleanup(peer)); + AST_SCHED_DEL_UNREF(sip_sched_context, alias->peer->qualify_sched_id, ao2_cleanup(peer)); + } + + /* These settings could have been overwritten by a reload */ + alias->peer->line_index = alias->line_index; + + ast_string_field_set(alias->peer, authorization_user, peer->name); + ast_string_field_set(alias->peer, secret, peer->secret); + ast_string_field_set(alias->peer, md5_secret, peer->md5_secret); + + ast_string_field_set(alias->peer, register_call_id, peer->register_call_id); + ast_string_field_set(alias->peer, device_name, peer->device_name); + ast_string_field_set(alias->peer, useragent, peer->useragent); + + alias->peer->do_not_disturb = peer->do_not_disturb; + alias->peer->hunt_group = peer->hunt_group; + + /* Peer hasn't changed */ + if (!ast_sockaddr_cmp(&peer->address, &alias->peer->address)) { + continue; + } + + alias->peer->port_in_uri = peer->port_in_uri; + alias->peer->from_domain_port = peer->from_domain_port; + + alias->peer->allow_methods = peer->allow_methods; + alias->peer->supported_options = peer->supported_options; + + ast_string_field_build(alias->peer, contact, "%s:%s@%s", scheme, alias->peer->name, domain); + + ast_sockaddr_copy(&alias->peer->address, &peer->address); + sip_socket_copy(&alias->peer->socket, &peer->socket); + + if (alias->peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + + ast_endpoint_set_state(alias->peer->endpoint, AST_ENDPOINT_ONLINE); + blob = ast_json_pack("{s: s, s: s}", + "peer_status", "Registered", "address", ast_sockaddr_stringify(&peer->address)); + ast_endpoint_blob_publish(alias->peer->endpoint, ast_endpoint_state_type(), blob); + } + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); + ast_verb(3, "Registered SIP peer '%s' at %s\n", + alias->peer->name, ast_sockaddr_stringify(&alias->peer->address)); + + if (sip_config.realtime_update_peer && (peer->realtime || peer->realtime_cache_peer)) { + sip_realtime_update(alias->peer); + } + + sip_peer_set_messages(alias->peer, 0, 0, FALSE); + } +} + +/* Parse contact header and save registration (peer registration) */ +int sip_peer_register(struct sip_peer *peer, struct sip_dialog *dialog, struct sip_message *request, + int *address_changed) +{ + char *user, *expires, *domain, *contact, *parameters, *headers; + const char *useragent; + struct ast_sockaddr address; + enum ast_transport transport; + + contact = ast_strdupa(sip_message_find_header(request, "Contact")); + expires = ast_strdupa(sip_message_find_header(request, "Expires")); + + /* If they did not specify Contact: or Expires:, they are querying what we currently have stored as their + * contact address, so return it */ + if (ast_strlen_zero(contact)) { + if (ast_strlen_zero(expires)) { + /* If we have an active registration, tell them when the registration is going to expire */ + if (peer->register_expires_sched_id != -1 && !ast_strlen_zero(peer->contact)) { + dialog->expires = ast_sched_when(sip_sched_context, peer->register_expires_sched_id); + } + + return 0; + } + + ast_log(LOG_WARNING, "Missing Contact: header from '%s'\n", + ast_sockaddr_stringify(&dialog->socket.address)); + return -1; + } + + if (sip_parse_contact(contact, NULL, &user, &domain, ¶meters, &headers)) { + ast_log(LOG_WARNING, "Invalid Contact: '%s' from %s\n", + sip_message_find_header(request, "Contact"), ast_sockaddr_stringify(&dialog->socket.address)); + sip_response_send_with_date(dialog, "400 Bad Request", request); + return -1; + } + + if (ast_strlen_zero(expires)) { + /* No expires header, try look in Contact: */ + sip_parse_parameters(headers, ';', "expires", &expires, NULL); + } + + if (!(dialog->expires = atoi(expires))) { /* Unregister this peer */ + peer->allow_methods = 0; + peer->supported_options = 0; + + /* This means remove all registrations and return OK */ + AST_SCHED_DEL_UNREF(sip_sched_context, peer->register_expires_sched_id, ao2_cleanup(peer)); + sip_peer_unregister(ao2_bump(peer)); + + ast_verb(3, "Unregistered SIP peer '%s'\n", peer->name); + return 0; + } + + if (dialog->expires > sip_config.register_max_expires) { + dialog->expires = sip_config.register_max_expires; + } else if (dialog->expires < sip_config.register_min_expires) { + dialog->expires = sip_config.register_min_expires; + } + + /* If the peer's socket type is different than the Registration transport type, reject the registration */ + transport = sip_parse_transport(parameters); + + if (transport && dialog->socket.transport != transport) { + ast_verb(3, "Invalid Contact: transport '%s' for peer '%s'\n", parameters, peer->name); + sip_response_send_with_date(dialog, "603 Decline", request); + return -1; + } + + if (!peer->nat_force_rport && !dialog->rport_present) { + if (ast_sockaddr_resolve_first_af(&address, domain, 0, AST_AF_INET)) { + ast_verb(3, "Invalid SIP domain '%s' for peer '%s'\n", domain, peer->name); + sip_response_send_with_date(dialog, "400 Bad Request", request); + return -1; + } + + /* If we have a port number in the given URI, make sure we do remember to not check for NAPTR/SRV + * records. The hostport part is actually a host */ + peer->port_in_uri = !!ast_sockaddr_port(&address); + + if (!ast_sockaddr_port(&address)) { + ast_sockaddr_set_port(&address, + dialog->socket.transport == AST_TRANSPORT_TLS ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + } + } else { + /* Don't trust the contact field. Just use what they came to us with */ + ast_sockaddr_copy(&address, &dialog->socket.address); + } + + /* Check that they're allowed to register at this IP */ + if (ast_apply_acl(peer->contact_acl, &address, NULL) != AST_SENSE_ALLOW) { + ast_verb(3, "Registration for SIP peer '%s' denied by contact ACL for '%s'\n", + peer->name, ast_sockaddr_stringify(&address)); + sip_response_send_with_date(dialog, "603 Decline", request); + return -1; + } + + contact = ast_strdupa(sip_message_find_header(request, "Contact")); + contact = sip_get_uri(contact); + + /* Store whatever we got as a contact from the client */ + ast_string_field_set(peer, contact, contact); + /* For the 200 OK, we should use the received contact */ + ast_string_field_build(dialog, our_contact, "<%s>", peer->contact); + + /* Is this a new IP address for us? */ + if ((*address_changed = !!ast_sockaddr_cmp(&peer->address, &address))) { + ast_verb(3, "Registered SIP peer '%s' at %s\n", peer->name, ast_sockaddr_stringify(&address)); + + peer->allow_methods = dialog->allow_methods; + peer->supported_options = dialog->supported_options; + peer->offhook = 0; /* Clear off-hook counter in case of the on-hook notification not being received */ + } + + /* If we were already linked into the sip_peer_addresses container unlink ourselves so nobody can find us */ + if (!ast_sockaddr_isnull(&peer->address) && (!peer->realtime || sip_config.realtime_cache_peer)) { + ao2_unlink(sip_peer_addresses, peer); + } + + ast_sockaddr_copy(&peer->address, &address); + sip_socket_copy(&peer->socket, &dialog->socket); + + /* Now that our address has been updated put ourselves back into the container for lookups */ + if (!peer->realtime || peer->realtime_cache_peer) { + ao2_link(sip_peer_addresses, peer); + } + + AST_SCHED_DEL_UNREF(sip_sched_context, peer->register_expires_sched_id, ao2_cleanup(peer)); + + if (peer->realtime && !peer->realtime_cache_peer) { + peer->register_expires_sched_id = -1; + } else { + if ((peer->register_expires_sched_id = ast_sched_add(sip_sched_context, + (dialog->expires + 10) * 1000, sip_peer_unregister, ao2_bump(peer))) == -1) { + ao2_ref(peer, -1); + } + } + + sip_parse_path(peer, request); + + /* We might not immediately be able to reconnect via TCP, but try caching it anyhow */ + if (!peer->realtime_contact || !sip_config.realtime_update_peer) { + char data[512]; + + if (peer->path_support && !sip_route_empty(&peer->path)) { + struct ast_str *path; + + if ((path = sip_route_list(&peer->path, 0))) { + ast_db_put("SIP/PeerPath", peer->name, ast_str_buffer(path)); + ast_free(path); + } + } + + if (!peer->cisco_mode) { + snprintf(data, sizeof(data), "%s %d %s %s", + ast_sockaddr_stringify(&peer->address), dialog->expires, peer->authorization_user, + peer->contact); + ast_db_put("SIP/Peer", peer->name, data); + } + } + + if (peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + + ast_endpoint_set_state(peer->endpoint, AST_ENDPOINT_ONLINE); + blob = ast_json_pack("{s: s, s: s}", + "peer_status", "Registered", "address", ast_sockaddr_stringify(&peer->address)); + ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); + } + + /* Save User-Agent */ + useragent = sip_message_find_header(request, "User-Agent"); + + if (strcasecmp(useragent, peer->useragent)) { + ast_string_field_set(peer, useragent, useragent); + } + + /* Save REGISTER dialog Call-ID */ + ast_string_field_set(peer, register_call_id, dialog->call_id); + + if (peer->cisco_mode) { + char *reason = ast_strdupa(sip_message_find_header(request, "Reason")); + + if (!strncmp(reason, "SIP;", 4)) { + char *text; + + sip_parse_parameters(reason + 4, ';', "text", &text, NULL); + + if (!ast_strlen_zero(text)) { + char *device_name, *inactive_load, *active_load, *loads; + + /* 79xx models only support Load= whereas new models have ActiveLoad and InactiveLoad */ + sip_parse_parameters(text, ' ', "Name", &device_name, "Load", &active_load, + "ActiveLoad", &active_load, "InactiveLoad", &inactive_load, NULL); + + if ((loads = strstr(active_load, ".loads"))) { + *loads = '\0'; + } + + if ((loads = strstr(inactive_load, ".loads"))) { + *loads = '\0'; + } + + ast_string_field_set(peer, device_name, device_name); + ast_string_field_set(peer, active_load, active_load); + ast_string_field_set(peer, inactive_load, inactive_load); + } + } + } + + return 0; +} +/* Expire registration of SIP peer */ +int sip_peer_unregister(const void *data) +{ + struct sip_peer *peer; + struct sip_subscription *subscription; + struct sip_alias *alias; + + peer = (struct sip_peer *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, peer->qualify_sched_id, ao2_cleanup(peer)); + + peer->register_expires_sched_id = -1; + peer->allow_methods = 0; + peer->supported_options = 0; + peer->port_in_uri = FALSE; + + peer->inuse = 0; + peer->ringing = 0; + peer->onhold = 0; + peer->offhook = 0; + + ast_string_field_set(peer, contact, NULL); + ast_string_field_set(peer, register_call_id, NULL); + sip_peer_astdb_delete(peer); /* Remove registration data from storage */ + + /* Do we need to release this peer from memory? Only for realtime peers */ + if (peer->realtime && sip_config.realtime_auto_clear) { + ast_debug(3, "Realtime peer '%s' expired registration\n", peer->name); + ao2_unlink(sip_peers, peer); + } + + if (!ast_sockaddr_isnull(&peer->address)) { + /* We still need to unlink the peer from the sip_peer_addresses table, otherwise we end up with multiple + * copies hanging around each time a registration expires and the peer re-registers */ + ao2_unlink(sip_peer_addresses, peer); + } + + /* Only clear the addr after we check for destruction. The addr must remain in order to unlink from the + * sip_peer_addresses container correctly */ + ast_sockaddr_setnull(&peer->address); + sip_socket_set_transport(&peer->socket, 0); + + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, next) { + if (subscription->dialog) { + sip_dialog_unlink(subscription->dialog); + ao2_ref(subscription->dialog, -1); + subscription->dialog = NULL; + } + } + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + + if (peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + + ast_endpoint_set_state(peer->endpoint, AST_ENDPOINT_OFFLINE); + blob = ast_json_pack("{s: s, s: s}", "peer_status", "Unregistered", "cause", "Expired"); + ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); + } + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (!alias->peer) { + continue; + } + + ast_verb(3, "Unregistered SIP peer '%s'\n", alias->name); + + alias->peer->qualify = 0; + + ast_sockaddr_setnull(&alias->peer->address); + sip_socket_set_transport(&alias->peer->socket, 0); + + ast_string_field_set(alias->peer, contact, NULL); + ast_string_field_set(alias->peer, useragent, NULL); + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); + + if (alias->peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + + ast_endpoint_set_state(alias->peer->endpoint, AST_ENDPOINT_OFFLINE); + blob = ast_json_pack("{s: s, s: s}", "peer_status", "Unregistered", "cause", "Expired"); + ast_endpoint_blob_publish(alias->peer->endpoint, ast_endpoint_state_type(), blob); + } + + ao2_ref(alias->peer, -1); + alias->peer = NULL; + } + + ao2_ref(peer, -1); + return 0; +} + +/* Get registration details from Asterisk DB */ +static void sip_peer_astdb_load(struct sip_peer *peer) +{ + char data[1024], *host, *authorization_user, *contact, *parse; + int expires; + + /* Cisco phones reboot when Asterisk restarts so there is no point trying to reconnect */ + if (peer->cisco_mode) { + return; + } + + /* If read-only RT backend, then refresh from local DB cache */ + if (peer->realtime_contact && sip_config.realtime_update_peer) { + return; + } + + if (ast_db_get("SIP/Peer", peer->name, data, sizeof(data))) { + return; + } + + parse = data; + + host = strsep(&parse, " "); + expires = atoi(strsep(&parse, " ")); + authorization_user = strsep(&parse, " "); + contact = strsep(&parse, " "); + + if (!ast_sockaddr_parse(&peer->address, host, PARSE_PORT_REQUIRE) || !expires) { + ast_sockaddr_setnull(&peer->address); + return; + } + + if (!ast_strlen_zero(authorization_user)) { + ast_string_field_set(peer, authorization_user, authorization_user); + } + + if (!ast_strlen_zero(contact)) { + ast_string_field_set(peer, contact, contact); + } + + if (peer->path_support && !ast_db_get("SIP/PeerPath", peer->name, data, sizeof(data))) { + sip_route_destroy(&peer->path); + sip_route_parse(&peer->path, data, FALSE); + sip_route_is_strict(&peer->path); + } + + ast_debug(2, "Seeding peer '%s' at %s@%s for %ds\n", + peer->name, peer->authorization_user, ast_sockaddr_stringify_host(&peer->address), expires); + + AST_SCHED_REPLACE_UNREF(peer->register_expires_sched_id, sip_sched_context, (expires + 10) * 1000, + sip_peer_unregister, peer, ao2_cleanup(_data), ao2_cleanup(peer), ao2_bump(peer)); +} + +/* Remove registration data from realtime database or AST/DB when registration expires */ +static void sip_peer_astdb_delete(struct sip_peer *peer) +{ + if (peer->realtime) { + if (!sip_config.realtime_ignore_expires && + sip_config.realtime_update_peer && peer->realtime_contact) { + ast_update_realtime("sippeers", "name", peer->name, "contact", "", "ipaddr", "", + "port", "0", "expires", "0", "sysname", "", "useragent", "", "lastqualify", "0", + SENTINEL); + } + } else { + ast_db_del("SIP/Peer", peer->name); + ast_db_del("SIP/PeerPath", peer->name); + } +} + +void sip_peer_set_auto_nat(struct sip_peer *peer, int nat_detected) +{ + if (peer->nat_auto_rport) { + peer->nat_force_rport = nat_detected; + } + + if (peer->nat_auto_rtp) { + peer->nat_rtp = nat_detected; + } +} + +/* Get peer status as string */ +char *sip_peer_status2str(struct sip_peer *peer) +{ + char *status_buf; + + if (!(status_buf = ast_threadstorage_get(&sip_peer_status2str_buf, 32))) { + return ""; + } + + if (!ast_sockaddr_isnull(&peer->address) && peer->qualify_max) { + if (peer->qualify < 0) { + strcpy(status_buf, "UNREACHABLE"); + } else if (peer->qualify > peer->qualify_max) { + snprintf(status_buf, 32, "LAGGED (%dms)", peer->qualify); + } else { + snprintf(status_buf, 32, "OK (%dms)", peer->qualify); + } + } else { + strcpy(status_buf, "UNKNOWN"); + } + + return status_buf; +} + +/* Get peer mailboxes as string */ +char *sip_peer_get_mailboxes(struct sip_peer *peer, struct ast_str **mailboxes) +{ + struct sip_mailbox *mailbox; + + ast_str_reset(*mailboxes); + + AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, next) { + ast_str_append(mailboxes, 0, "%s%s", ast_str_strlen(*mailboxes) ? "," : "", mailbox->name); + } + + return ast_str_buffer(*mailboxes); +} + +/* Get cached MWI info */ +static int sip_peer_get_messages(struct sip_peer *peer, int *new_messages, int *old_messages) +{ + struct sip_mailbox *mailbox; + int cached; + + *new_messages = 0; + *old_messages = 0; + cached = FALSE; + + AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, next) { + RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup); + struct ast_mwi_state *mwi_state; + + if (!(message = stasis_cache_get(ast_mwi_state_cache(), ast_mwi_state_type(), mailbox->name))) { + continue; + } + + mwi_state = stasis_message_data(message); + + *new_messages += mwi_state->new_msgs; + *old_messages += mwi_state->old_msgs; + cached = TRUE; + } + + return cached; +} + +/* Helper function to update a peer's new_messages and old_messages values */ +void sip_peer_set_messages(struct sip_peer *peer, int new_messages, int old_messages, int locked) +{ + if (!locked) { + ao2_lock(peer); + } + + peer->new_messages = new_messages; + peer->old_messages = old_messages; + + if (!locked) { + ao2_unlock(peer); + } +} + +/* Receive MWI events that we have subscribed to */ +static void sip_peer_mwi_event(void *data, struct stasis_subscription *subscription, struct stasis_message *message) +{ + struct sip_peer *peer = (struct sip_peer *) data; + + /* Peer can't be NULL here but the peer can be in the process of being destroyed. If it is, we don't want to + * send any messages. In most cases, the peer is actually gone and there's no sense sending NOTIFYs that will + * never be answered */ + if (stasis_subscription_final_message(subscription, message) || ao2_ref(peer, 0) == 0) { + return; + } + + if (ast_mwi_state_type() == stasis_message_type(message)) { + sip_peer_send_mwi(peer, FALSE); + } +} + +/* Send message waiting indication to alert peer that they've got voicemail. Both peer and associated sip_dialog must + * be unlocked prior to calling this function. It's possible that this function will get called during peer destruction + * as final messages are processed. The peer will still be valid however */ +int sip_peer_send_mwi(struct sip_peer *peer, int cache_only) +{ + /* Called with peer lock, but releases it */ + struct sip_dialog *dialog; + int new_messages, old_messages; + const char *mwi_exten; + + ao2_lock(peer); + + /* Do we have an IP address or should we only send if there is a MWI subscription? */ + if (ast_sockaddr_isnull(&peer->address) || (peer->subscribe_mwi_only && !peer->mwi_dialog)) { + sip_peer_set_messages(peer, 0, 0, TRUE); + ao2_unlock(peer); + return -1; + } + + /* Attempt to use cached mwi to get message counts */ + if (!sip_peer_get_messages(peer, &new_messages, &old_messages) && !cache_only) { + /* Fall back to manually checking the mailbox if not cache_only and sip_peer_get_messages failed */ + struct ast_str *mailboxes = ast_str_alloca(512); + + sip_peer_get_mailboxes(peer, &mailboxes); + + /* If there is no mailbox do nothing */ + if (!ast_str_strlen(mailboxes)) { + ao2_unlock(peer); + return -1; + } + + ao2_unlock(peer); + ast_app_inboxcount(ast_str_buffer(mailboxes), &new_messages, &old_messages); + ao2_lock(peer); + } + + if (!ast_strlen_zero(peer->mwi_exten)) { + mwi_exten = ast_strdupa(peer->mwi_exten); + } else { + mwi_exten = ast_strdupa(peer->name); + } + + if (peer->mwi_dialog) { + /* Base message on subscription */ + dialog = ao2_bump(peer->mwi_dialog); + ao2_unlock(peer); + } else { + ao2_unlock(peer); + + /* Build temporary dialog for this message */ + if (!(dialog = sip_dialog_alloc(NULL, &peer->socket, SIP_METHOD_NOTIFY, NULL, 0))) { + sip_peer_set_messages(peer, 0, 0, FALSE); + return -1; + } + + if (sip_dialog_build_from_peer(dialog, peer)) { + /* Maybe they're not registered, etc */ + sip_dialog_unlink(dialog); + ao2_ref(dialog, -1); + sip_peer_set_messages(peer, 0, 0, FALSE); + return -1; + } + + sip_dialog_set_our_address(dialog); + /* Destroy this session after 32 secs */ + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } + + /* Send MWI */ + dialog->outgoing = TRUE; + + ao2_lock(dialog); + sip_request_send_notify_with_mwi(dialog, new_messages, old_messages, mwi_exten); + ao2_unlock(dialog); + + ao2_ref(dialog, -1); + sip_peer_set_messages(peer, new_messages, old_messages, FALSE); + return 0; +} + +/* Check availability of peer, also keep NAT open. This is done with 60 seconds between each ping, unless forced by CLI + * or manager. If peer is unreachable, We check every 15 seconds by default. Do *not* hold a dialog lock while calling + * this function. This function calls sip_dialog_alloc, which can cause a deadlock if another sip_dialog is held */ +int sip_peer_send_qualify(struct sip_peer *peer, int force) +{ + struct sip_dialog *dialog; + + if ((!peer->qualify_max && !force) || ast_sockaddr_isnull(&peer->address)) { + /* IF we have no IP, or this isn't to be monitored, return immediately after clearing things out */ + AST_SCHED_DEL_UNREF(sip_sched_context, peer->qualify_sched_id, ao2_cleanup(peer)); + peer->qualify = 0; + + if (peer->qualify_dialog) { + ao2_ref(peer->qualify_dialog, -1); + peer->qualify_dialog = NULL; + } + + return 0; + } + + if (peer->qualify_dialog) { + ast_debug(1, "Still have a OPTIONS dialog active, deleting\n"); + + sip_dialog_unlink(peer->qualify_dialog); + ao2_ref(peer->qualify_dialog, -1); + peer->qualify_dialog = NULL; + } + + if (!(dialog = sip_dialog_alloc(NULL, &peer->socket, SIP_METHOD_OPTIONS, NULL, 0))) { + return -1; + } + + /* No socket transport means that the peer may have been seeded from database */ + peer->qualify_dialog = ao2_bump(dialog); + sip_route_copy(&dialog->route, &peer->path); + + if (!sip_route_empty(&dialog->route)) { + /* Parse SIP URI of first route-set hop and use it as target address */ + sip_get_uri_address(sip_route_first_uri(&dialog->route), &dialog->address); + } + + /* Get the outbound proxy information */ + sip_proxy_set(dialog, sip_proxy_get(dialog, peer)); + + /* Send OPTIONs to peer's full contact */ + if (!ast_strlen_zero(peer->contact)) { + ast_string_field_set(dialog, contact, peer->contact); + } + + if (!ast_strlen_zero(peer->from_user)) { + ast_string_field_set(dialog, from_user, peer->from_user); + } + + if (!ast_strlen_zero(peer->host)) { + ast_string_field_set(dialog, to_host, peer->host); + } else { + ast_string_field_set(dialog, to_host, ast_sockaddr_stringify_host_remote(&peer->address)); + } + + /* Recalculate our side, and recalculate Call ID */ + sip_dialog_set_our_address(dialog); + + AST_SCHED_DEL_UNREF(sip_sched_context, peer->qualify_sched_id, ao2_cleanup(peer)); + ao2_cleanup(dialog->peer); + + dialog->peer = ao2_bump(peer); + dialog->outgoing = TRUE; + + peer->qualify_sent = ast_tvnow(); + + /* Sinks the dialog ref-count */ + if (sip_request_send_options(dialog)) { + /* Immediately unreachable, network problems */ + sip_peer_qualify_timeout(ao2_bump(peer)); + } else if (!force) { + AST_SCHED_REPLACE_UNREF(peer->qualify_sched_id, sip_sched_context, peer->qualify_max * 2, + sip_peer_qualify_timeout, peer, ao2_cleanup(_data), ao2_cleanup(peer), ao2_bump(peer)); + } + + ao2_ref(dialog, -1); + return 0; +} + +/* React to lack of answer to Qualify qualify */ +int sip_peer_qualify_timeout(const void *data) +{ + struct sip_peer *peer = (struct sip_peer *) data; + + peer->qualify_sched_id = -1; + + if (peer->qualify > -1) { + ast_verb(3, "SIP peer '%s' is now unreachable\n", peer->name); + + if (sip_config.realtime_update_peer) { + ast_update_realtime("sippeers", "name", peer->name, "lastqualify", "-1", SENTINEL); + } + + if (peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + + ast_endpoint_set_state(peer->endpoint, AST_ENDPOINT_OFFLINE); + blob = ast_json_pack("{s: s, s: s}", "peer_status", "Unreachable", "time", "-1"); + ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); + } + } + + if (peer->qualify_dialog) { + sip_dialog_unlink(peer->qualify_dialog); + ao2_ref(peer->qualify_dialog, -1); + peer->qualify_dialog = NULL; + } + + /* Don't send a devstate change if nothing changed */ + if (peer->qualify != -1) { + peer->qualify = -1; + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + } + + if (!AST_LIST_EMPTY(&peer->aliases)) { + struct sip_alias *alias; + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (!alias->peer || alias->peer->qualify == -1) { + continue; + } + + ast_log(LOG_NOTICE, "SIP peer '%s' is now unreachable\n", alias->peer->name); + + if (sip_config.realtime_update_peer) { + ast_update_realtime("sippeers", "name", alias->peer->name, "lastqualify", "-1", SENTINEL); + } + + if (alias->peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + + ast_endpoint_set_state(alias->peer->endpoint, AST_ENDPOINT_OFFLINE); + blob = ast_json_pack("{s: s, s: s}", "peer_status", "Unreachable", "time", "-1"); + ast_endpoint_blob_publish(alias->peer->endpoint, ast_endpoint_state_type(), blob); + } + + alias->peer->qualify = -1; + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); + } + } + + /* Try again quickly */ + AST_SCHED_REPLACE_UNREF(peer->qualify_sched_id, sip_sched_context, SIP_QUALIFY_UNREACHABLE * 1000, + sip_peer_qualify, peer, ao2_cleanup(_data), ao2_cleanup(peer), ao2_bump(peer)); + /* Release the ref held by the running scheduler entry */ + ao2_ref(peer, -1); + return 0; +} + +/* Poke peer (send qualify to check if peer is alive and well) */ +int sip_peer_qualify(const void *data) +{ + struct sip_peer *peer = (struct sip_peer *) data; + + peer->qualify_sched_id = -1; + + sip_peer_send_qualify(peer, FALSE); + ao2_ref(peer, -1); + return 0; +} + +/* Send a qualify to all known peers */ +void sip_peer_qualify_all(void) +{ + int when, count; + struct ao2_iterator iter; + struct sip_peer *peer; + + if (!sip_peer_static_count) { /* No peers, just give up */ + return; + } + + count = 0; + when = 0; + + iter = ao2_iterator_init(sip_peers, 0); + + while ((peer = ao2_iterator_next(&iter))) { + ao2_lock(peer); + + /* Only qualify the primary line */ + if (peer->line_index > 1) { + ao2_unlock(peer); + continue; + } + + /* Don't schedule on a peer without qualify enabled */ + if (peer->qualify_max) { + if (count == sip_config.qualify_peers) { + when += sip_config.qualify_gap; + count = 0; + } else { + count++; + } + + AST_SCHED_REPLACE_UNREF(peer->qualify_sched_id, sip_sched_context, when, sip_peer_qualify, peer, + ao2_cleanup(_data), ao2_cleanup(peer), ao2_bump(peer)); + } + + ao2_unlock(peer); + ao2_ref(peer, -1); + } + + ao2_iterator_destroy(&iter); +} + +/* Send donotdisturb, call forward and huntgroup in one bulk update */ +int sip_peer_send_bulk_update(struct sip_peer *peer) +{ + if (ast_sockaddr_isnull(&peer->address)) { + return 0; + } + + if (peer->cisco_mode) { + struct sip_dialog *dialog; + struct ast_str *content; + int new_messages, old_messages; + struct sip_alias *alias; + + if (!(content = ast_str_create(8192))) { + return -1; + } + + if (!(dialog = sip_dialog_alloc(NULL, &peer->socket, SIP_METHOD_REFER, NULL, 0))) { + ast_free(content); + return -1; + } + + /* Don't use sip_dialog_build_from_peer here as it may fail due to the peer not having responded to an + * OPTIONS request yet */ + sip_dialog_set_our_address(dialog); + + if (!ast_strlen_zero(peer->host)) { + ast_string_field_set(dialog, to_host, peer->host); + } else { + ast_string_field_set(dialog, to_host, ast_sockaddr_stringify_host_remote(&peer->address)); + } + + if (!dialog->port_in_uri) { + dialog->port_in_uri = peer->port_in_uri; + } + + if (peer->from_domain_port) { + dialog->from_domain_port = peer->from_domain_port; + } + + ast_string_field_set(dialog, from_user, peer->from_user); + ast_string_field_set(dialog, from_domain, peer->from_domain); + + ast_string_field_set(dialog, contact, peer->contact); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n"); + ast_str_append(&content, 0, " %s\n", peer->do_not_disturb ? "enable" : "disable"); + ast_str_append(&content, 0, " \n", + peer->busy_when_dnd ? "callreject" : "ringeroff"); + ast_str_append(&content, 0, " \n" + "\n" + "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n"); + ast_str_append(&content, 0, " %s\n", peer->hunt_group ? "on" : "off"); + ast_str_append(&content, 0, " \n" + "\n" + "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n"); + + if (!sip_peer_get_messages(peer, &new_messages, &old_messages)) { + struct ast_str *mailboxes = ast_str_alloca(512); + + sip_peer_get_mailboxes(peer, &mailboxes); + + if (ast_str_strlen(mailboxes)) { + ast_app_inboxcount(ast_str_buffer(mailboxes), &new_messages, &old_messages); + sip_peer_set_messages(peer, new_messages, old_messages, FALSE); + } else { + sip_peer_set_messages(peer, 0, 0, FALSE); + } + } + + ast_str_append(&content, 0, " \n", peer->line_index); + ast_str_append(&content, 0, " %s\n", new_messages ? "yes" : "no"); + ast_str_append(&content, 0, " \n", + new_messages, old_messages); + ast_str_append(&content, 0, " \n" + " %s\n", peer->call_forward); + ast_str_append(&content, 0, " %s\n", + !ast_strlen_zero(peer->mwi_exten) && + !strcmp(peer->call_forward, peer->mwi_exten) ? "on" : "off"); + ast_str_append(&content, 0, " \n" + " \n"); + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + int new_messages, old_messages; + + if (!alias->peer) { + continue; + } + + if (!sip_peer_get_messages(alias->peer, &new_messages, &old_messages)) { + struct ast_str *mailboxes = ast_str_alloca(512); + + sip_peer_get_mailboxes(alias->peer, &mailboxes); + + if (ast_str_strlen(mailboxes)) { + ast_app_inboxcount(ast_str_buffer(mailboxes), &new_messages, &old_messages); + sip_peer_set_messages(peer, new_messages, old_messages, FALSE); + } else { + sip_peer_set_messages(peer, 0, 0, FALSE); + } + } + + ast_str_append(&content, 0, " \n", alias->peer->line_index); + ast_str_append(&content, 0, " %s\n", new_messages ? "yes" : "no"); + ast_str_append(&content, 0, " \n", + new_messages, old_messages); + ast_str_append(&content, 0, " \n" + " %s\n", alias->peer->call_forward); + ast_str_append(&content, 0, " %s\n", + !ast_strlen_zero(alias->peer->mwi_exten) && + !strcmp(alias->peer->call_forward, alias->peer->mwi_exten) ? "on" : "off"); + ast_str_append(&content, 0, " \n" + " \n"); + } + + ast_str_append(&content, 0, " \n" + "\n" + "\r\n" + "--uniqueBoundary--\r\n"); + + sip_request_send_refer_with_content(dialog, "multipart/mixed; boundary=uniqueBoundary", + ast_str_buffer(content)); + ao2_ref(dialog, -1); + + ast_free(content); + } else if (peer->feature_events_dialog) { + struct sip_message request; + char boundary[32]; + + sip_message_build_request(&request, peer->feature_events_dialog, SIP_METHOD_NOTIFY, 0, TRUE); + sip_message_add_header(&request, "Event", "as-feature-event"); + sip_message_add_header(&request, "Subscription-State", + peer->feature_events_dialog->expires ? "active" : "terminated;reason=timeout"); + + snprintf(boundary, sizeof(boundary), "%08lx%08lx%08lx", ast_random(), ast_random(), ast_random()); + sip_message_build_header(&request, "Content-Type", "multipart/mixed; boundary=%s", boundary); + + sip_message_build_content(&request, "--%s\r\n", boundary); + sip_message_add_content(&request, "Content-Type: application/x-as-feature-event+xml\r\n" + "\r\n" + "\n" + "\n" + " \n"); + sip_message_build_content(&request, " %s\n", + peer->do_not_disturb ? "true" : "false"); + sip_message_add_content(&request, "\n" + "\r\n"); + sip_message_build_content(&request, "--%s\r\n", boundary); + sip_message_add_content(&request, "Content-Type: application/x-as-feature-event+xml\r\n" + "\r\n" + "\n" + "\n" + " \n" + " forwardImmediate\n"); + sip_message_build_content(&request, " %s\n%s\n", + !ast_strlen_zero(peer->call_forward) ? "true" : "false", peer->call_forward); + sip_message_add_content(&request, "\n" + "\r\n"); + sip_message_build_content(&request, "--%s--\r\n", boundary); + + sip_message_send(peer->feature_events_dialog, &request, SIP_SEND_RELIABLE, + peer->feature_events_dialog->outgoing_cseq); + } + + return 0; +} + +/* Notify peer that the do not disturb status has changed */ +int sip_peer_send_do_not_disturb(struct sip_peer *peer) +{ + if (ast_sockaddr_isnull(&peer->address)) { + return 0; + } + + if (peer->cisco_mode) { + struct sip_dialog *dialog; + struct ast_str *content; + + if (!(dialog = sip_dialog_alloc(NULL, NULL, SIP_METHOD_REFER, NULL, 0))) { + return -1; + } + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + ao2_ref(dialog, -1); + return -1; + } + + sip_dialog_set_our_address(dialog); + content = ast_str_alloca(2048); + + ast_str_append(&content, 0, "\n" + "\n" + " \n"); + ast_str_append(&content, 0, " %s\n", peer->do_not_disturb ? "enable" : "disable"); + ast_str_append(&content, 0, " \n", peer->busy_when_dnd ? "callreject" : "ringeroff"); + ast_str_append(&content, 0, " \n" + "\n"); + + sip_request_send_refer_with_content(dialog, "application/x-cisco-remotecc-request+xml", + ast_str_buffer(content)); + ao2_ref(dialog, -1); + } else if (peer->feature_events_dialog) { + struct sip_message request; + + sip_message_build_request(&request, peer->feature_events_dialog, SIP_METHOD_NOTIFY, 0, TRUE); + sip_message_add_header(&request, "Event", "as-feature-event"); + sip_message_add_header(&request, "Subscription-State", + peer->feature_events_dialog->expires ? "active" : "terminated;reason=timeout"); + sip_message_add_header(&request, "Content-Type", "application/x-as-feature-event+xml"); + + sip_message_add_content(&request, "\n" + "\n" + " \n"); + sip_message_build_content(&request, " %s\n", + peer->do_not_disturb ? "true" : "false"); + sip_message_add_content(&request, "\n"); + + sip_message_send(peer->feature_events_dialog, &request, SIP_SEND_RELIABLE, + peer->feature_events_dialog->outgoing_cseq); + } + + return 0; +} + +/* Notify peer that the huntgroup login state has changed */ +int sip_peer_send_hunt_group(struct sip_peer *peer) +{ + if (ast_sockaddr_isnull(&peer->address)) { + return 0; + } + + if (peer->cisco_mode) { + struct sip_dialog *dialog; + struct ast_str *content; + + if (!(dialog = sip_dialog_alloc(NULL, NULL, SIP_METHOD_REFER, NULL, 0))) { + return -1; + } + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + ao2_ref(dialog, -1); + return -1; + } + + sip_dialog_set_our_address(dialog); + content = ast_str_alloca(2048); + + ast_str_append(&content, 0, "\n" + "\n" + " \n"); + ast_str_append(&content, 0, " %s\n", peer->hunt_group ? "on" : "off"); + ast_str_append(&content, 0, " \n" + "\n"); + + sip_request_send_refer_with_content(dialog, "application/x-cisco-remotecc-request+xml", + ast_str_buffer(content)); + ao2_ref(dialog, -1); + } + + return 0; +} + +/* Notify peer that the call forwarding extension has changed */ +int sip_peer_send_call_forward(struct sip_peer *peer) +{ + if (ast_sockaddr_isnull(&peer->address)) { + return 0; + } + + if (peer->cisco_mode) { + struct sip_dialog *dialog; + struct ast_str *content; + + if (!(dialog = sip_dialog_alloc(NULL, NULL, SIP_METHOD_REFER, NULL, 0))) { + return -1; + } + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + ao2_ref(dialog, -1); + return -1; + } + + sip_dialog_set_our_address(dialog); + + content = ast_str_alloca(2048); + + ast_str_append(&content, 0, "\n" + "\n" + " \n"); + ast_str_append(&content, 0, " %d\n", peer->line_index); + ast_str_append(&content, 0, " %s\n", peer->call_forward); + ast_str_append(&content, 0, " %s\n", + !ast_strlen_zero(peer->mwi_exten) && + !strcmp(peer->call_forward, peer->mwi_exten) ? "on" : "off"); + ast_str_append(&content, 0, " \n" + "\n"); + + sip_request_send_refer_with_content(dialog, "application/x-cisco-remotecc-request+xml", + ast_str_buffer(content)); + ao2_ref(dialog, -1); + } else if (peer->feature_events_dialog) { + struct sip_message request; + + sip_message_build_request(&request, peer->feature_events_dialog, SIP_METHOD_NOTIFY, 0, TRUE); + sip_message_add_header(&request, "Event", "as-feature-event"); + sip_message_add_header(&request, "Subscription-State", + peer->feature_events_dialog->expires ? "active" : "terminated;reason=timeout"); + sip_message_add_header(&request, "Content-Type", "application/x-as-feature-event+xml"); + + sip_message_add_content(&request, "\n" + "\n" + " \n" + " forwardImmediate\n"); + sip_message_build_content(&request, " %s\n", + !ast_strlen_zero(peer->call_forward) ? "true" : "false"); + sip_message_build_content(&request, " %s\n", + !ast_strlen_zero(peer->call_forward) ? "true" : "false", peer->call_forward); + sip_message_add_content(&request, "\n"); + + sip_message_send(peer->feature_events_dialog, &request, SIP_SEND_RELIABLE, + peer->feature_events_dialog->outgoing_cseq); + } + + return 0; +} + +int sip_peer_send_qrt_url(struct sip_peer *peer) +{ + struct sip_dialog *dialog; + struct ast_str *url, *content; + char encoded_name[512]; + + if (ast_strlen_zero(peer->qrt_url)) { + return -1; + } + + url = ast_str_alloca(512); /* Maximum size of a CGIExecute request */ + ast_str_set(&url, 0, "%s%sname=%s", peer->qrt_url, strchr(peer->qrt_url, '?') ? "&" : "?", + ast_uri_encode(peer->device_name, encoded_name, sizeof(encoded_name), ast_uri_http)); + + /* Phones only support URLs up to 256 characters long */ + if (ast_str_strlen(url) > 256) { + ast_log(LOG_WARNING, "QRT URL too long: %s\n", ast_str_buffer(url)); + return -1; + } + + if (!(dialog = sip_dialog_alloc(NULL, NULL, SIP_METHOD_REFER, NULL, 0))) { + return -1; + } + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + ao2_ref(dialog, -1); + return -1; + } + + content = ast_str_alloca(2048); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n" + " 0\n" + " 0\n" + " StationSequenceLast\n" + " 2\n" + " 0\n" + " 0\n" + " 0\n" + " \n" + "\n" + "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-cm+xml\r\n" + "\r\n" + "\n" + "\n" + " \n", ast_str_buffer(url)); + ast_str_append(&content, 0, "\n" + "\r\n" + "--uniqueBoundary--\r\n"); + + sip_request_send_refer_with_content(dialog, "multipart/mixed; boundary=uniqueBoundary", + ast_str_buffer(content)); + ao2_ref(dialog, -1); + return 0; +} + +/* Implement the setvar config line */ +static void sip_variable_build(struct sip_peer *peer, const char *config, int lineno) +{ + char *name, *value; + struct ast_variable *variable; + + name = strdupa(config); + + if (!(value = strchr(name, '='))) { + ast_log(LOG_WARNING, "Invalid variable '%s' at line %d\n", name, lineno); + return; + } + + *value++ = '\0'; + + if ((variable = ast_variable_new(name, value, ""))) { + if (ast_variable_list_replace(&peer->channel_variables, variable)) { + ast_variable_list_append(&peer->channel_variables, variable); + } + } +} + +/* Add a mailbox to a peer */ +static void sip_mailbox_build(struct sip_peer *peer, const char *config, int lineno) +{ + char *parse, *name; + struct sip_mailbox *mailbox; + + parse = ast_strdupa(config); + + while ((name = strsep(&parse, ","))) { + name = ast_strip(name); + + if (ast_strlen_zero(name)) { + ast_log(LOG_WARNING, "Invalid mailbox '%s' at line %d\n", config, lineno); + continue; + } + + /* Check whether the mailbox is already in the list */ + AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, next) { + if (!strcmp(mailbox->name, name)) { + break; + } + } + + if (mailbox) { + mailbox->removed = FALSE; + } else { + if (!(mailbox = ast_calloc(1, sizeof(*mailbox)))) { + return; + } + + mailbox->peer = peer; + mailbox->name = ast_strdup(name); + + AST_LIST_INSERT_TAIL(&peer->mailboxes, mailbox, next); + } + } +} + +/* Destroy mailbox subscriptions */ +static void sip_mailbox_destroy(struct sip_mailbox *mailbox) +{ + if (mailbox->mwi_subscription) { + mailbox->mwi_subscription = ast_mwi_unsubscribe_and_join(mailbox->mwi_subscription); + } + + ast_free(mailbox->name); + ast_free(mailbox); +} + +static void sip_subscription_build(struct sip_peer *peer, const char *config, int lineno) +{ + char *parse, *exten, *context; + struct sip_subscription *subscription; + + parse = ast_strdupa(config); + + while ((exten = strsep(&parse, ","))) { + if ((context = strchr(exten, '@'))) { + *context++ = '\0'; + context = ast_strip(context); + } else { + context = ast_strdupa(S_OR(peer->subscribe_context, peer->context)); + } + + exten = ast_strip(exten); + + if (ast_strlen_zero(exten) || ast_strlen_zero(context)) { + ast_log(LOG_WARNING, "Invalid subscription '%s' at line %d\n", config, lineno); + continue; + } + + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, next) { + if (!strcmp(subscription->exten, exten) && !strcmp(subscription->context, context)) { + break; + } + } + + if (subscription) { + subscription->removed = FALSE; + } else { + if (!(subscription = ast_calloc_with_stringfields(1, struct sip_subscription, 64))) { + return; + } + + ast_string_field_set(subscription, exten, exten); + ast_string_field_set(subscription, context, context); + + AST_LIST_INSERT_TAIL(&peer->subscriptions, subscription, next); + } + } +} + +/* Destroy extension state subscription */ +static void sip_subscription_destroy(struct sip_subscription *subscription) +{ + if (subscription->dialog) { + sip_dialog_unlink(subscription->dialog); + ao2_ref(subscription->dialog, -1); + } + + ast_string_field_free_memory(subscription); + ast_free(subscription); +} + +static void sip_alias_build(struct sip_peer *peer, const char *config, int lineno, int *line_index) +{ + char *parse, *name; + struct sip_alias *alias; + + parse = ast_strdupa(config); + + while ((name = strsep(&parse, ","))) { + name = ast_strip(name); + + if (ast_strlen_zero(name)) { + ast_log(LOG_WARNING, "Invalid register '%s' at line %d\n", config, lineno); + continue; + } + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (!strcmp(alias->name, name)) { + break; + } + } + + if (alias) { + alias->removed = FALSE; + } else { + if (!(alias = ast_calloc(1, sizeof(*alias)))) { + return; + } + + if (!(alias->name = ast_strdup(name))) { + ast_free(alias); + return; + } + + AST_LIST_INSERT_TAIL(&peer->aliases, alias, next); + } + + alias->line_index = (*line_index)++; + } +} + +static void sip_alias_destroy(struct sip_alias *alias) +{ + if (alias->peer) { + alias->peer->qualify = 0; + + ast_sockaddr_setnull(&alias->peer->address); + sip_socket_set_transport(&alias->peer->socket, 0); + + ast_string_field_set(alias->peer, contact, NULL); + ast_string_field_set(alias->peer, useragent, NULL); + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); + ao2_ref(alias->peer, -1); + } + + ast_free(alias->name); + ast_free(alias); +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/pickup.c asterisk-22.6.0/channels/sip/pickup.c --- asterisk-22.6.0.orig/channels/sip/pickup.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/pickup.c 2025-10-21 18:38:17.461217853 +1300 @@ -0,0 +1,338 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" + +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/channel.h" +#include "asterisk/causes.h" +#include "asterisk/callerid.h" +#include "asterisk/indications.h" +#include "asterisk/stasis.h" +#include "asterisk/pbx.h" +#include "asterisk/pickup.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/session_timer.h" +#include "include/proxy.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/pickup.h" + +struct sip_pickup_notify_args { + ast_group_t callgroup; + struct ast_namedgroups *named_callgroups; +}; + +static void *sip_pickup_thread(void *data); +static int sip_pickup_notify_cmp(void *data, void *arg, int flags); +static void *sip_pickup_notify_thread(void *data); +static void sip_pickup_notify_event(void *data, struct stasis_subscription *sub, struct stasis_message *msg); + +static struct stasis_subscription *sip_pickup_notify_subscription; /* Subscription for call ringing events */ + +/* SIP pickup support function. Starts in a new thread, then pickup the call */ +static void *sip_pickup_thread(void *data) +{ + struct ast_channel *channel = (struct ast_channel *) data; + + ast_channel_hangupcause_set(channel, AST_CAUSE_NORMAL_CLEARING); + + if (ast_pickup_call(channel)) { + ast_channel_hangupcause_set(channel, AST_CAUSE_CALL_REJECTED); + } + + ast_hangup(channel); + ast_channel_unref(channel); + return NULL; +} + +/* Pickup a call using the subsystem in features.c This is executed in a separate thread */ +int sip_pickup_call(struct ast_channel *channel) +{ + pthread_t threadid; + + ast_channel_ref(channel); + + if (ast_pthread_create_detached_background(&threadid, NULL, sip_pickup_thread, channel)) { + ast_debug(1, "Unable to start group pickup thread on channel %s\n", ast_channel_name(channel)); + ast_channel_unref(channel); + return -1; + } + + ast_debug(1, "Started group pickup thread on channel %s\n", ast_channel_name(channel)); + return 0; +} + +/* No channel or dialog locks should be held while calling this function */ +int sip_pickup_exten(struct ast_channel *channel, const char *exten, const char *context) +{ + struct ast_app *app_pickup; + struct ast_str *args; + + if (!(app_pickup = pbx_findapp("Pickup"))) { + ast_log(LOG_ERROR, + "Unable to perform pickup: Application 'Pickup' not loaded (app_directed_pickup.so)\n"); + return -1; + } + + args = ast_str_alloca(AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2); + ast_str_set(&args, 0, "%s@%s", exten, sip_config.pickup_context ? context : "PICKUPMARK"); + + ast_debug(2, "About to call Pickup(%s)\n", ast_str_buffer(args)); + /* There is no point in capturing the return value since pickup_exec doesn't return anything meaningful unless + * the passed data is an empty string (which in our case it will not be) */ + pbx_exec(channel, app_pickup, ast_str_buffer(args)); + return 0; +} + +static int sip_pickup_notify_cmp(void *data, void *arg, int flags) +{ + struct sip_peer *peer; + struct sip_pickup_notify_args *pickup_args; + + peer = (struct sip_peer *) data; + pickup_args = (struct sip_pickup_notify_args *) arg; + + ao2_lock(peer); + + if (!peer->cisco_mode || !peer->pickup_notify) { + ao2_unlock(peer); + return 0; + } + + if (ast_sockaddr_isnull(&peer->address) || peer->ringing || peer->inuse || peer->do_not_disturb) { + ao2_unlock(peer); + return 0; + } + + if ((peer->pickupgroup & pickup_args->callgroup) || + ast_namedgroups_intersect(peer->named_pickupgroups, pickup_args->named_callgroups)) { + if (ast_tvdiff_sec(ast_tvnow(), peer->pickup_notify_sent) > peer->pickup_notify_timer) { + peer->pickup_notify_sent = ast_tvnow(); + ao2_unlock(peer); + return CMP_MATCH; + } + } + + ao2_unlock(peer); + return 0; +} + +static void *sip_pickup_notify_thread(void *data) +{ + char *device_name, name[AST_CHANNEL_NAME], *caller_number, *connected_number; + struct ast_channel_iterator *channel_iter; + struct ast_channel *pickup_channel, *channel; + struct timeval creation_time; + struct sip_pickup_notify_args pickup_args; + struct ao2_iterator *peer_iter; + struct sip_peer *peer; + struct sip_dialog *dialog; + struct ast_str *content; + + device_name = (char * ) data; + + snprintf(name, sizeof(name), "%s-", (char *) device_name); + channel_iter = ast_channel_iterator_by_name_new(name, strlen(name)); + ast_free(device_name); + + pickup_channel = NULL; + creation_time = ast_tv(0, 0); + + while ((channel = ast_channel_iterator_next(channel_iter))) { + ast_channel_lock(channel); + + /* Pick the youngest ringing channel */ + if (ast_channel_state(channel) == AST_STATE_RINGING && + ast_tvcmp(ast_channel_creationtime(channel), creation_time) > 0) { + if (pickup_channel) { + ast_channel_unref(pickup_channel); + } + + pickup_channel = ast_channel_ref(channel); + creation_time = ast_channel_creationtime(channel); + } + + ast_channel_unlock(channel); + ast_channel_unref(channel); + } + + ast_channel_iterator_destroy(channel_iter); + + if (!pickup_channel) { + return NULL; + } + + ast_channel_lock(pickup_channel); + + pickup_args.callgroup = ast_channel_callgroup(pickup_channel); + pickup_args.named_callgroups = ast_ref_namedgroups(ast_channel_named_callgroups(pickup_channel)); + + caller_number = ast_strdupa(S_COR(ast_channel_caller(pickup_channel)->id.number.valid, + ast_channel_caller(pickup_channel)->id.number.str, "")); + connected_number = ast_strdupa(S_COR(ast_channel_connected(pickup_channel)->id.number.valid, + ast_channel_connected(pickup_channel)->id.number.str, "")); + + ast_channel_unlock(pickup_channel); + ast_channel_unref(pickup_channel); + + if (!pickup_args.callgroup && !pickup_args.named_callgroups) { + return NULL; + } + + /* We use ao2_callback here so that we don't hold the lock on the peers container while sending the notify + * dialogs */ + if (!(peer_iter = ao2_callback(sip_peers, OBJ_MULTIPLE, sip_pickup_notify_cmp, &pickup_args))) { + return NULL; + } + + ast_unref_namedgroups(pickup_args.named_callgroups); + content = ast_str_alloca(4096); + + while ((peer = ao2_iterator_next(peer_iter))) { + if ((!(dialog = sip_dialog_alloc(NULL, NULL, SIP_METHOD_REFER, NULL, 0)))) { + ao2_ref(peer, -1); + continue; + } + + sip_socket_set_transport(&dialog->socket, 0); + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + + ao2_ref(dialog, -1); + ao2_ref(peer, -1); + continue; + } + + ast_str_reset(content); + + if (peer->pickup_notify & (SIP_PICKUP_NOTIFY_FROM | SIP_PICKUP_NOTIFY_TO)) { + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n" + " notify_display\n" + " "); + + if (peer->pickup_notify & SIP_PICKUP_NOTIFY_FROM) { + ast_str_append(&content, 0, "From %s", connected_number); + } + + if (peer->pickup_notify & SIP_PICKUP_NOTIFY_TO) { + ast_str_append(&content, 0, "%s %s", + (peer->pickup_notify & SIP_PICKUP_NOTIFY_FROM) ? " to" : "To", + caller_number); + } + + ast_str_append(&content, 0, "\n" + " %d\n", peer->pickup_notify_timer); + ast_str_append(&content, 0, " 0\n" + " 1\n" + " \n" + "\n" + "\r\n"); + } + + if (peer->pickup_notify & SIP_PICKUP_NOTIFY_BEEP) { + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n" + " DtZipZip\n" + " all\n" + " \n" + "\n" + "\r\n"); + } + + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + sip_request_send_refer_with_content(dialog, "multipart/mixed; boundary=uniqueBoundary", + ast_str_buffer(content)); + ao2_ref(dialog, -1); + ao2_ref(peer, -1); + } + + ao2_iterator_destroy(peer_iter); + return NULL; +} + +void sip_pickup_notify_subscribe(void) +{ + if (!sip_pickup_notify_subscription) { + sip_pickup_notify_subscription = stasis_subscribe(ast_device_state_topic_all(), + sip_pickup_notify_event, NULL); + } +} + +void sip_pickup_notify_unsubscribe(void) +{ + if (sip_pickup_notify_subscription) { + sip_pickup_notify_subscription = stasis_unsubscribe(sip_pickup_notify_subscription); + } +} + +static void sip_pickup_notify_event(void *data, struct stasis_subscription *subscription, + struct stasis_message *message) +{ + struct ast_device_state_message *device_state; + char *device_name; + pthread_t threadid; + + if (stasis_message_type(message) != ast_device_state_message_type()) { + return; + } + + device_state = stasis_message_data(message); + + if (device_state->state != AST_DEVICE_RINGING) { + return; + } + + if (!(device_name = ast_strdup(device_state->device))) { + return; + } + + if (ast_pthread_create_detached_background(&threadid, NULL, sip_pickup_notify_thread, device_name)) { + ast_free(device_name); + } +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/proxy.c asterisk-22.6.0/channels/sip/proxy.c --- asterisk-22.6.0.orig/channels/sip/proxy.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/proxy.c 2025-10-21 18:38:17.461217853 +1300 @@ -0,0 +1,162 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/astobj2.h" +#include "asterisk/strings.h" +#include "asterisk/channel.h" +#include "asterisk/indications.h" +#include "asterisk/pbx.h" +#include "asterisk/acl.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" + +/* Parse proxy string and return an ao2_alloc'd proxy. If proxy arg is non-NULL, no allocation is performed and proxy + * is used instead */ +struct sip_proxy *sip_proxy_build(const char *config, int lineno, struct sip_proxy *proxy) +{ + enum ast_transport transport; + char *protocol, *host, *option; + int force, port; + + /* Format is: [transport://]host[:port][,force] */ + protocol = ast_strdupa(config); + + if (!strncasecmp(protocol, "tcp://", 6)) { + transport = AST_TRANSPORT_TCP; + host = protocol + 6; + } else if (!strncasecmp(protocol, "tls://", 6)) { + transport = AST_TRANSPORT_TLS; + host = protocol + 6; + } else if (!strncasecmp(protocol, "udp://", 6)) { + transport = AST_TRANSPORT_UDP; + host = protocol + 6; + } else { + if (strstr(protocol, "://")) { + ast_log(LOG_NOTICE, "Invalid transport for '%s' on line %d\n", config, lineno); + return NULL; + } + + transport = AST_TRANSPORT_UDP; + host = protocol; + } + + if ((option = strrchr(host, ','))) { + *option++ = '\0'; + force = !strcasecmp(option, "force"); + } else { + force = FALSE; + } + + if (sip_parse_port(host, &port)) { + ast_log(LOG_WARNING, "Invalid port '%s' on line %d\n", config, lineno); + return NULL; + } + + if (!proxy) { + if (!(proxy = ao2_alloc(sizeof(*proxy), NULL))) { + return NULL; + } + } + + ast_copy_string(proxy->host, host, sizeof(proxy->host)); + + proxy->transport = transport; + proxy->port = port; + proxy->force = force; + proxy->last_dns_update = time(NULL); + + /* If it's actually an IP address and not a name, there's no need for a managed lookup */ + if (!ast_sockaddr_parse(&proxy->address, proxy->host, 0)) { + char service[MAXHOSTNAMELEN]; + + /* Ok, not an IP address, then let's check if it's a domain or host */ + proxy->address.ss.ss_family = AF_INET; + snprintf(service, sizeof(service), "_%s._%s", + sip_srv_service(proxy->transport), sip_srv_protocol(proxy->transport)); + + if (ast_get_ip_or_srv(&proxy->address, proxy->host, + sip_config.srv_lookup && !proxy->port ? service : NULL) == -1) { + ast_log(LOG_WARNING, "Unable to locate host '%s'\n", proxy->host); + return FALSE; + } + } + + ast_sockaddr_set_port(&proxy->address, proxy->port); + return proxy; +} + +/* Maintain proper refcounts for a sip_dialog's outboundproxy. This function sets dialog's outboundproxy pointer to the + * one referenced by the proxy parameter. Because proxy may be a refcounted object, and because dialog's old proxy may + * also be a refcounted object, we need to maintain the proper refcounts */ +void sip_proxy_set(struct sip_dialog *dialog, struct sip_proxy *proxy) +{ + struct sip_proxy *old_proxy = dialog->proxy; + + /* The sip_config.proxy is statically allocated, and so we don't ever need to adjust refcounts for it */ + if (proxy && proxy != &sip_config.proxy) { + ao2_ref(proxy, +1); + } + + dialog->proxy = proxy; + + if (old_proxy && old_proxy != &sip_config.proxy) { + ao2_ref(old_proxy, -1); + } +} + +/* Get default outbound proxy or global proxy */ +struct sip_proxy *sip_proxy_get(struct sip_dialog *dialog, struct sip_peer *peer) +{ + if (dialog && dialog->proxy) { + ast_debug(1, "Applying dialplan set outbound proxy to this call\n"); + return dialog->proxy; + } + + if (peer && peer->proxy) { + ast_debug(1, "Applying peer outbound proxy to this call\n"); + return peer->proxy; + } + + if (!ast_strlen_zero(sip_config.proxy.host)) { + ast_debug(1, "Applying global outbound proxy to this call\n"); + return &sip_config.proxy; + } + + ast_debug(1, "Not applying outbound proxy to this call\n"); + return NULL; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/realtime.c asterisk-22.6.0/channels/sip/realtime.c --- asterisk-22.6.0.orig/channels/sip/realtime.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/realtime.c 2025-10-21 18:38:17.462217826 +1300 @@ -0,0 +1,222 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/paths.h" +#include "asterisk/sched.h" +#include "asterisk/netsock2.h" +#include "asterisk/channel.h" +#include "asterisk/indications.h" +#include "asterisk/pbx.h" +#include "asterisk/config.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/realtime.h" +#include "include/utils.h" +#include "include/config.h" + +static int sip_realtime_by_name(const char **name, const struct ast_sockaddr *address, const char *host, + struct ast_variable **variables); +static int sip_realtime_by_address(const char **name, const struct ast_sockaddr *address, const char *host, + struct ast_variable **variables); + +static int sip_realtime_by_name(const char **name, const struct ast_sockaddr *address, const char *host, + struct ast_variable **variables) +{ + /* Peer by name and host=dynamic */ + if ((*variables = ast_load_realtime("sippeers", "name", *name, "host", "dynamic", SENTINEL))) { + /* Empty */ ; + /* Peer by name and host=HOSTNAME */ + } else if ((*variables = ast_load_realtime("sippeers", "name", *name, "host", host, SENTINEL))) { + /* Empty */ ; + } else if ((*variables = ast_load_realtime("sippeers", "name", *name, SENTINEL))) { + /* If this one loaded something, then we need to ensure that the host field matched. The only reason + * why we can't have this as a criteria is because we only have the IP address and the host field might + * be set as a name (and the reverse PTR might not match) */ + if (address) { + const char *host; + + if ((host = ast_variable_find_in_list(*variables, "host"))) { + struct ast_sockaddr *addresses = NULL; + + if (!ast_sockaddr_resolve(&addresses, host, PARSE_PORT_FORBID, AF_INET) || + ast_sockaddr_cmp(&addresses[0], address)) { + /* No match */ + ast_variables_destroy(*variables); + *variables = NULL; + } + + ast_free(addresses); + } + } + } + + /* Did we find anything? */ + if (*variables) { + return TRUE; + } + + return FALSE; +} + +/* If we return true, then *name is set */ +static int sip_realtime_by_address(const char **name, const struct ast_sockaddr *address, const char *host, + struct ast_variable **variables) +{ + char port[6]; /* Up to 5 digits plus null terminator */ + + ast_copy_string(port, ast_sockaddr_stringify_port(address), sizeof(port)); + *name = NULL; /* We're not finding this peer by this name anymore. Reset it */ + + /* First check for fixed IP hosts */ + if (!(*variables = ast_load_realtime("sippeers", "host", host, "port", port, SENTINEL))) { + /* Nothing found? */ + return FALSE; + } + + /* Check peer name. It must not be empty. There may exist a different match that does have a name, but it's too + * late for that now */ + if (!*name && !(*name = ast_variable_find_in_list(*variables, "name"))) { + ast_log(LOG_WARNING, "Found peer for IP %s but it has no name\n", host); + + ast_variables_destroy(*variables); + *variables = NULL; + return FALSE; + } + + return TRUE; +} + +/* Get peer from realtime storage. This is never called with both name and addr at the same time. If you do, be prepared + * to get a peer with a different name than name */ +struct sip_peer *sip_realtime_load(const char *name, const struct ast_sockaddr *address, int devicestate_only) +{ + struct sip_peer *peer; + struct ast_variable *variables; + char host[INET6_ADDRSTRLEN]; + + if (address) { + ast_copy_string(host, ast_sockaddr_stringify_addr(address), sizeof(host)); + } else { + host[0] = '\0'; + } + + variables = NULL; + + if (name && sip_realtime_by_name(&name, address, host, &variables)) { + /* Empty */ ; + } else if (address && sip_realtime_by_address(&name, address, host, &variables)) { + /* Empty */ ; + } else { + return NULL; + } + + /* Peer found in realtime, now build it in memory */ + if (!(peer = sip_peer_build(name, variables, TRUE, devicestate_only))) { + ast_variables_destroy(variables); + return NULL; + } + + ast_debug(3, "Realtime peer '%s' loaded from database to memory\n", peer->name); + + if (sip_config.realtime_cache_peer && !devicestate_only) { + peer->realtime_cache_peer = TRUE; /* Cache peer */ + + if (sip_config.realtime_auto_clear) { + AST_SCHED_REPLACE_UNREF(peer->register_expires_sched_id, sip_sched_context, + sip_config.realtime_auto_clear * 1000, sip_peer_unregister, peer, ao2_cleanup(_data), + ao2_cleanup(peer), ao2_bump(peer)); + } + + ao2_link(sip_peers, peer); + + if (!ast_sockaddr_isnull(&peer->address)) { + ao2_link(sip_peer_addresses, peer); + } + } + + peer->realtime = TRUE; + ast_variables_destroy(variables); + return peer; +} + +/* Update peer object in realtime storage. If the Asterisk system name is set in asterisk.conf, we will use that name + * and store that in the "regserver" field in the sippeers table to facilitate multi-server setups */ +void sip_realtime_update(struct sip_peer *peer) +{ + char port[6], address[INET6_ADDRSTRLEN], qualify[16], expires[16], *system_label; + const char *system_name; + + ast_copy_string(address, + !ast_sockaddr_isnull(&peer->address) ? ast_sockaddr_stringify_addr(&peer->address) : "", + sizeof(address)); + ast_copy_string(port, + ast_sockaddr_port(&peer->address) ? ast_sockaddr_stringify_port(&peer->address) : "", sizeof(port)); + + snprintf(qualify, sizeof(qualify), "%d", peer->qualify); + + /* Expiration time */ + if (peer->register_expires_sched_id == -1) { + expires[0] = '\0'; + } else { + snprintf(expires, sizeof(expires), "%ld", + time(NULL) + ast_sched_when(sip_sched_context, peer->register_expires_sched_id)); + } + + system_name = ast_config_AST_SYSTEM_NAME; + system_label = NULL; + + if (ast_strlen_zero(system_name)) { /* No system name, disable this */ + system_name = NULL; + } else if (sip_config.realtime_save_sysname) { + system_label = "sysname"; + } + + ast_update_realtime("sippeers", "name", peer->name, "ipaddr", address, "port", port, "expires", expires, + "useragent", peer->useragent, "qualify", qualify, system_label, system_name, SENTINEL); + + if (peer->contact) { + ast_update_realtime("sippeers", "name", peer->name, "contact", peer->contact, SENTINEL); + } + + if (sip_config.realtime_save_path) { + struct ast_str *path = sip_route_list(&peer->path, 0); + + if (path) { + ast_update_realtime("sippeers", "name", peer->name, "routepath", ast_str_buffer(path), SENTINEL); + ast_free(path); + } + } +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/recording.c asterisk-22.6.0/channels/sip/recording.c --- asterisk-22.6.0.orig/channels/sip/recording.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/recording.c 2025-10-21 18:38:17.463217799 +1300 @@ -0,0 +1,247 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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 + extended + ***/ + +#include "asterisk.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/channel.h" +#include "asterisk/callerid.h" +#include "asterisk/indications.h" +#include "asterisk/stasis.h" +#include "asterisk/pbx.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/config.h" +#include "include/recording.h" + +static int sip_recording_wait_for_answer(void *data); +static void *sip_recording_thread(void *data); + +static int sip_recording_wait_for_answer(void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + ao2_lock(dialog); + + if (ast_channel_state(dialog->channel) != AST_STATE_UP) { + ao2_unlock(dialog); + return -1; + } + + if (!dialog->recording_active) { + ao2_unlock(dialog); + return -1; + } + + ao2_unlock(dialog); + return 0; +} + +static void *sip_recording_thread(void *data) +{ + struct sip_recording_data *recording_data; + struct sip_dialog *dialog, *target_dialog; + struct ast_channel *channel; + RAII_VAR(struct ast_format_cap *, format_cap, NULL, ao2_cleanup); + struct ast_party_connected_line connected_line; + char *peer_name, *uniqueid, *chan_name; + int cause; + + recording_data = (struct sip_recording_data *) data; + + if (!(target_dialog = sip_dialog_find(recording_data->call_id, recording_data->local_tag, recording_data->remote_tag))) { + ast_debug(1, "No dialog with Call-ID: '%s' From: tag='%s' To: tag='%s'\n", + recording_data->call_id, recording_data->local_tag, recording_data->remote_tag); + goto cleanup; + } + + ao2_lock(target_dialog); + + if (!(channel = target_dialog->channel)) { + ast_debug(1, "No owner channel\n"); + + ao2_unlock(target_dialog); + ao2_ref(target_dialog, -1); + goto cleanup; + } + + peer_name = ast_strdupa(target_dialog->peer->name); + chan_name = ast_strdupa(ast_channel_name(channel)); + uniqueid = ast_strdupa(ast_channel_uniqueid(channel)); + + format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + + ast_format_cap_append(format_cap, ast_channel_readformat(channel), 0); + ao2_unlock(target_dialog); + + if (!(channel = ast_request("SIP", format_cap, NULL, NULL, peer_name, &cause))) { + ast_debug(1, "Unable to request channel\n"); + ao2_ref(target_dialog, -1); + goto cleanup; + } + + dialog = ast_channel_tech_pvt(channel); + + ast_string_field_set(dialog, join_call_id, recording_data->call_id); + ast_string_field_set(dialog, join_local_tag, recording_data->local_tag); + ast_string_field_set(dialog, join_remote_tag, recording_data->remote_tag); + + /* We are abusing the onhold flag to set the inactive attribute in the SDP, so bump the onhold counter because + * when recording starts the reinvite code will decrement onhold when that flag is cleared */ + dialog->onhold = SIP_ONHOLD_INACTIVE; + dialog->peer->onhold++; + + if (recording_data->outgoing) { + dialog->sdp_relay_nearend = TRUE; + } else { + dialog->sdp_relay_farend = TRUE; + } + + ast_party_connected_line_set_init(&connected_line, ast_channel_connected(channel)); + connected_line.id.name.valid = TRUE; + connected_line.id.name.str = "Record"; + ast_channel_set_connected_line(channel, &connected_line, NULL); + + ast_channel_context_set(channel, dialog->peer->context); + ast_channel_exten_set(channel, "record"); + ast_channel_priority_set(channel, 1); + + pbx_builtin_setvar_helper(channel, "RECORD_PEERNAME", peer_name); + pbx_builtin_setvar_helper(channel, "RECORD_UNIQUEID", uniqueid); + pbx_builtin_setvar_helper(channel, "RECORD_CHANNEL", chan_name); + pbx_builtin_setvar_helper(channel, "RECORD_DIRECTION", recording_data->outgoing ? "out" : "in"); + + if (ast_call(channel, peer_name, 5000)) { + ast_debug(1, "Unable to call\n"); + + ast_hangup(channel); + ao2_ref(target_dialog, -1); + goto cleanup; + } + + if (ast_safe_sleep_conditional(channel, 5000, sip_recording_wait_for_answer, dialog)) { + ast_debug(1, "No answer\n"); + + ast_hangup(channel); + ao2_ref(target_dialog, -1); + goto cleanup; + } + + ao2_lock(target_dialog); + + if (recording_data->outgoing) { + target_dialog->record_outgoing_dialog = ao2_bump(dialog); + } else { + target_dialog->record_incoming_dialog = ao2_bump(dialog); + } + + ao2_unlock(target_dialog); + ao2_ref(target_dialog, -1); + + if (!ast_check_hangup(channel)) { + ast_pbx_run(channel); + } + +cleanup: + ast_string_field_free_memory(recording_data); + ast_free(recording_data); + return NULL; +} + +int sip_recording_start(const char *call_id, const char *local_tag, const char *remote_tag, int outgoing) +{ + pthread_t threadid; + struct sip_recording_data *recording_data; + + if (!(recording_data = ast_calloc_with_stringfields(1, struct sip_recording_data, 512))) { + return -1; + } + + ast_string_field_set(recording_data, call_id, call_id); + ast_string_field_set(recording_data, local_tag, local_tag); + ast_string_field_set(recording_data, remote_tag, remote_tag); + recording_data->outgoing = outgoing; + + if (ast_pthread_create_detached_background(&threadid, NULL, sip_recording_thread, recording_data)) { + ast_string_field_free_memory(recording_data); + ast_free(recording_data); + } + + return 0; +} + +int sip_recording_stop(const char *call_id, const char *local_tag, const char *remote_tag) +{ + struct sip_dialog *target_dialog; + struct ast_channel *channel; + + if (!(target_dialog = sip_dialog_find(call_id, local_tag, remote_tag))) { + ast_debug(1, "No dialog with Call-ID: '%s' From: tag='%s' To: tag='%s'\n", + call_id, local_tag, remote_tag); + return -1; + } + + if (target_dialog->record_outgoing_dialog) { + ao2_lock(target_dialog->record_outgoing_dialog); + + if ((channel = target_dialog->record_outgoing_dialog->channel)) { + ast_softhangup(channel, AST_SOFTHANGUP_EXPLICIT); + } + + ao2_unlock(target_dialog->record_outgoing_dialog); + ao2_lock(target_dialog); + + ao2_ref(target_dialog->record_outgoing_dialog, -1); + target_dialog->record_outgoing_dialog = NULL; + + ao2_unlock(target_dialog); + } + + if (target_dialog->record_incoming_dialog) { + ao2_lock(target_dialog->record_incoming_dialog); + + if ((channel = target_dialog->record_incoming_dialog->channel)) { + ast_softhangup(channel, AST_SOFTHANGUP_EXPLICIT); + } + + ao2_unlock(target_dialog->record_incoming_dialog); + ao2_lock(target_dialog); + + ao2_ref(target_dialog->record_incoming_dialog, -1); + target_dialog->record_incoming_dialog = NULL; + + ao2_unlock(target_dialog); + } + + ao2_ref(target_dialog, -1); + return 0; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/registrations.c asterisk-22.6.0/channels/sip/registrations.c --- asterisk-22.6.0.orig/channels/sip/registrations.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/registrations.c 2025-10-21 18:38:17.463217799 +1300 @@ -0,0 +1,612 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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 + extended + ***/ + +#include "asterisk.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/utils.h" +#include "asterisk/channel.h" +#include "asterisk/indications.h" +#include "asterisk/pbx.h" +#include "asterisk/stasis_system.h" +#include "asterisk/dnsmgr.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/registrations.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" + +static void sip_registration_destroy(void *data); +static int __sip_registration_sched_resend(const void *data); +static int __sip_registration_stop_timeout(const void *data); +static int __sip_registration_start_timeout(const void *data); +static int __sip_registration_unlink(const void *data); + +struct ao2_container *sip_registrations = NULL; + +int sip_registration_cmp(void *data, void *arg, int flags) +{ + struct sip_registration *registration; + const char *config; + + registration = (struct sip_registration *) data; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + struct sip_registration *registration = (struct sip_registration *) arg; + + config = registration->config; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + config = (const char *) arg; + } else { + return 0; + } + + if (!strcmp(registration->config, config)) { + return CMP_MATCH | CMP_STOP; + } + + return 0; +} + +/* Create sip_registration object from register line and link into registration container */ +int sip_registration_build(const char *config, int lineno) +{ + struct sip_registration *registration; + int port, domain_port, expires; + enum ast_transport transport; + char *protocol, *user, *domain, *host, *secret, *md5_secret, *authorization_user, *exten, *sep; + + if (strchr(config, '?')) { + ast_log(LOG_WARNING, "Specifying a peer is no longer supported at line %d\n", lineno); + return -1; + } + + protocol = ast_strdupa(config); + + if (!strncmp(protocol, "udp://", 6)) { + transport = AST_TRANSPORT_UDP; + user = protocol + 6; + } else if (!strncmp(protocol, "tcp://", 6)) { + transport = AST_TRANSPORT_TCP; + user = protocol + 6; + } else if (!strncmp(protocol, "tls://", 6)) { + transport = AST_TRANSPORT_TLS; + user = protocol + 6; + } else { + if (strstr(protocol, "://")) { + ast_log(LOG_WARNING, "Invalid transport for '%s' at line %d\n", config, lineno); + return -1; + } + + transport = AST_TRANSPORT_UDP; + user = protocol; + } + + if (!(host = strrchr(user, '@'))) { + ast_log(LOG_WARNING, "Missing host for '%s' at line %d\n", config, lineno); + return -1; + } + + *host++ = '\0'; + + if ((domain = strchr(user, '@'))) { + *domain++ = '\0'; + domain_port = 0; + + /* Might be secret or port */ + if ((secret = strchr(domain, ':'))) { + /* This will fail if the secret is all digits, the port would have to be specified */ + if (sscanf(secret, "%30d:", &domain_port) == 1 && port >= 1 && port <= 65535) { + *secret++ = '\0'; + + if ((secret = strchr(secret, ':'))) { + *secret++ = '\0'; + } + } + } else { + secret = NULL; + } + } else { + domain = NULL; + domain_port = 0; + + if ((secret = strchr(user, ':'))) { + *secret++ = '\0'; + } else { + secret = NULL; + } + } + + if (secret && (md5_secret = strchr(secret, ':'))) { + *md5_secret++ = '\0'; + } else { + md5_secret = NULL; + } + + if (md5_secret && (authorization_user = strchr(md5_secret, ':'))) { + *authorization_user++ = '\0'; + } else { + authorization_user = NULL; + } + + if ((sep = strchr(host, '~'))) { + *sep++ = '\0'; + + if (sscanf(sep, "%30d", &expires) != 1 || + expires < sip_config.register_min_expires || expires > sip_config.register_max_expires) { + ast_log(LOG_WARNING, "Invalid expires for '%s' at line %d\n", config, lineno); + return -1; + } + } else { + expires = sip_config.default_expires; + } + + if ((exten = strchr(host, '/'))) { + *exten++ = '\0'; + } else { + exten = NULL; + } + + if (sip_parse_port(host, &port)) { + ast_log(LOG_WARNING, "Invalid host port for '%s' at line %d\n", config, lineno); + return -1; + } + + if (ast_strlen_zero(user) || ast_strlen_zero(host)) { + ast_log(LOG_WARNING, + "Format for 'register' is [transport://]user[@domain[:port]][:secret[:md5secret[:authuser]]]@host[:port][/exten][~expires] at line %d\n", + lineno); + return -1; + } + + if ((registration = ao2_find(sip_registrations, config, OBJ_SEARCH_KEY))) { + ao2_ref(registration, -1); + return 0; + } + + if (!(registration = ao2_alloc(sizeof(*registration), sip_registration_destroy))) { + return -1; + } + + if (ast_string_field_init(registration, 256)) { + ao2_ref(registration, -1); + return -1; + } + + ast_string_field_set(registration, config, config); + ast_string_field_set(registration, user, user); + ast_string_field_set(registration, domain, domain); + ast_string_field_set(registration, secret, secret); + ast_string_field_set(registration, md5_secret, md5_secret); + ast_string_field_set(registration, authorization_user, authorization_user); + ast_string_field_set(registration, host, host); + ast_string_field_set(registration, exten, exten); + + registration->transport = transport; + registration->port = port; + registration->domain_port = domain_port; + registration->expires = expires; + + registration->expires_sched_id = -1; + registration->timeout_sched_id = -1; + + ao2_link(sip_registrations, registration); + ao2_ref(registration, -1); + return 0; +} + +/* Destroy registration */ +static void sip_registration_destroy(void *data) +{ + struct sip_registration *registration = (struct sip_registration *) data; + + /* Really delete */ + ast_debug(3, "Destroying registration for %s@%s\n", registration->user, registration->host); + + if (registration->dialog) { + /* Clear registration before destroying to ensure we don't get reentered trying to grab the + * registration lock */ + ao2_replace(registration->dialog->registration, NULL); + ast_debug(3, "Destroying active SIP dialog for registration '%s@%s'\n", + registration->user, registration->host); + + sip_dialog_unlink(registration->dialog); + ao2_ref(registration->dialog, -1); + } + + ast_string_field_free_memory(registration); +} + +/* Run by the sched thread */ +static int __sip_registration_unlink(const void *data) +{ + struct sip_registration *registration = (struct sip_registration *) data; + + ao2_lock(registration); + + if (registration->dialog) { + ast_debug(3, "Destroying active SIP dialog for registration %s@%s\n", + registration->user, registration->host); + /* This will also remove references to the registration */ + sip_dialog_unlink(registration->dialog); + + ao2_ref(registration->dialog, -1); + registration->dialog = NULL; + } + + AST_SCHED_DEL_UNREF(sip_sched_context, registration->expires_sched_id, ao2_cleanup(registration)); + AST_SCHED_DEL_UNREF(sip_sched_context, registration->timeout_sched_id, ao2_cleanup(registration)); + + if (registration->dnsmgr) { + ast_dnsmgr_release(registration->dnsmgr); + registration->dnsmgr = NULL; + ao2_ref(registration, -1); + } + + ao2_unlock(registration); + ao2_ref(registration, -1); + return 0; +} + +int sip_registration_unlink(void *data, void *arg, int flags) +{ + struct sip_registration *registration = (struct sip_registration *) data; + + if (ast_sched_add(sip_sched_context, 0, __sip_registration_unlink, ao2_bump(registration)) == -1) { + ao2_ref(registration, -1); + } + + return CMP_MATCH; +} + +int sip_registration_send(struct sip_registration *registration) +{ + ast_debug(2, "Sending registration for '%s@%s'\n", registration->user, registration->host); + + switch (registration->state) { + case SIP_REGISTRATION_UNREGISTERED: + case SIP_REGISTRATION_REQUEST_SENT: + case SIP_REGISTRATION_AUTHORIZATION_SENT: + break; + case SIP_REGISTRATION_REJECTED: + case SIP_REGISTRATION_AUTHORIZATION_FAILED: + case SIP_REGISTRATION_FAILED: + /* Restarting registration as unregistered */ + registration->state = SIP_REGISTRATION_UNREGISTERED; + break; + case SIP_REGISTRATION_TIMEOUT: + case SIP_REGISTRATION_REGISTERED: + /* Registration needs to be renewed */ + registration->state = SIP_REGISTRATION_TIMEOUT; + break; + } + + if (!registration->dnsmgr) { + /* No point in doing a DNS lookup of the register hostname if we're just going to end up using an + * outbound proxy. sip_proxy_get is safe to call with either of registration->dialog of NULL. Since + * we're only concerned with its existence, we're not going to bother getting a ref to the proxy */ + if (!sip_proxy_get(registration->dialog, NULL)) { + char service[MAXHOSTNAMELEN]; + + registration->address.ss.ss_family = AF_INET; /* Filter address family */ + snprintf(service, sizeof(service), "_%s._%s", + sip_srv_service(registration->transport), sip_srv_protocol(registration->transport)); + + ast_dnsmgr_lookup_cb(registration->host, ®istration->address, ®istration->dnsmgr, + sip_config.srv_lookup && !registration->port ? service : NULL, + sip_registration_dnsmgr_lookup,ao2_bump(registration)); + + if (!registration->dnsmgr) { + ao2_ref(registration, -1); /* 'dnsmgr' refresh disabled, no reference added */ + } + } + } + + if (registration->dialog) { + registration->dialog->authorization_code = 0; + ast_string_field_set(registration->dialog, authorization, NULL); + + sip_request_send_register(registration->dialog, FALSE); + return 0; + } + + /* Allocate SIP dialog for registration */ + if (!(registration->dialog = sip_dialog_alloc(NULL, NULL, SIP_METHOD_REGISTER, NULL, 0))) { + return -1; + } + + registration->dialog->socket.transport = registration->transport; + + /* Use port number specified if no SRV record was found */ + if (!ast_sockaddr_isnull(®istration->address) && registration->port) { + ast_sockaddr_set_port(®istration->address, registration->port); + } + + /* Find address to hostname */ + if (sip_dialog_build(registration->dialog, registration->host, ®istration->address, FALSE)) { + /* We have what we hope is a temporary network error, probably DNS. We need to reschedule a + * registration try */ + sip_dialog_unlink(registration->dialog); + + ao2_ref(registration->dialog, -1); + registration->dialog = NULL; + + ast_log(LOG_WARNING, "DNS lookup error for registration to '%s@%s' trying again (after %ds)\n", + registration->user, registration->host, sip_config.register_timeout); + + sip_registration_start_timeout(registration); + registration->attempts++; + return 0; + } + + if (!registration->dnsmgr && registration->port) { + ast_sockaddr_set_port(®istration->dialog->address, registration->port); + } + + /* Set transport so the correct contact is built */ + sip_socket_set_transport(®istration->dialog->socket, registration->transport); + /* Check which address we should use in our contact header based on whether the remote host is on the external + * or internal network so we can register through NAT */ + sip_dialog_set_our_address(registration->dialog); + + if (!ast_strlen_zero(registration->authorization_user)) { + ast_string_field_set(registration->dialog->peer, authorization_user, registration->authorization_user); + } else if (!ast_strlen_zero(registration->user)) { + ast_string_field_set(registration->dialog->peer, authorization_user, registration->user); + } + + ast_string_field_set(registration->dialog->peer, secret, registration->secret); + ast_string_field_set(registration->dialog->peer, md5_secret, registration->md5_secret); + + ast_string_field_set(registration->dialog, from_user, S_OR(registration->exten, registration->user)); + ast_string_field_set(registration->dialog, from_domain, S_OR(registration->domain, registration->host)); + + ast_string_field_set(registration->dialog, to_user, registration->user); + ast_string_field_set(registration->dialog, to_host, S_OR(registration->domain, registration->host)); + + registration->dialog->outgoing = TRUE; + registration->dialog->expires = registration->expires; + + registration->dialog->registration = ao2_bump(registration); + sip_request_send_register(registration->dialog, FALSE); + return 0; +} + +void sip_registration_dnsmgr_lookup(struct ast_sockaddr *old_address, struct ast_sockaddr *new_address, void *data) +{ + struct sip_registration *registration; + const char *old_host, *new_host; + + registration = (struct sip_registration *) data; + + /* This shouldn't happen, but just in case */ + if (ast_sockaddr_isnull(new_address)) { + return; + } + + if (!ast_sockaddr_port(new_address)) { + ast_sockaddr_set_port(new_address, registration->port); + } + + old_host = ast_strdupa(ast_sockaddr_stringify(old_address)); + new_host = ast_strdupa(ast_sockaddr_stringify(new_address)); + + ast_debug(1, "Changing registration %s from %s to %s\n", registration->host, old_host, new_host); + ast_sockaddr_copy(®istration->address, new_address); +} + +/* Send all known registrations */ +void sip_registration_send_all(void) +{ + int when, gap; + struct ao2_iterator iter; + struct sip_registration *registration; + + if (!ao2_container_count(sip_registrations)) { + return; + } + + gap = MIN((sip_config.default_expires * 1000) / ao2_container_count(sip_registrations), 100); + when = gap; + + iter = ao2_iterator_init(sip_registrations, 0); + + while ((registration = ao2_iterator_next(&iter))) { + when += gap; + + ao2_lock(registration); + sip_registration_sched_resend(registration, when); + ao2_unlock(registration); + + ao2_ref(registration, -1); + } + + ao2_iterator_destroy(&iter); +} + +/* Run by the sched thread */ +static int __sip_registration_sched_resend(const void *data) +{ + struct sip_sched_data *sched_data; + struct sip_registration *registration; + int when; + + sched_data = (struct sip_sched_data *) data; + + registration = sched_data->registration; + when = sched_data->when; + ast_free(sched_data); + + AST_SCHED_DEL_UNREF(sip_sched_context, registration->expires_sched_id, ao2_cleanup(registration)); + + if ((registration->expires_sched_id = ast_sched_add(sip_sched_context, when, sip_registration_resend, + ao2_bump(registration))) == -1) { + ao2_ref(registration, -1); + } + + ao2_ref(registration, -1); + return 0; +} + +void sip_registration_sched_resend(struct sip_registration *registration, int when) +{ + struct sip_sched_data *sched_data; + + if (!(sched_data = ast_malloc(sizeof(*sched_data)))) { + return; + } + + registration->expires_sched_id = -1; + + sched_data->registration = ao2_bump(registration); + sched_data->when = when; + + if (ast_sched_add(sip_sched_context, 0, __sip_registration_sched_resend, sched_data) == -1) { + ao2_ref(sched_data->registration, -1); + ast_free(sched_data); + } +} + +int sip_registration_resend(const void *data) +{ + struct sip_registration *registration = (struct sip_registration *) data; + + sip_registration_send(registration); + ao2_ref(registration, -1); + return 0; +} + +/* Registration request timeout, register again. Registered as a timeout handler during sip_request_send_register(), + * to retransmit the packet if a reply does not come back. This is called by the scheduler so the event is not pending + * anymore when we are called */ +int sip_registration_timeout(const void *data) +{ + struct sip_registration *registration = (struct sip_registration *) data; + + if (registration->state == SIP_REGISTRATION_REGISTERED || registration->state == SIP_REGISTRATION_REJECTED || + registration->state == SIP_REGISTRATION_AUTHORIZATION_FAILED || registration->state == SIP_REGISTRATION_FAILED) { + /* Registration completed because we got a request response and we couldn't stop the scheduled entry in time */ + registration->timeout_sched_id = -1; + registration->last_registered = 0; + + ao2_ref(registration, -1); + return 0; + } + + if (registration->dnsmgr) { + /* If the registration has timed out, maybe the IP changed. Force a refresh */ + ast_dnsmgr_refresh(registration->dnsmgr); + } + + /* If the initial tranmission failed, we may not have an existing dialog, so it is possible that + * registration->dialog is NULL. Otherwise destroy it, as we have a timeout so we don't want it */ + if (registration->dialog) { + /* Unlink and destroy old dialog. Locking is not relevant here because all this happens in the single + * monitor thread */ + ao2_lock(registration->dialog); + sip_dialog_set_need_destroy(registration->dialog, "registration timeout"); + + /* Pretend to ACK anything just in case */ + sip_packet_pretend_ack(registration->dialog); + ao2_unlock(registration->dialog); + + /* Decouple the two objects. dialog->registration == registration, so registration has 2 refs, and the + * unref won't take the object away */ + ao2_replace(registration->dialog->registration, NULL); + ao2_ref(registration->dialog, -1); + + registration->dialog = NULL; + } + + /* If we have a limit, stop registration and give up */ + registration->timeout_sched_id = -1; + registration->last_registered = 0; + + if (sip_config.register_max_attempts && registration->attempts >= sip_config.register_max_attempts) { + /* Ok, enough is enough. Don't try any more. We could add an external notification here */ + ast_verb(3, "SIP registration '%s@%s' failed, giving up (attempt %d)\n", + registration->user, registration->host, registration->attempts); + + registration->state = SIP_REGISTRATION_FAILED; + registration->attempts = 0; + } else { + registration->state = SIP_REGISTRATION_UNREGISTERED; + sip_registration_send(registration); + + ast_log(LOG_NOTICE, "SIP registration '%s@%s' timed out, trying again (attempt %d)\n", + registration->user, registration->host, registration->attempts); + } + + ast_system_publish_registry("SIP", registration->user, registration->host, + registration->state == SIP_REGISTRATION_FAILED ? "Failed" : "Unregistered", NULL); + ao2_ref(registration, -1); + return 0; +} + +/* Run by the sched thread */ +static int __sip_registration_start_timeout(const void *data) +{ + struct sip_registration *registration = (struct sip_registration *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, registration->timeout_sched_id, ao2_cleanup(registration)); + + if ((registration->timeout_sched_id = ast_sched_add(sip_sched_context, sip_config.register_timeout * 1000, + sip_registration_timeout, ao2_bump(registration))) == -1) { + ao2_ref(registration, -1); + } + + ast_debug(1, "Scheduled a registration timeout for %s\n", registration->host); + ao2_ref(registration, -1); + return 0; +} + +void sip_registration_start_timeout(struct sip_registration *registration) +{ + if (ast_sched_add(sip_sched_context, 0, __sip_registration_start_timeout, ao2_bump(registration)) == -1) { + ao2_ref(registration, -1); + } +} + +/* Run by the sched thread */ +static int __sip_registration_stop_timeout(const void *data) +{ + struct sip_registration *registration = (struct sip_registration *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, registration->timeout_sched_id, ao2_cleanup(registration)); + ao2_ref(registration, -1); + return 0; +} + +void sip_registration_stop_timeout(struct sip_registration *registration) +{ + if (ast_sched_add(sip_sched_context, 0, __sip_registration_stop_timeout, ao2_bump(registration)) == -1) { + ao2_ref(registration, -1); + } +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/remotecc.c asterisk-22.6.0/channels/sip/remotecc.c --- asterisk-22.6.0.orig/channels/sip/remotecc.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/remotecc.c 2025-10-21 18:38:17.465217746 +1300 @@ -0,0 +1,364 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/module.h" +#include "asterisk/logger.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/utils.h" +#include "asterisk/xml.h" +#include "asterisk/bridge.h" +#include "asterisk/callerid.h" +#include "asterisk/causes.h" +#include "asterisk/indications.h" +#include "asterisk/cdr.h" +#include "asterisk/pbx.h" +#include "asterisk/devicestate.h" +#include "asterisk/astdb.h" +#include "asterisk/message.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/domains.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/remotecc.h" +#include "include/pickup.h" +#include "include/parking.h" +#include "include/conference.h" +#include "include/recording.h" +#include "include/callback.h" + +int sip_remotecc_park(struct sip_dialog *dialog, struct sip_message *request, struct sip_remotecc_data *remotecc_data) +{ + return sip_park_call(dialog, request, remotecc_data->dialog.call_id, + remotecc_data->dialog.local_tag, remotecc_data->dialog.remote_tag, FALSE); +} + +int sip_remotecc_parkmonitor(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data) +{ + return sip_park_call(dialog, request, remotecc_data->dialog.call_id, + remotecc_data->dialog.local_tag, remotecc_data->dialog.remote_tag, TRUE); +} + +int sip_remotecc_conference(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data) +{ + return sip_conference_build(dialog, request, remotecc_data->dialog.call_id, remotecc_data->dialog.local_tag, + remotecc_data->dialog.remote_tag, remotecc_data->consult_dialog.call_id, + remotecc_data->consult_dialog.local_tag, remotecc_data->consult_dialog.remote_tag, FALSE); +} + +int sip_remotecc_join(struct sip_dialog *dialog, struct sip_message *request, struct sip_remotecc_data *remotecc_data) +{ + return sip_conference_build(dialog, request, remotecc_data->dialog.call_id, remotecc_data->dialog.local_tag, + remotecc_data->dialog.remote_tag, NULL, NULL, NULL, TRUE); +} + +int sip_remotecc_rmlastconf(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data) +{ + return sip_conference_remove_last(dialog, request, remotecc_data->dialog.call_id, + remotecc_data->dialog.local_tag, remotecc_data->dialog.remote_tag); +} + +int sip_remotecc_conflist(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data) +{ + return sip_conference_participants(dialog, request, remotecc_data->dialog.call_id, + remotecc_data->dialog.local_tag, remotecc_data->dialog.remote_tag, remotecc_data->conference_id, + remotecc_data->user_call_data); +} + +int sip_remotecc_select(struct sip_dialog *dialog, struct sip_message *request, struct sip_remotecc_data *remotecc_data) +{ + sip_response_send(dialog, "202 Accepted", request); + return sip_selected_add(dialog, remotecc_data->dialog.call_id, remotecc_data->dialog.local_tag, + remotecc_data->dialog.remote_tag); +} + +int sip_remotecc_unselect(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data) +{ + sip_response_send(dialog, "202 Accepted", request); + return sip_selected_remove(dialog, remotecc_data->dialog.call_id, remotecc_data->dialog.local_tag, + remotecc_data->dialog.remote_tag); +} + +int sip_remotecc_idivert(struct sip_dialog *dialog, struct sip_message *request, struct sip_remotecc_data *remotecc_data) +{ + struct sip_dialog *target_dialog; + struct ast_channel *channel, *bridge_channel; + + /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ + if (!(target_dialog = sip_dialog_find(remotecc_data->dialog.call_id, + remotecc_data->dialog.remote_tag, remotecc_data->dialog.local_tag))) { + ast_debug(1, "No dialog with Call-ID: '%s' From: tag='%s' To: tag='%s'\n", + remotecc_data->dialog.call_id, remotecc_data->dialog.remote_tag, remotecc_data->dialog.local_tag); + return -1; + } + + ao2_lock(target_dialog); + + if (!(channel = target_dialog->channel)) { + ast_debug(1, "No owner channel\n"); + + ao2_unlock(target_dialog); + ao2_ref(target_dialog, -1); + return -1; + } + + ast_channel_ref(channel); + ao2_unlock(target_dialog); + ao2_ref(target_dialog, -1); + + sip_response_send(dialog, "202 Accepted", request); + + if (ast_channel_state(channel) == AST_STATE_RINGING) { + ast_queue_control(channel, AST_CONTROL_BUSY); + } else if (ast_channel_state(channel) == AST_STATE_UP) { + if ((bridge_channel = ast_channel_bridge_peer(channel))) { + pbx_builtin_setvar_helper(bridge_channel, "IDIVERT_PEERNAME", dialog->peer->name); + ast_async_goto(bridge_channel, dialog->peer->context, "idivert", 1); + ast_channel_unref(bridge_channel); + } + } + + ast_channel_unref(channel); + return 0; +} + +int sip_remotecc_hlog(struct sip_dialog *dialog, struct sip_message *request, struct sip_remotecc_data *remotecc_data) +{ + struct sip_alias *alias; + + sip_response_send(dialog, "202 Accepted", request); + + dialog->peer->hunt_group = !dialog->peer->hunt_group; + + AST_LIST_TRAVERSE(&dialog->peer->aliases, alias, next) { + if (alias->peer) { + alias->peer->hunt_group = dialog->peer->hunt_group; + } + } + + if (!dialog->peer->realtime) { + ast_db_put("SIP/HuntGroup", dialog->peer->name, dialog->peer->hunt_group ? "yes" : "no"); + } else if (sip_config.realtime_update_peer && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", dialog->peer->name, + "huntgroup", dialog->peer->hunt_group ? "yes" : "no", SENTINEL); + } + + sip_peer_send_hunt_group(dialog->peer); + return 0; +} + +int sip_remotecc_qrt(struct sip_dialog *dialog, struct sip_message *request, struct sip_remotecc_data *remotecc_data) +{ + struct sip_dialog *target_dialog, *qrt_dialog; + + if (!ast_strlen_zero(remotecc_data->dialog.call_id)) { + struct ast_str *content; + + /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ + if (!(target_dialog = sip_dialog_find(remotecc_data->dialog.call_id, + remotecc_data->dialog.remote_tag, remotecc_data->dialog.local_tag))) { + ast_debug(1, "No dialog with Call-ID: '%s' From: tag='%s' To tag='%s'\n", + remotecc_data->dialog.call_id, remotecc_data->dialog.remote_tag, remotecc_data->dialog.local_tag); + return -1; + } + + target_dialog->send_qrt_url = TRUE; + + ao2_ref(target_dialog, -1); + sip_response_send(dialog, "202 Accepted", request); + + if (!(qrt_dialog = sip_dialog_alloc(NULL, &dialog->socket, SIP_METHOD_REFER, NULL, 0))) { + return 0; + } + + sip_dialog_copy(qrt_dialog, dialog); + + content = ast_str_alloca(4096); + + ast_str_append(&content, 0, "\n" + "\n" + " \n" + " notify_display\n" + " \n"); + ast_str_append(&content, 0, " %s\n", remotecc_data->dialog.call_id); + ast_str_append(&content, 0, " %s\n", remotecc_data->dialog.local_tag); + ast_str_append(&content, 0, " %s\n", remotecc_data->dialog.remote_tag); + ast_str_append(&content, 0, " \n" + " Quality Reporting Tool is active\n" + " 7\n" + " 0\n" + " 1\n" + " \n" + "\n"); + + sip_request_send_refer_with_content(qrt_dialog, "application/x-cisco-remotecc-request+xml", + ast_str_buffer(content)); + ao2_ref(qrt_dialog, -1); + } else { + sip_response_send(dialog, "202 Accepted", request); + sip_peer_send_qrt_url(dialog->peer); + } + + return 0; +} + +int sip_remotecc_mcid(struct sip_dialog *dialog, struct sip_message *request, struct sip_remotecc_data *remotecc_data) +{ + struct sip_dialog *target_dialog, *mcid_dialog; + struct ast_channel *channel, *bridge_channel; + struct ast_str *content; + + /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ + if (!(target_dialog = sip_dialog_find(remotecc_data->dialog.call_id, + remotecc_data->dialog.remote_tag, remotecc_data->dialog.local_tag))) { + ast_debug(1, "No dialog with Call-ID: '%s' From: tag='%s' To: tag='%s'\n", + remotecc_data->dialog.call_id, remotecc_data->dialog.remote_tag, remotecc_data->dialog.local_tag); + return -1; + } + + ao2_lock(target_dialog); + + if (!(channel = target_dialog->channel)) { + ast_debug(1, "No owner channel\n"); + + ao2_unlock(target_dialog); + ao2_ref(target_dialog, -1); + return -1; + } + + ast_channel_ref(channel); + + ao2_unlock(target_dialog); + ao2_ref(target_dialog, -1); + + sip_response_send(dialog, "202 Accepted", request); + + if ((bridge_channel = ast_channel_bridge_peer(channel))) { + ast_verb(3, "%s has a malicious call from '%s'\n", + target_dialog->peer->name, ast_channel_name(bridge_channel)); + + ast_queue_control(channel, AST_CONTROL_MCID); + ast_channel_unref(bridge_channel); + } + + ast_channel_unref(channel); + + if (!(mcid_dialog = sip_dialog_alloc(NULL, &dialog->socket, SIP_METHOD_REFER, NULL, 0))) { + return 0; + } + + sip_dialog_copy(mcid_dialog, dialog); + + content = ast_str_alloca(4096); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n" + " notify_display\n" + " \n"); + ast_str_append(&content, 0, " %s\n", remotecc_data->dialog.call_id); + ast_str_append(&content, 0, " %s\n", remotecc_data->dialog.local_tag); + ast_str_append(&content, 0, " %s\n", remotecc_data->dialog.remote_tag); + ast_str_append(&content, 0, " \n" + " \200T\n" + " 7\n" + " 0\n" + " 1\n" + " \n" + "\n" + "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n" + "Content-Type: application/x-cisco-remotecc-request+xml\r\n" + "\r\n" + "\n" + "\n" + " \n" + " \n"); + ast_str_append(&content, 0, " %s\n", remotecc_data->dialog.call_id); + ast_str_append(&content, 0, " %s\n", remotecc_data->dialog.local_tag); + ast_str_append(&content, 0, " %s\n", remotecc_data->dialog.remote_tag); + ast_str_append(&content, 0, " \n" + " DtZipZip\n" + " all\n" + " \n" + "\n" + "\r\n" + "--uniqueBoundary--\r\n"); + + sip_request_send_refer_with_content(mcid_dialog, "multipart/mixed; boundary=uniqueBoundary", + ast_str_buffer(content)); + ao2_ref(mcid_dialog, -1); + return 0; +} + +int sip_remotecc_startrecording(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data) +{ + sip_response_send(dialog, "202 Accepted", request); + sip_recording_start(remotecc_data->dialog.call_id, remotecc_data->dialog.remote_tag, + remotecc_data->dialog.local_tag, TRUE); + return 0; +} + +int sip_remotecc_stoprecording(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data) +{ + sip_response_send(dialog, "202 Accepted", request); + sip_recording_stop(remotecc_data->dialog.call_id, remotecc_data->dialog.remote_tag, + remotecc_data->dialog.local_tag); + return 0; +} + +int sip_remotecc_callback(struct sip_dialog *dialog, struct sip_message *request, + struct sip_remotecc_data *remotecc_data) +{ + return sip_callback_build(dialog, request, remotecc_data->dialog.call_id, + remotecc_data->dialog.local_tag, remotecc_data->dialog.remote_tag, remotecc_data->user_call_data); +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/request.c asterisk-22.6.0/channels/sip/request.c --- asterisk-22.6.0.orig/channels/sip/request.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/request.c 2025-10-21 18:38:17.465217746 +1300 @@ -0,0 +1,797 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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 + extended + ***/ + +#include "asterisk.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/threadstorage.h" +#include "asterisk/utils.h" +#include "asterisk/channel.h" +#include "asterisk/callerid.h" +#include "asterisk/indications.h" +#include "asterisk/rtp_engine.h" +#include "asterisk/pbx.h" +#include "asterisk/message.h" +#include "asterisk/dnsmgr.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/registrations.h" +#include "include/dialog.h" +#include "include/sdp.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/fax.h" + +/* Send an INVITE request, used to initiate a call to a peer */ +int sip_request_send_invite(struct sip_dialog *dialog, int add_sdp, int init, const char *explicit_uri) +{ + struct sip_message request; + + if (init == SIP_INIT_REQUEST) { + sip_message_build_initial_request(&request, dialog, SIP_METHOD_INVITE, explicit_uri); + } else { + sip_message_build_request(&request, dialog, SIP_METHOD_INVITE, 0, init == SIP_INIT_BRANCH); + } + + sip_message_add_authorization(&request, dialog); + sip_message_add_date(&request); + + /* This new INVITE is part of an attended transfer. Make sure that the other end knows and replace the + * current call with this new call */ + if (!ast_strlen_zero(dialog->replaces)) { + sip_message_add_header(&request, "Replaces", dialog->replaces); + sip_message_add_header(&request, "Require", "replaces"); + } + + sip_message_add_header(&request, "Allow", SIP_ALLOW_METHODS); + sip_message_add_supported(&request, dialog); + sip_message_add_identity(&request, dialog); + sip_message_add_diversion(&request, dialog); + sip_message_add_join(&request, dialog); + sip_message_add_call_info(&request, dialog); + + if (dialog->peer->session_timer_mode == SIP_SESSION_TIMER_MODE_ORIGINATE || + (dialog->peer->session_timer_mode == SIP_SESSION_TIMER_MODE_ACCEPT && + dialog->session_timer_expires >= dialog->peer->session_timer_min_expires)) { + dialog->session_timer_active = TRUE; + + if (dialog->peer->session_timer_mode == SIP_SESSION_TIMER_MODE_ORIGINATE) { + sip_message_build_header(&request, "Session-Expires", "%d", dialog->session_timer_expires); + } + + sip_message_build_header(&request, "Min-SE", "%d", dialog->peer->session_timer_min_expires); + } + + if (dialog->channel && dialog->add_headers) { + const struct ast_var_t *var; + + ast_channel_lock(dialog->channel); + + AST_LIST_TRAVERSE(ast_channel_varshead(dialog->channel), var, entries) { + char *name, *value; + + /* SIPADDHEADER: Add SIP header to outgoing call */ + if (strncmp(ast_var_name(var), "SIPADDHEADER", 12)) { + continue; + } + + name = ast_strdupa(ast_var_value(var)); + name = ast_strip_quoted(name, "\"", "\""); /* Strip quotes if present */ + + if ((value = strchr(name, ':'))) { + *value++ = '\0'; + value = ast_skip_blanks(value); /* Skip white space */ + + sip_message_add_header(&request, name, value); + ast_debug(3, "Adding SIP Header \"%s\" with content \"%s\"\n", name, value); + } + } + + ast_channel_unlock(dialog->channel); + } + + if (add_sdp) { + sip_sdp_media_destroy(dialog); + + if (dialog->udptl && dialog->fax_state == SIP_FAX_LOCAL_REINVITE) { + ast_debug(1, "T38 is in state %u on channel %s\n", + dialog->fax_state, dialog->channel ? ast_channel_name(dialog->channel) : ""); + sip_sdp_build(dialog, &request, FALSE, FALSE, TRUE); + } else if (dialog->audio_rtp) { + sip_try_suggested_codec(dialog); + sip_sdp_build(dialog, &request, FALSE, TRUE, FALSE); + } + } + + if (ast_strlen_zero(dialog->initial_request.uri)) { + ast_debug(1, "Set initial %s request for '%s'\n", sip_method2str(request.method), dialog->call_id); + sip_message_copy(&dialog->initial_request, &request); + } + + dialog->last_invite_cseq = dialog->outgoing_cseq; + return sip_message_send(dialog, &request, init != SIP_INIT_NONE ? SIP_SEND_CRITICAL : SIP_SEND_RELIABLE, + dialog->outgoing_cseq); +} + +/* Send an INVITE request with an SDP. A re-invite is basically a new INVITE with the same CALL-ID and TAG as the INVITE + * that opened the SIP dialog. We reinvite so that the audio stream (RTP) go directly between the SIP UAs. SIP + * signalling stays with Asterisk in the path. If add_image is TRUE, we send T38 SDP for re-invite from audio/video to + * T38 UDPTL transmission on the channel. If old_sdp is TRUE then the SDP version number is not incremented. This is + * needed for Session-Timers so we can send a re-invite to refresh the SIP session without modifying the media session */ +int sip_request_send_reinvite_with_sdp(struct sip_dialog *dialog, int old_sdp_version, int add_image) +{ + struct sip_message request; + + if (add_image) { + /* Force media to go through us for T.38 */ + ast_sockaddr_setnull(&dialog->audio_redirect_address); + } + + if (dialog->audio_rtp) { + if (add_image) { + /* Silence RTCP while audio RTP is inactive */ + ast_rtp_instance_set_prop(dialog->audio_rtp, + AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_DISABLED); + + if (dialog->channel) { + /* Prevent audio RTCP reads */ + ast_channel_set_fd(dialog->channel, SIP_AUDIO_RTCP_FD, -1); + } + } else if (ast_sockaddr_isnull(&dialog->audio_redirect_address)) { + /* Enable RTCP since it will be inactive if we're coming back with this reinvite */ + ast_rtp_instance_set_prop(dialog->audio_rtp, + AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_STANDARD); + + if (dialog->channel) { + /* Enable audio RTCP reads */ + ast_channel_set_fd(dialog->channel, SIP_AUDIO_RTCP_FD, + ast_rtp_instance_fd(dialog->audio_rtp, 1)); + } + } + } + + sip_message_build_request(&request, dialog, + (dialog->direct_media & SIP_DIRECT_MEDIA_UPDATE) ? SIP_METHOD_UPDATE : SIP_METHOD_INVITE, 0, TRUE); + sip_message_add_header(&request, "Allow", SIP_ALLOW_METHODS); + sip_message_add_supported(&request, dialog); + sip_message_add_identity(&request, dialog); + + /* Add Session-Timers related headers if the feature is active for this session. An exception to this behavior + * is the ACK request. Since Asterisk never requires session-timers support from a remote end-point (UAS) in an + * INVITE, it must not send 'Require: timer' header in the ACK request */ + if (dialog->session_timer_active && dialog->session_timer_remote_active) { + sip_message_build_header(&request, "Session-Expires", "%d;refresher=%s", dialog->session_timer_expires, + dialog->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_UAC ? "uac" : "uas"); + sip_message_build_header(&request, "Min-SE", "%d", dialog->peer->session_timer_min_expires); + } + + sip_sdp_media_destroy(dialog); + sip_try_suggested_codec(dialog); + + /* Either only add audio,video,text or image */ + sip_sdp_build(dialog, &request, old_sdp_version, !add_image, add_image); + + /* Use this as the basis */ + sip_message_copy(&dialog->initial_request, &request); + ast_debug(1, "Set initial %s request '%s'\n", sip_method2str(request.method), dialog->call_id); + + dialog->last_invite_cseq = dialog->outgoing_cseq; + dialog->ongoing_reinvite = TRUE; + dialog->outgoing = TRUE; /* Change direction of this dialog */ + + return sip_message_send(dialog, &request, SIP_SEND_CRITICAL, dialog->outgoing_cseq); +} + +/* Send an UPDATE request, used to update the connected line information for semi-attended transfer */ +int sip_request_send_update(struct sip_dialog *dialog) +{ + struct sip_message request; + + sip_message_build_request(&request, dialog, SIP_METHOD_UPDATE, 0, TRUE); + sip_message_add_authorization(&request, dialog); + sip_message_add_identity(&request, dialog); + + return sip_message_send(dialog, &request, SIP_SEND_CRITICAL, dialog->outgoing_cseq); +} + +/* Send an OPTIONS request, used to qualify a peer */ +int sip_request_send_options(struct sip_dialog *dialog) +{ + struct sip_message request; + + sip_message_build_initial_request(&request, dialog, SIP_METHOD_OPTIONS, NULL); + sip_message_add_authorization(&request, dialog); + sip_message_add_date(&request); + sip_message_add_header(&request, "Allow", SIP_ALLOW_METHODS); + sip_message_add_supported(&request, dialog); + + if (ast_strlen_zero(dialog->initial_request.uri)) { + ast_debug(1, "Set initial %s request for '%s'\n", sip_method2str(request.method), dialog->call_id); + sip_message_copy(&dialog->initial_request, &request); + } + + return sip_message_send(dialog, &request, SIP_SEND_RELIABLE, dialog->outgoing_cseq); +} + +/* Send an ACK request to responsed to an INVITE */ +int sip_request_send_ack(struct sip_dialog *dialog, uint32_t cseq, int new_branch) +{ + struct sip_message request; + + sip_message_build_request(&request, dialog, SIP_METHOD_ACK, cseq, new_branch); + + if (dialog->ack_add_sdp) { + sip_sdp_build(dialog, &request, FALSE, TRUE, FALSE); + } + + dialog->invite_state = SIP_INVITE_CONFIRMED; + return sip_message_send(dialog, &request, SIP_SEND_UNRELIABLE, cseq); +} + +/* Send a CANCEL request to end an outgoing INVITE */ +int sip_request_send_cancel(struct sip_dialog *dialog) +{ + struct sip_message request; + + sip_message_build_request(&request, dialog, SIP_METHOD_CANCEL, dialog->last_invite_cseq, FALSE); + + if (dialog->answered_elsewhere) { + sip_message_add_header(&request, "Reason", "SIP;cause=200;text=\"Call completed elsewhere\""); + } + + dialog->invite_state = SIP_INVITE_CANCELLED; + return sip_message_send(dialog, &request, SIP_SEND_RELIABLE, dialog->last_invite_cseq); +} + +/* Send a BYE request */ +int sip_request_send_bye(struct sip_dialog *dialog) +{ + struct sip_message request; + + sip_message_build_request(&request, dialog, SIP_METHOD_BYE, 0, TRUE); + + if (!ast_strlen_zero(dialog->authorization_realm)) { + if (!sip_dialog_build_authorization(dialog, SIP_METHOD_BYE)) { + if (!dialog->authorization_code) { + dialog->authorization_code = 401; + } + + sip_message_add_authorization(&request, dialog); + } else { + ast_log(LOG_WARNING, "No authentication available for '%s'\n", dialog->call_id); + } + } + + if (dialog->peer->reason_support && dialog->hangupcause) { + sip_message_build_header(&request, "Reason", "Q.850;cause=%d", dialog->hangupcause & 0x7f); + } + + return sip_message_send(dialog, &request, SIP_SEND_RELIABLE, dialog->outgoing_cseq); +} + +/* Send a REGISTER request, used to tell a proxy our address */ +int sip_request_send_register(struct sip_dialog *dialog, int add_authorization) +{ + struct sip_message request; + + /* Abort if we are already in process with this registrar */ + if (!add_authorization && + (dialog->registration->state == SIP_REGISTRATION_REQUEST_SENT || + dialog->registration->state == SIP_REGISTRATION_AUTHORIZATION_SENT)) { + return 0; + } + + if (!add_authorization) { + sip_message_build_initial_request(&request, dialog, SIP_METHOD_REGISTER, NULL); + dialog->registration->state = SIP_REGISTRATION_REQUEST_SENT; + } else { + sip_message_build_request(&request, dialog, SIP_METHOD_REGISTER, 0, TRUE); + dialog->registration->state = SIP_REGISTRATION_AUTHORIZATION_SENT; + } + + dialog->registration->attempts++; /* Another attempt */ + + sip_message_add_header(&request, "Allow", SIP_ALLOW_METHODS); + sip_message_add_supported(&request, dialog); + sip_message_build_header(&request, "Expires", "%d", dialog->registration->expires); + + if (add_authorization) { + sip_message_add_authorization(&request, dialog); + } + + if (ast_strlen_zero(dialog->initial_request.uri)) { + ast_debug(1, "Set initial %s request for '%s'\n", sip_method2str(request.method), dialog->call_id); + sip_message_copy(&dialog->initial_request, &request); + } + + ast_debug(4, "REGISTER to '%s@%s' attempt %d\n", + dialog->registration->user, dialog->registration->host, dialog->registration->attempts); + + /* Set up a timeout */ + if (!add_authorization) { + sip_registration_start_timeout(dialog->registration); + } + + return sip_message_send(dialog, &request, SIP_SEND_CRITICAL, dialog->outgoing_cseq); +} + +/* Send a SUBSCRIBE request, used to monitor remote mailboxes only */ +int sip_request_send_subscribe(struct sip_dialog *dialog, int init) +{ + struct sip_message request; + + if (dialog->subscribe_event != SIP_SUBSCRIBE_MESSAGE_SUMMARY) { + ast_log(LOG_WARNING, "Subscribe-event is not message-summary (%d)\n", dialog->subscribe_event); + return -1; + } + + if (init == SIP_INIT_REQUEST) { + sip_message_build_initial_request(&request, dialog, SIP_METHOD_SUBSCRIBE, NULL); + } else { + sip_message_build_request(&request, dialog, SIP_METHOD_SUBSCRIBE, 0, init == SIP_INIT_BRANCH); + } + + sip_message_add_authorization(&request, dialog); + sip_message_add_header(&request, "Event", "message-summary"); + sip_message_add_header(&request, "Accept", "application/simple-message-summary"); + sip_message_build_header(&request, "Expires", "%d", dialog->expires); + + if (ast_strlen_zero(dialog->initial_request.uri)) { + ast_debug(1, "Set initial %s request for '%s'\n", sip_method2str(request.method), dialog->call_id); + sip_message_copy(&dialog->initial_request, &request); + } + + dialog->last_invite_cseq = dialog->outgoing_cseq; + return sip_message_send(dialog, &request, init != SIP_INIT_NONE ? SIP_SEND_CRITICAL : SIP_SEND_RELIABLE, + dialog->outgoing_cseq); +} + +/* Send a NOTIFY request */ +int sip_request_send_notify(struct sip_dialog *dialog, int init) +{ + struct sip_message request; + struct ast_variable *header; + + if (init == SIP_INIT_REQUEST) { + sip_message_build_initial_request(&request, dialog, SIP_METHOD_NOTIFY, NULL); + } else { + sip_message_build_request(&request, dialog, SIP_METHOD_NOTIFY, 0, init == SIP_INIT_BRANCH); + } + + sip_message_add_authorization(&request, dialog); + + for (header = dialog->notify_headers; header; header = header->next) { + sip_message_add_header(&request, header->name, header->value); + } + + if (!ast_strlen_zero(dialog->notify_content)) { + sip_message_add_content(&request, dialog->notify_content); + } + + if (ast_strlen_zero(dialog->initial_request.uri)) { + ast_debug(1, "Set initial %s request for '%s'\n", sip_method2str(request.method), dialog->call_id); + sip_message_copy(&dialog->initial_request, &request); + } + + return sip_message_send(dialog, &request, init != SIP_INIT_NONE ? SIP_SEND_CRITICAL : SIP_SEND_RELIABLE, + dialog->outgoing_cseq); +} + +/* Send a NOTIFY request with information about the status of a transfer (RFC3515) */ +int sip_request_send_notify_with_sipfrag(struct sip_dialog *dialog, uint32_t cseq, char *status_line) +{ + struct sip_message request; + + sip_message_build_request(&request, dialog, SIP_METHOD_NOTIFY, 0, TRUE); + sip_message_build_header(&request, "Event", "refer;id=%d", cseq); + sip_message_add_header(&request, "Subscription-State", "terminated;reason=noresource"); + sip_message_add_header(&request, "Content-Type", "message/sipfrag;version=2.0"); + sip_message_add_header(&request, "Allow", SIP_ALLOW_METHODS); + sip_message_add_supported(&request, dialog); + + sip_message_build_content(&request, "SIP/2.0 %s\r\n", status_line); + + if (ast_strlen_zero(dialog->initial_request.uri)) { + sip_message_copy(&dialog->initial_request, &request); + ast_debug(1, "Set initial %s request for '%s'\n", sip_method2str(request.method), dialog->call_id); + } + + return sip_message_send(dialog, &request, SIP_SEND_RELIABLE, dialog->outgoing_cseq); +} + +/* Send a NOTIFY request with an event subscription state (RFC3265) */ +int sip_request_send_notify_with_extension_state(struct sip_dialog *dialog, struct ast_state_cb_info *state_info, + int timeout) +{ + struct ast_str *content; + struct sip_message request; + + sip_message_build_request(&request, dialog, SIP_METHOD_NOTIFY, 0, TRUE); + + switch (state_info->exten_state) { + case AST_EXTENSION_DEACTIVATED: + if (timeout) { + sip_message_add_header(&request, "Subscription-State", "terminated;reason=timeout"); + } else { + sip_message_add_header(&request, "Subscription-State", "terminated;reason=probation"); + sip_message_add_header(&request, "Retry-After", "60"); + } + + break; + case AST_EXTENSION_REMOVED: + sip_message_add_header(&request, "Subscription-State", "terminated;reason=noresource"); + break; + default: + sip_message_add_header(&request, "Subscription-State", + dialog->expires ? "active" : "terminated;reason=timeout"); + break; + } + + switch (dialog->subscribe_event) { + case SIP_SUBSCRIBE_DIALOG: + sip_message_add_header(&request, "Event", "dialog"); + sip_message_add_header(&request, "Content-Type", "application/dialog-info+xml"); + break; + case SIP_SUBSCRIBE_PRESENCE: + sip_message_add_header(&request, "Event", "presence"); + sip_message_add_header(&request, "Content-Type", "application/pidf+xml"); + break; + default: + ast_debug(1, "Unknown subscribe event: %d\n", dialog->subscribe_event); + return -1; + } + + content = ast_str_alloca(4096); + + switch (dialog->subscribe_event) { + case SIP_SUBSCRIBE_DIALOG: + ast_str_append(&content, 0, "\n" + "\n", + dialog->outgoing_cseq, dialog->to_user, dialog->from_domain); + + if (sip_config.notify_callerid && (state_info->exten_state & AST_EXTENSION_RINGING)) { + /* There are some limitations to how this works. The primary one is that the callee must be + * dialing the same extension that is being monitored. Simply dialing the hint'd device is not + * sufficient */ + char local_display[AST_MAX_EXTENSION * 2], remote_display[AST_MAX_EXTENSION * 2], *local_uri, *remote_uri; + RAII_VAR(struct ast_channel *, channel, NULL, ao2_cleanup); + + /* It may seem odd to base the remote_uri on the To header here, but testing by reporters found + * that basing on the From header would cause ringing state hints to not work properly on + * certain SNOM devices. If you are using notifycallerid properly (i.e. in the same extension + * and context as the dialed call) then this should not be an issue since the data will be + * overwritten shortly with channel caller ID */ + local_uri = ast_strdupa(dialog->to_user); + ast_xml_escape(dialog->to_user, local_display, sizeof(local_display)); + + remote_uri = ast_strdupa(dialog->to_user); + ast_xml_escape(dialog->to_user, remote_display, sizeof(remote_display)); + + if ((channel = sip_find_ringing_channel(state_info->device_state_info))) { + char uri[AST_MAX_EXTENSION]; + + ast_channel_lock(channel); + + if (!ast_channel_caller(channel)->id.number.valid || + (ast_channel_caller(channel)->id.number.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED) { + local_uri = "anonymous@anonymous.invalid"; + } else { + snprintf(uri, sizeof(uri), "%s@%s", + ast_channel_caller(channel)->id.number.str, dialog->from_domain); + local_uri = ast_strdupa(uri); + } + + if (!ast_channel_caller(channel)->id.name.valid || + (ast_channel_caller(channel)->id.name.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED) { + local_display[0] = '\0'; + } else { + ast_xml_escape(ast_channel_caller(channel)->id.name.str, + local_display, sizeof(local_display)); + } + + if (!ast_channel_connected(channel)->id.number.valid || + (ast_channel_connected(channel)->id.number.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED) { + remote_uri = "anonymous@anonymous.invalid"; + } else { + snprintf(uri, sizeof(uri), "%s@%s", + ast_channel_connected(channel)->id.number.str, dialog->from_domain); + remote_uri = ast_strdupa(uri); + } + + if (!ast_channel_connected(channel)->id.name.valid || + (ast_channel_connected(channel)->id.name.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED) { + remote_display[0] = '\0'; + } else { + ast_xml_escape(ast_channel_caller(channel)->id.name.str, + remote_display, sizeof(remote_display)); + } + + ast_channel_unlock(channel); + } + + /* We create a fake call-id which the phone will send back in an INVITE Replaces header which + * we can grab and do some magic with */ + ast_str_append(&content, 0, + " \n", + dialog->to_user, dialog->call_id, dialog->remote_tag, dialog->local_tag); + /* See the limitations of this above. Luckily the phone seems to still be happy when these + * values are not correct */ + ast_str_append(&content, 0, + " %s\n\n", + remote_display, remote_uri, remote_uri); + ast_str_append(&content, 0, + " %s\n\n", + local_display, local_uri, local_uri); + } else { + ast_str_append(&content, 0, " \n", dialog->to_user); + } + + if (state_info->exten_state & AST_EXTENSION_RINGING) { + ast_str_append(&content, 0, " early\n"); + } else if (state_info->exten_state & (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD) || + state_info->presence_state == AST_PRESENCE_DND) { + ast_str_append(&content, 0, " confirmed\n"); + } else { + ast_str_append(&content, 0, " terminated\n"); + } + + if (state_info->exten_state == AST_EXTENSION_ONHOLD) { + ast_str_append(&content, 0, + " \n", + dialog->to_user, dialog->from_domain); + } + + ast_str_append(&content, 0, " \n" + "\n"); + break; + case SIP_SUBSCRIBE_PRESENCE: + ast_str_append(&content, 0, "\n" + "\n", + dialog->from_user, dialog->from_domain); + ast_str_append(&content, 0, " \n" + " \n"); + + if (state_info->exten_state & AST_EXTENSION_RINGING) { + ast_str_append(&content, 0, " \n"); + } else if (state_info->exten_state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD)) { + ast_str_append(&content, 0, " \n"); + } else if (state_info->presence_state == AST_PRESENCE_DND) { + ast_str_append(&content, 0, " \n"); + } + + ast_str_append(&content, 0, " \n" + " \n" + " \n", dialog->to_user); + ast_str_append(&content, 0, " \n"); + + if (state_info->exten_state == AST_EXTENSION_UNAVAILABLE) { + ast_str_append(&content, 0, " closed\n"); + } else { + ast_str_append(&content, 0, " open\n"); + } + + ast_str_append(&content, 0, " \n" + " \n" + "\n"); + break; + default: + break; + } + + sip_message_add_content(&request, ast_str_buffer(content)); + + /* Remember that we have a pending NOTIFY in order not to confuse the NOTIFY subsystem */ + dialog->pending_invite_cseq = dialog->outgoing_cseq; + /* Send as SIP_SEND_CRITICAL as we may never receive a 200 OK Response which clears dialog->pending_invite_cseq. + * sip_handle_response_notify uses dialog->pending_invite_cseq for queuing control. Updates stall if + * pending_invite_cseq != 0. The most appropriate solution is to remove the subscription when the NOTIFY + * transaction fails. The client will re-subscribe after restarting or max_expires timeout */ + return sip_message_send(dialog, &request, SIP_SEND_CRITICAL, dialog->outgoing_cseq); +} + +/* Send a NOTIFY request with the state of a mailbox (RFC3842) */ +int sip_request_send_notify_with_mwi(struct sip_dialog *dialog, int new_messages, int old_messages, + const char *mwi_exten) +{ + struct sip_message request; + const char *user, *domain, *transport; + int port; + + sip_message_build_initial_request(&request, dialog, SIP_METHOD_NOTIFY, NULL); + sip_message_add_header(&request, "Event", "message-summary"); + sip_message_add_header(&request, "Content-Type", "application/simple-message-summary"); + + if (dialog->subscribe_event == SIP_SUBSCRIBE_MESSAGE_SUMMARY) { + sip_message_add_header(&request, "Subscription-State", + dialog->expires ? "active" : "terminated;reason=timeout"); + } + + user = S_OR(mwi_exten, "vm"); + domain = S_OR(dialog->from_domain, ast_sockaddr_stringify_host_remote(&dialog->our_address)); + + if (dialog->from_domain_port && dialog->from_domain_port != SIP_STANDARD_PORT) { + port = dialog->from_domain_port; + } else { + port = ast_sockaddr_port(&dialog->our_address); + } + + transport = ast_str_to_lower(ast_strdupa(ast_transport2str(dialog->socket.transport))); + sip_message_build_content(&request, "Messages-Waiting: %s\r\n", new_messages ? "yes" : "no"); + + if (port != (dialog->socket.transport == AST_TRANSPORT_TLS ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT)) { + sip_message_build_content(&request, "Message-Account: sip:%s@%s:%d;transport=%s\r\n", + user, domain, port, transport); + } else { + sip_message_build_content(&request, "Message-Account: sip:%s@%s;transport=%s\r\n", + user, domain, transport); + } + + /* Cisco has a bug in the SIP stack where it can't accept the (0/0) notification */ + sip_message_build_content(&request, "Voice-Message: %d/%d%s\r\n", new_messages, old_messages, + dialog->peer->cisco_mode ? "" : " (0/0)"); + + if (ast_strlen_zero(dialog->initial_request.uri)) { + sip_message_copy(&dialog->initial_request, &request); + ast_debug(1, "Set initial %s request for '%s'\n", sip_method2str(request.method), dialog->call_id); + } + + return sip_message_send(dialog, &request, SIP_SEND_RELIABLE, dialog->outgoing_cseq); +} + +/* Send a REFER request */ +int sip_request_send_refer(struct sip_dialog *dialog, int init) +{ + struct sip_message request; + + if (init != SIP_INIT_NONE) { /* Bump branch even on initial requests */ + ast_string_field_build(dialog, branch, "%s%08x", SIP_MAGIC_COOKIE, (unsigned int) ast_random()); + ast_string_field_set(dialog, invite_branch, dialog->branch); + } + + if (init == SIP_INIT_REQUEST) { + sip_message_build_initial_request(&request, dialog, SIP_METHOD_REFER, NULL); + } else { + sip_message_build_request(&request, dialog, SIP_METHOD_REFER, 0, init == SIP_INIT_BRANCH); + } + + sip_message_add_authorization(&request, dialog); + + if (!ast_strlen_zero(dialog->require)) { + sip_message_add_header(&request, "Require", dialog->require); + } + + if (!ast_strlen_zero(dialog->refer_to)) { + sip_message_add_header(&request, "Refer-To", dialog->refer_to); + } + + if (!ast_strlen_zero(dialog->referred_by)) { + sip_message_add_header(&request, "Referred-By", dialog->referred_by); + } + + if (!ast_strlen_zero(dialog->content_id)) { + sip_message_add_header(&request, "Content-Id", dialog->content_id); + } + + if (!ast_strlen_zero(dialog->content_type)) { + sip_message_add_header(&request, "Content-Type", dialog->content_type); + } + + sip_message_build_header(&request, "Expires", "%d", dialog->expires); + + if (!ast_strlen_zero(dialog->refer_content)) { + sip_message_add_content(&request, dialog->refer_content); + } + + if (ast_strlen_zero(dialog->initial_request.uri)) { + ast_debug(1, "Set initial %s request for '%s'\n", sip_method2str(request.method), dialog->call_id); + sip_message_copy(&dialog->initial_request, &request); + } + + dialog->refer_state = SIP_REFER_SENT; /* Set refer status */ + return sip_message_send(dialog, &request, SIP_SEND_RELIABLE, dialog->outgoing_cseq); +} + +/* Send an out-of-dialog SIP REFER requet with content */ +int sip_request_send_refer_with_content(struct sip_dialog *dialog, const char *content_type, const char *content) +{ + ast_string_field_set(dialog, require, "norefersub"); + ast_string_field_set(dialog, referred_by, dialog->our_contact); + ast_string_field_build(dialog, content_id, "%08x", (unsigned int) ast_random()); + ast_string_field_build(dialog, refer_to, "cid:%s", dialog->content_id); + ast_string_field_set(dialog, content_type, content_type); + ast_string_field_set(dialog, refer_content, content); + + sip_dialog_sched_destroy(dialog, dialog->timer_b); + sip_dialog_set_already_gone(dialog); + + dialog->outgoing = TRUE; /* Refer is outgoing call */ + return sip_request_send_refer(dialog, SIP_INIT_REQUEST); +} + +/* Send an INFO request with a video update */ +int sip_request_send_info_with_media_control(struct sip_dialog *dialog) +{ + struct sip_message request; + + sip_message_build_request(&request, dialog, SIP_METHOD_INFO, 0, TRUE); + sip_message_add_header(&request, "Content-Type", "application/media_control+xml"); + sip_message_add_content(&request, "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); + + return sip_message_send(dialog, &request, SIP_SEND_RELIABLE, dialog->outgoing_cseq); +} + +/* Send a MESSAGE request */ +int sip_request_send_message(struct sip_dialog *dialog, int init) +{ + struct sip_message request; + struct ast_variable *header; + const char *content_type; + + if (init != SIP_INIT_NONE) { /* Bump branch even on initial requests */ + ast_string_field_build(dialog, branch, "%s%08x", SIP_MAGIC_COOKIE, (unsigned int) ast_random()); + ast_string_field_set(dialog, invite_branch, dialog->branch); + } + + if (init == SIP_INIT_REQUEST) { + sip_message_build_initial_request(&request, dialog, SIP_METHOD_MESSAGE, NULL); + } else { + sip_message_build_request(&request, dialog, SIP_METHOD_MESSAGE, 0, init == SIP_INIT_BRANCH); + } + + sip_message_add_authorization(&request, dialog); + content_type = "text/plain"; + + /* Add any additional MESSAGE headers */ + for (header = dialog->message_headers; header; header = header->next) { + if (!strcasecmp(header->name, "Content-Type")) { + content_type = header->value; /* Save content-type */ + } else { + sip_message_add_header(&request, header->name, header->value); + } + } + + sip_message_add_header(&request, "Content-Type", content_type); + sip_message_add_content(&request, dialog->message_content); + + if (ast_strlen_zero(dialog->initial_request.uri)) { + ast_debug(1, "Set initial %s request for '%s'\n", sip_method2str(request.method), dialog->call_id); + sip_message_copy(&dialog->initial_request, &request); + } + + return sip_message_send(dialog, &request, init != SIP_INIT_NONE ? SIP_SEND_CRITICAL : SIP_SEND_RELIABLE, + dialog->outgoing_cseq); +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/response.c asterisk-22.6.0/channels/sip/response.c --- asterisk-22.6.0.orig/channels/sip/response.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/response.c 2025-10-21 18:38:17.466217720 +1300 @@ -0,0 +1,478 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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 + extended + ***/ + +#include "asterisk.h" +#include "asterisk/logger.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/utils.h" +#include "asterisk/netsock2.h" +#include "asterisk/channel.h" +#include "asterisk/callerid.h" +#include "asterisk/indications.h" +#include "asterisk/rtp_engine.h" +#include "asterisk/pbx.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/sdp.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/fax.h" + +static int __sip_response_send(struct sip_dialog *dialog, const char *message, struct sip_message *request, + int reliable); +static void sip_response_temp_dialog_destroy(void *data); + +AST_THREADSTORAGE_CUSTOM(sip_response_temp_dialog_buf, NULL, sip_response_temp_dialog_destroy); + +/* Base transmit response function */ +static int __sip_response_send(struct sip_dialog *dialog, const char *status_line, struct sip_message *request, + int reliable) +{ + struct sip_message response; + + sip_message_build_response(&response, dialog, status_line, request); + + if (dialog->pending_connected_line && (response.code == 180 || response.code == 183)) { + dialog->pending_connected_line = FALSE; + + sip_message_add_identity(&response, dialog); + } + + /* If we are sending a 302 Redirect we can add a diversion header if the redirect information is set */ + if (response.code == 302) { + sip_message_add_diversion(&response, dialog); + } + + if (response.method == SIP_METHOD_INVITE) { + sip_message_add_call_info(&response, dialog); + } + + if (response.method == SIP_METHOD_INVITE || response.method == SIP_METHOD_OPTIONS) { + sip_message_add_date(&response); + } + + /* If we are cancelling an incoming invite for some reason, add information about the reason why we are doing + * this in clear text */ + if (response.method == SIP_METHOD_INVITE && response.code >= 200 && dialog->peer->reason_support) { + int hangupcause; + + if (dialog->channel && ast_channel_hangupcause(dialog->channel)) { + hangupcause = ast_channel_hangupcause(dialog->channel); + } else if (dialog->hangupcause) { + hangupcause = dialog->hangupcause; + } else { + hangupcause = sip_hangup2cause(response.code); + } + + if (hangupcause) { + sip_message_build_header(&response, "Reason", "Q.850;cause=%i", hangupcause & 0x7f); + } + } + + return sip_message_send(dialog, &response, reliable, request->cseq); +} + +/* Transmit response, no resendmits */ +int sip_response_send(struct sip_dialog *dialog, const char *status_line, struct sip_message *request) +{ + return __sip_response_send(dialog, status_line, request, SIP_SEND_UNRELIABLE); +} + +/* Transmit response, Make sure you get an ACK This is only used for responses to INVITEs, where we need to make sure + * we get an ACK */ +int sip_response_send_reliable(struct sip_dialog *dialog, const char *status_line, struct sip_message *request) +{ + return __sip_response_send(dialog, status_line, request, request->ignore ? SIP_SEND_UNRELIABLE : SIP_SEND_CRITICAL); +} + +/* Transmit response, no resendmits */ +int sip_response_send_with_unsupported(struct sip_dialog *dialog, struct sip_message *request, const char *unsupported) +{ + struct sip_message response; + + sip_message_build_response(&response, dialog, "420 Bad Extension", request); + sip_message_add_header(&response, "Unsupported", unsupported); + + return sip_message_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + +/* Append Retry-After header field when transmitting response */ +int sip_response_send_with_retry_after(struct sip_dialog *dialog, struct sip_message *request, int retry_after) +{ + struct sip_message response; + + sip_message_build_response(&response, dialog, "500 Internal Server Error", request); + sip_message_build_header(&response, "Retry-After", "%d", retry_after); + + return sip_message_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + +/* Add date before transmitting response */ +int sip_response_send_with_date(struct sip_dialog *dialog, const char *status_line, struct sip_message *request) +{ + struct sip_message response; + + sip_message_build_response(&response, dialog, status_line, request); + sip_message_add_date(&response); + + return sip_message_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + +/* Append Accept header, content length before transmitting response */ +int sip_response_send_with_accept(struct sip_dialog *dialog, const char *status_line, struct sip_message *request) +{ + struct sip_message response; + + sip_message_build_response(&response, dialog, status_line, request); + sip_message_add_header(&response, "Accept", "application/sdp"); + + return sip_message_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + +/* Append Min-Expires header, content length before transmitting response */ +int sip_response_send_with_min_expires(struct sip_dialog *dialog, struct sip_message *request, int min_expires) +{ + struct sip_message response; + + sip_message_build_response(&response, dialog, "423 Interval Too Small", request); + sip_message_build_header(&response, "Min-Expires", "%d", min_expires); + + return sip_message_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + +/* Respond with authorization request */ +int sip_response_send_with_www_authenticate(struct sip_dialog *dialog, struct sip_message *request, int reliable, + int stale) +{ + struct sip_message response; + + /* Choose Realm */ + sip_dialog_set_realm(dialog, request); + sip_message_build_response(&response, dialog, "401 Unauthorized", request); + + /* Stale means that they sent us correct authentication, but based it on an old challenge (nonce) */ + sip_message_build_header(&response, "WWW-Authenticate", "Digest algorithm=MD5,realm=\"%s\",nonce=\"%s\"%s", + dialog->authorization_realm, dialog->nonce, stale ? ",stale=true" : ""); + + return sip_message_send(dialog, &response, reliable, request->cseq); +} + +int sip_response_send_with_authorization_failure(struct sip_dialog *dialog, struct sip_message *request, int res, + int reliable) +{ + const char *status_line; + + switch (res) { + case SIP_AUTHORIZATION_SECRET_FAILED: + case SIP_AUTHORIZATION_USERNAME_MISMATCH: + case SIP_AUTHORIZATION_NOT_FOUND: + case SIP_AUTHORIZATION_UNKNOWN_DOMAIN: + case SIP_AUTHORIZATION_PEER_NOT_DYNAMIC: + case SIP_AUTHORIZATION_INVALID_TRANSPORT: + case SIP_AUTHORIZATION_ACL_FAILED: + ast_verb(3, "Failed to authenticate %s from '%s'\n", + sip_method2str(dialog->method), sip_message_find_header(request, "From")); + status_line = "403 Forbidden"; + break; + case SIP_AUTHORIZATION_SESSION_LIMIT: + /* Unexpected here, actually. As it's handled elsewhere */ + ast_verb(3, "Maximum calls reached for %s from '%s'\n", + sip_method2str(dialog->method), sip_message_find_header(request, "From")); + status_line = "480 Temporarily Unavailable"; + break; + case SIP_AUTHORIZATION_RTP_FAILED: + /* We don't want to send a 403 in the RTP_FAILED case. The cause could be any one of: out of memory or + * rtp ports or srtp requested but not loaded/invalid. Neither of them warrant a 403. A 503 makes more + * sense, as this node is broken/overloaded */ + ast_verb(3, "RTP init failure for %s from '%s'\n", + sip_method2str(dialog->method), sip_message_find_header(request, "From")); + status_line = "503 Service Unavailable"; + break; + case SIP_AUTHORIZATION_SUCCESS: + case SIP_AUTHORIZATION_CHALLENGE_SENT: + /* These should have been handled elsewhere */ + default: + ast_verb(3, "Unexpected error for %s from '%s'\n", + sip_method2str(dialog->method), sip_message_find_header(request, "From")); + status_line = "503 Service Unavailable"; + break; + } + + if (reliable == SIP_SEND_RELIABLE) { + return sip_response_send_reliable(dialog, status_line, request); + } else { + return sip_response_send(dialog, status_line, request); + } +} + +/* Send a fake 401 Unauthorized response when the administrator wants to hide the names of local devices from fishers */ +int sip_response_send_with_fake_authorization(struct sip_dialog *dialog, struct sip_message *request) +{ + char *authorization = ast_strdupa(sip_message_find_header(request, "Authorization")); + + /* We have to emulate EXACTLY what we'd get with a good peer * and a bad password, or else we leak information */ + if (request->ignore && !ast_strlen_zero(dialog->nonce) && ast_strlen_zero(authorization)) { + /* This is a resendmitted invite/register/etc, don't reconstruct authentication information */ + sip_response_send_with_www_authenticate(dialog, request, SIP_SEND_UNRELIABLE, FALSE); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return 0; + } else if (ast_strlen_zero(dialog->nonce) || ast_strlen_zero(authorization)) { + /* We have no auth, so issue challenge and request authentication */ + sip_dialog_build_nonce(dialog, TRUE); + + sip_response_send_with_www_authenticate(dialog, request, SIP_SEND_UNRELIABLE, FALSE); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + return 0; + } + + sip_parse_authorization(dialog, authorization); + + /* Verify nonce from request matches our nonce. If not, send 401 with new nonce */ + if (strcmp(dialog->nonce, dialog->authorization_nonce)) { + if (!request->ignore) { + sip_dialog_build_nonce(dialog, TRUE); + } + + sip_response_send_with_www_authenticate(dialog, request, SIP_SEND_UNRELIABLE, FALSE); + sip_dialog_sched_destroy(dialog, dialog->timer_b); + } else { + sip_response_send(dialog, "403 Forbidden", &dialog->initial_request); + } + + return 0; +} + +/* Send a 200 OK response with and optionind payload */ +int sip_response_send_with_optionsind(struct sip_dialog *dialog, struct sip_message *request) +{ + struct sip_message response; + + sip_message_build_response(&response, dialog, "200 OK", request); + sip_message_add_header(&response, "Content-Type", "application/x-cisco-remotecc-response+xml"); + sip_message_add_content(&response, "\n" + "\n" + " \n" + " 200\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " Yes\n" + " \n" + " \n" + "\n"); + + return sip_message_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + +int sip_response_send_provisional(struct sip_dialog *dialog, const char *status_line, struct sip_message *request, + int with_sdp) +{ + int res; + + if (with_sdp) { + res = sip_response_send_with_sdp(dialog, status_line, request, SIP_SEND_UNRELIABLE, FALSE, FALSE); + } else { + res = sip_response_send(dialog, status_line, request); + } + + if (!res) { + ast_string_field_set(dialog, provisional_status_line, status_line); + sip_dialog_sched_provisional_keepalive(dialog, with_sdp); + } + + return res; +} + +/* Used for 200 OK and 183 early media */ +int sip_response_send_with_sdp(struct sip_dialog *dialog, const char *status_line, struct sip_message *request, + int reliable, int old_sdp_version, int add_identity) +{ + struct sip_message response; + + sip_message_build_response(&response, dialog, status_line, request); + + if (add_identity) { + sip_message_add_identity(&response, dialog); + sip_message_add_call_info(&response, dialog); + } + + if (dialog->audio_rtp) { + ast_rtp_instance_activate(dialog->audio_rtp); + sip_try_suggested_codec(dialog); + sip_sdp_build(dialog, &response, old_sdp_version, TRUE, dialog->fax_state == SIP_FAX_ENABLED); + } else if (dialog->udptl) { + sip_sdp_build(dialog, &response, FALSE, FALSE, TRUE); + } else { + ast_log(LOG_ERROR, "Unable to add SDP to response to '%s' since we have no RTP or UDPTL allocated\n", + dialog->call_id); + } + + if (reliable && !dialog->pending_invite_cseq) { + dialog->pending_invite_cseq = request->cseq; /* Buggy clients sends ACK on RINGING too */ + } + + return sip_message_send(dialog, &response, reliable, request->cseq); +} + +int sip_response_send_with_identity(struct sip_dialog *dialog, const char *status_line, struct sip_message *request) +{ + struct sip_message response; + + sip_message_build_response(&response, dialog, status_line, request); + sip_message_add_identity(&response, dialog); + + return sip_message_send(dialog, &response, SIP_SEND_UNRELIABLE, request->cseq); +} + +int sip_response_send_with_diversion(struct sip_dialog *dialog, struct sip_message *request) +{ + struct sip_message response; + + sip_message_build_response(&response, dialog, "181 Call Is Being Forwarded", request); + sip_message_add_diversion(&response, dialog); + + return sip_message_send(dialog, &response, SIP_SEND_UNRELIABLE, request->cseq); +} + +int sip_response_send_with_etag(struct sip_dialog *dialog, struct sip_message *request, char *etag) +{ + struct sip_message response; + + sip_message_build_response(&response, dialog, "200 OK", request); + sip_message_add_header(&response, "SIP-ETag", etag); + + return sip_message_send(dialog, &response, SIP_SEND_UNRELIABLE, request->cseq); +} + +/* Transmit 422 response with Min-SE header (Session-Timers) */ +int sip_response_send_with_min_se(struct sip_dialog *dialog, struct sip_message *request, int min_se) +{ + struct sip_message response; + + sip_message_build_response(&response, dialog, "422 Session Interval Too Small", request); + sip_message_build_header(&response, "Min-SE", "%d", min_se); + + return sip_message_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + + +int sip_response_send_with_feature_event(struct sip_dialog *dialog, struct sip_message *request, int feature_event) +{ + struct sip_message response; + + sip_message_build_response(&response, dialog, "200 OK", request); + sip_message_add_header(&response, "Content-Type", "application/x-as-feature-event+xml"); + sip_message_add_content(&response, "\n"); + + if (feature_event == SIP_FEATURE_DO_NOT_DISTURB) { + sip_message_add_content(&response, + "\n"); + } else if (feature_event == SIP_FEATURE_CALL_FORWARD) { + sip_message_add_content(&response, + "\n"); + } + + return sip_message_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + +static void sip_response_temp_dialog_destroy(void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + ast_string_field_free_memory(dialog); + ast_free(dialog); +} + +/* Sends a response using a temporary dialog structure */ +int sip_response_send_using_temp(struct sip_socket *socket, const char *status_line, struct sip_message *request) +{ + struct sip_dialog *dialog; + + if (!(dialog = ast_threadstorage_get(&sip_response_temp_dialog_buf, sizeof(*dialog)))) { + ast_log(LOG_ERROR, "Failed to get temporary dialog\n"); + return -1; + } + + /* Structure may be dirty from previous usage */ + memset(dialog, 0, sizeof(*dialog)); + + if (ast_string_field_init(dialog, 512)) { + ast_free(dialog); + return -1; + } + + /* Initialize the bare minimum */ + sip_socket_copy(&dialog->socket, socket); + ast_sockaddr_copy(&dialog->address, &socket->address); + + sip_dialog_set_our_address(dialog); + dialog->method = request->method; + + ast_string_field_set(dialog, call_id, request->call_id); + ast_string_field_build(dialog, branch, "%s%08x", SIP_MAGIC_COOKIE, (unsigned int) ast_random()); + + sip_dialog_build_local_tag(dialog); + ast_string_field_set(dialog, from_domain, sip_config.from_domain); + dialog->from_domain_port = sip_config.from_domain_port; + + /* Global NAT settings */ + dialog->nat_force_rport = sip_config.nat_force_rport; + dialog->nat_auto_rport = sip_config.nat_auto_rport; + + /* Via header could be missing */ + if (ast_strlen_zero(request->via_sent_by)) { + request->via_sent_by = ast_strdup(ast_sockaddr_stringify_remote(&socket->address)); + } else { + sip_dialog_check_via(dialog, request); + } + + if (ast_strlen_zero(request->via_branch)) { + request->via_branch = ast_strdup(SIP_MAGIC_COOKIE); + } + + sip_response_send(dialog, status_line, request); + ast_string_field_init(dialog, 0); /* Free the string fields, but not the pool space */ + return 0; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/route.c asterisk-22.6.0/channels/sip/route.c --- asterisk-22.6.0.orig/channels/sip/route.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/route.c 2025-10-21 18:38:17.466217720 +1300 @@ -0,0 +1,191 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/utils.h" +#include "asterisk/netsock2.h" +#include "asterisk/channel.h" +#include "asterisk/callerid.h" +#include "asterisk/indications.h" +#include "asterisk/pbx.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/dialog.h" +#include "include/utils.h" + +static void sip_route_build(struct sip_route *route, const char *uri, int forwards); + +static void sip_route_build(struct sip_route *route, const char *uri, int forwards) +{ + struct sip_route_hop *hop; + + /* Expand len to include null terminator */ + if (!(hop = ast_calloc(1, sizeof(*hop)))) { + return; + } + + hop->uri = ast_strdup(uri); + + if (forwards) { + route->type = SIP_ROUTE_UNKNOWN; + + AST_LIST_INSERT_HEAD(&route->hops, hop, next); + } else { + if (sip_route_empty(route)) { + route->type = SIP_ROUTE_UNKNOWN; + } + + AST_LIST_INSERT_TAIL(&route->hops, hop, next); + } +} + +void sip_route_parse(struct sip_route *route, const char *original_path, int forwards) +{ + char *path; + + if (!route) { + return; + } + + path = ast_strdup(original_path); + + while (!ast_strlen_zero(path)) { + char *uri; + + if (!(uri = strchr(path, '<'))) { + break; + } + + *uri++ = '\0'; + + if (!(path = strchr(uri, '>'))) { + break; + } + + *path++ = '\0'; + path = strchr(path, ','); + + sip_route_build(route, uri, forwards); + } +} + +void sip_route_copy(struct sip_route *to_route, const struct sip_route *from_route) +{ + struct sip_route_hop *hop; + + sip_route_destroy(to_route); + + AST_LIST_TRAVERSE(&from_route->hops, hop, next) { + sip_route_build(to_route, hop->uri, FALSE); + } + + to_route->type = from_route->type; +} + +void sip_route_destroy(struct sip_route *route) +{ + struct sip_route_hop *hop; + + while ((hop = AST_LIST_REMOVE_HEAD(&route->hops, next))) { + ast_free(hop->uri); + ast_free(hop); + } + + route->type = SIP_ROUTE_LOOSE; +} + +struct ast_str *sip_route_list(const struct sip_route *route, int skip) +{ + struct sip_route_hop *hop; + struct ast_str *path; + int count; + + if (!(path = ast_str_create(64))) { + return NULL; + } + + count = 0; + + AST_LIST_TRAVERSE(&route->hops, hop, next) { + if (count >= skip) { + if (ast_str_strlen(path)) { + ast_str_append(&path, 0, ","); + } + + ast_str_append(&path, 0, "<%s>", hop->uri); + } + + count++; + } + + return path; +} + +int sip_route_empty(const struct sip_route *route) +{ + return AST_LIST_EMPTY(&route->hops); +} + +int sip_route_is_strict(struct sip_route *route) +{ + if (!route) { + return 0; + } + + if (route->type == SIP_ROUTE_UNKNOWN) { + struct sip_route_hop *hop; + char *parameters; + + route->type = SIP_ROUTE_LOOSE; + hop = AST_LIST_FIRST(&route->hops); + + if (hop && (parameters = strchr(hop->uri, ';'))) { + char *lr; + + parameters = ast_strdupa(parameters + 1); + sip_parse_parameters(parameters, ';', "lr", &lr, NULL); + + if (!ast_strlen_zero(lr)) { + route->type = SIP_ROUTE_STRICT; + } + } + } + + return route->type == SIP_ROUTE_STRICT; +} + +const char *sip_route_first_uri(const struct sip_route *route) +{ + struct sip_route_hop *hop = AST_LIST_FIRST(&route->hops); + + return hop ? hop->uri : NULL; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/rtp_glue.c asterisk-22.6.0/channels/sip/rtp_glue.c --- asterisk-22.6.0.orig/channels/sip/rtp_glue.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/rtp_glue.c 2025-10-21 18:38:17.467217693 +1300 @@ -0,0 +1,343 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/astobj2.h" +#include "asterisk/netsock2.h" +#include "asterisk/channel.h" +#include "asterisk/indications.h" +#include "asterisk/format.h" +#include "asterisk/rtp_engine.h" +#include "asterisk/pbx.h" +#include "asterisk/acl.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/config.h" +#include "include/rtp_glue.h" +#include "include/fax.h" + +struct ast_rtp_glue sip_rtp_glue = { + .type = "SIP", + .get_rtp_info = sip_rtp_get_info, + .allow_rtp_remote = sip_rtp_allow_remote, + .get_vrtp_info = sip_vrtp_get_info, + .allow_vrtp_remote = sip_vrtp_allow_remote, + .get_trtp_info = sip_trtp_get_info, + .update_peer = sip_rtp_update_peer, + .get_codec = sip_rtp_get_codec, +}; + +void sip_rtp_get_codec(struct ast_channel *channel, struct ast_format_cap *format_cap) +{ + ast_format_cap_append_from_cap(format_cap, ast_channel_nativeformats(channel), AST_MEDIA_TYPE_UNKNOWN); +} + +int sip_rtp_allow_any_remote(struct ast_channel *channel, struct ast_rtp_instance *rtp, const char *type) +{ + struct sip_dialog *dialog; + struct ast_acl_list *direct_media_acl; + int allow; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + return FALSE; + } + + ao2_lock(dialog); + direct_media_acl = ast_duplicate_acl_list(dialog->peer->direct_media_acl); + ao2_unlock(dialog); + + if (!direct_media_acl) { + return TRUE; + } + + allow = TRUE; + + if (dialog->direct_media & SIP_DIRECT_MEDIA_NO_NAT) { + struct ast_sockaddr local_address, remote_address; + + ast_rtp_instance_get_requested_target_address(rtp, &remote_address); + ast_rtp_instance_get_local_address(rtp, &local_address); + + if (ast_apply_acl(direct_media_acl, &remote_address, NULL) == AST_SENSE_DENY) { + const char *local_host, *remote_host; + + local_host = ast_strdupa(ast_sockaddr_stringify(&local_address)); + remote_host = ast_strdupa(ast_sockaddr_stringify(&remote_address)); + + allow = FALSE; + ast_debug(3, "Re-invite '%s' to %s denied by directmedia ACL on %s\n", + type, remote_host, local_host); + } + } + + ast_free_acl_list(direct_media_acl); + return allow; +} + +int sip_rtp_allow_remote(struct ast_channel *channel, struct ast_rtp_instance *rtp) +{ + return sip_rtp_allow_any_remote(channel, rtp, "audio"); +} + +int sip_vrtp_allow_remote(struct ast_channel *channel, struct ast_rtp_instance *rtp) +{ + return sip_rtp_allow_any_remote(channel, rtp, "video"); +} + +enum ast_rtp_glue_result sip_rtp_get_info(struct ast_channel *channel, struct ast_rtp_instance **audio_rtp) +{ + struct sip_dialog *dialog; + enum ast_rtp_glue_result res; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + return AST_RTP_GLUE_RESULT_FORBID; + } + + ao2_lock(dialog); + + if (!dialog->audio_rtp) { + ao2_unlock(dialog); + return AST_RTP_GLUE_RESULT_FORBID; + } + + *audio_rtp = ao2_bump(dialog->audio_rtp); + + if (dialog->direct_media & (SIP_DIRECT_MEDIA_NO_NAT | SIP_DIRECT_MEDIA_NAT)) { + res = AST_RTP_GLUE_RESULT_REMOTE; + } else if (ast_test_flag(&sip_jb_config, AST_JB_FORCED)) { + res = AST_RTP_GLUE_RESULT_FORBID; + } else { + res = AST_RTP_GLUE_RESULT_LOCAL; + } + + if (dialog->peer->fax_support) { + switch (dialog->fax_state) { + case SIP_FAX_LOCAL_REINVITE: + case SIP_FAX_REMOTE_REINVITE: + case SIP_FAX_ENABLED: + res = AST_RTP_GLUE_RESULT_LOCAL; + break; + case SIP_FAX_REJECTED: + default: + break; + } + } + + if (dialog->secure_audio_rtp) { + res = AST_RTP_GLUE_RESULT_FORBID; + } + + ao2_unlock(dialog); + return res; +} + +enum ast_rtp_glue_result sip_vrtp_get_info(struct ast_channel *channel, struct ast_rtp_instance **video_rtp) +{ + struct sip_dialog *dialog; + enum ast_rtp_glue_result res; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + return AST_RTP_GLUE_RESULT_FORBID; + } + + ao2_lock(dialog); + + if (!dialog->video_rtp) { + ao2_unlock(dialog); + return AST_RTP_GLUE_RESULT_FORBID; + } + + *video_rtp = ao2_bump(dialog->video_rtp); + + if (dialog->direct_media & SIP_DIRECT_MEDIA_NO_NAT) { + res = AST_RTP_GLUE_RESULT_REMOTE; + } else { + res = AST_RTP_GLUE_RESULT_FORBID; + } + + ao2_unlock(dialog); + return res; +} + +enum ast_rtp_glue_result sip_trtp_get_info(struct ast_channel *channel, struct ast_rtp_instance **text_rtp) +{ + struct sip_dialog *dialog; + enum ast_rtp_glue_result res; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + return AST_RTP_GLUE_RESULT_FORBID; + } + + ao2_lock(dialog); + + if (!dialog->text_rtp) { + ao2_unlock(dialog); + return AST_RTP_GLUE_RESULT_FORBID; + } + + *text_rtp = ao2_bump(dialog->text_rtp); + + if (dialog->direct_media & SIP_DIRECT_MEDIA_NO_NAT) { + res = AST_RTP_GLUE_RESULT_REMOTE; + } else { + res = AST_RTP_GLUE_RESULT_FORBID; + } + + ao2_unlock(dialog); + return res; +} + +int sip_rtp_update_peer(struct ast_channel *channel, struct ast_rtp_instance *audio_rtp, + struct ast_rtp_instance *video_rtp, struct ast_rtp_instance *text_rtp, const struct ast_format_cap *format_cap, + int nat_active) +{ + struct sip_dialog *dialog; + int changed; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + return -1; + } + + ao2_lock(dialog); + + if (dialog->channel != channel) { + /* I suppose it could be argued that if this happens it is a bug */ + ast_debug(1, "The dialog does not own the channel '%s' anymore\n", ast_channel_name(channel)); + ao2_unlock(dialog); + return 0; + } + + /* Disable early RTP bridge */ + if ((audio_rtp || video_rtp || text_rtp) && !ast_channel_is_bridged(channel) && !sip_config.direct_rtp_setup) { + ao2_unlock(dialog); + return 0; + } + + if (dialog->already_gone) { + /* If we're destroyed, don't bother */ + ao2_unlock(dialog); + return 0; + } + + /* If this peer cannot handle reinvites of the media stream to devices that are known to be behind a NAT, + * then stop the process now */ + if (nat_active && !(dialog->direct_media & SIP_DIRECT_MEDIA_NAT)) { + ao2_unlock(dialog); + return 0; + } + + changed = FALSE; + + if (audio_rtp) { + changed |= ast_rtp_instance_get_and_cmp_remote_address(audio_rtp, &dialog->audio_redirect_address); + + if (dialog->audio_rtp) { + /* Prevent audio RTCP reads */ + ast_channel_set_fd(channel, SIP_AUDIO_RTCP_FD, -1); + /* Silence RTCP while audio RTP is inactive */ + ast_rtp_instance_set_prop(dialog->audio_rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_DISABLED); + } + } else if (!ast_sockaddr_isnull(&dialog->audio_redirect_address)) { + ast_sockaddr_setnull(&dialog->audio_redirect_address); + changed = TRUE; + } + + if (video_rtp) { + changed |= ast_rtp_instance_get_and_cmp_remote_address(video_rtp, &dialog->video_redirect_address); + + if (dialog->video_rtp) { + /* Prevent video RTCP reads */ + ast_channel_set_fd(channel, SIP_VIDEO_RTCP_FD, -1); + /* Silence RTCP while video RTP is inactive */ + ast_rtp_instance_set_prop(dialog->video_rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_DISABLED); + } + } else if (!ast_sockaddr_isnull(&dialog->video_redirect_address)) { + ast_sockaddr_setnull(&dialog->video_redirect_address); + changed = TRUE; + + if (dialog->video_rtp) { + /* Enable RTCP since it will be inactive if we're coming back from a reinvite */ + ast_rtp_instance_set_prop(dialog->video_rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_STANDARD); + /* Enable video RTCP reads */ + ast_channel_set_fd(channel, SIP_VIDEO_RTCP_FD, ast_rtp_instance_fd(dialog->video_rtp, TRUE)); + } + } + + if (text_rtp) { + changed |= ast_rtp_instance_get_and_cmp_remote_address(text_rtp, &dialog->text_redirect_address); + } else if (!ast_sockaddr_isnull(&dialog->text_redirect_address)) { + ast_sockaddr_setnull(&dialog->text_redirect_address); + changed = TRUE; + } + + if (format_cap && ast_format_cap_count(format_cap) && !ast_format_cap_identical(format_cap, dialog->redirect_format_cap)) { + ast_format_cap_remove_by_type(dialog->redirect_format_cap, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(dialog->redirect_format_cap, format_cap, AST_MEDIA_TYPE_UNKNOWN); + changed = TRUE; + } + + if ((dialog->direct_media & SIP_DIRECT_MEDIA_OUTGOING) && !dialog->originated_call) { + /* We only wish to withhold sending the initial direct media reinvite on the incoming dialog. Further + * direct media reinvites beyond the initial should be sent. In order to allow further direct media + * reinvites to be sent, we clear this flag */ + dialog->direct_media &= ~SIP_DIRECT_MEDIA_OUTGOING; + ao2_unlock(dialog); + return 0; + } + + if (changed && !dialog->transferring_call && !dialog->defer_bye_on_transfer) { + if (ast_channel_state(channel) != AST_STATE_UP) { /* We are in early state */ + ast_debug(1, "Early remote bridge setting SIP '%s' sending media to %s\n", + dialog->call_id, + ast_sockaddr_stringify(audio_rtp ? &dialog->audio_redirect_address : &dialog->our_address)); + } else if (!dialog->pending_invite_cseq) { /* We are up, and have no outstanding invite */ + ast_debug(3, "Sending reinvite on SIP '%s' it's audio soon redirected to IP %s\n", + dialog->call_id, + ast_sockaddr_stringify(audio_rtp ? &dialog->audio_redirect_address : &dialog->our_address)); + sip_request_send_reinvite_with_sdp(dialog, FALSE, FALSE); + } else if (!dialog->pending_bye) { + ast_debug(3, "Deferring reinvite on SIP '%s' it's audio will be redirected to IP %s\n", + dialog->call_id, + ast_sockaddr_stringify(audio_rtp ? &dialog->audio_redirect_address : &dialog->our_address)); + /* We have a pending Invite. Send re-invite when we're done with the invite */ + dialog->need_reinvite = TRUE; + } + } + + /* Reset last rtp received and sent time */ + dialog->last_rtp_received = dialog->last_rtp_sent = ast_tvnow(); + ao2_unlock(dialog); + return 0; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/sdp.c asterisk-22.6.0/channels/sip/sdp.c --- asterisk-22.6.0.orig/channels/sip/sdp.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/sdp.c 2025-10-21 18:38:17.468217666 +1300 @@ -0,0 +1,2424 @@ + /* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/strings.h" +#include "asterisk/indications.h" +#include "asterisk/sdp_srtp.h" +#include "asterisk/format.h" +#include "asterisk/format_cache.h" +#include "asterisk/pbx.h" +#include "asterisk/udptl.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/sdp.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/fax.h" + +#define SIP_SDP_MAX_CODECS 32 /* Maximum number of codecs allowed in received SDP */ + +enum sip_sdp_mode { + SIP_SDP_MODE_UNKNOWN = -1, + SIP_SDP_MODE_SENDRECV = 0, + SIP_SDP_MODE_SENDONLY = 1, + SIP_SDP_MODE_INACTIVE = 2, +}; + +static void sip_sdp_media_address(struct sip_dialog *dialog, int add_audio, struct ast_sockaddr *audio_address, + int add_video, struct ast_sockaddr *video_address, int add_text, struct ast_sockaddr *text_address); +static void sip_sdp_remove_unused_crypto(struct ast_sdp_srtp **sdp_srtp); +static const char *sip_sdp_next_line(struct sip_message *message, int *iter, int no_m_line); + +static int sip_sdp_parse_o(const char *o_line, struct sip_dialog *dialog); +static int sip_sdp_parse_c(const char *c_line, struct ast_sockaddr *address); + +static int sip_sdp_parse_m_audio(const char *m_line, struct sip_dialog *dialog, struct ast_rtp_codecs *audio_codecs, + int *audio_port, int *secure_audio); +static int sip_sdp_parse_a_audio(const char *a_line, struct sip_dialog *dialog, struct ast_rtp_codecs *audio_codecs, + int *audio_codec, int *codec_count); + +static int sip_sdp_parse_m_video(const char *m_line, struct sip_dialog *dialog, struct ast_rtp_codecs *video_codecs, + int *video_port, int *secure_video); +static int sip_sdp_parse_a_video(const char *a_line, struct sip_dialog *dialog, struct ast_rtp_codecs *video_codecs, + int *video_codec, int *codec_count); + +static int sip_sdp_parse_m_text(const char *m_line, struct sip_dialog *dialog, struct ast_rtp_codecs *text_codecs, + int *text_port, int *secure_text); +static int sip_sdp_parse_a_text(const char *a_line, struct sip_dialog *dialog, struct ast_rtp_codecs *text_codecs, + int *codec_count, char *red_fmtp, int *red_generation, int *red_data_pt); + +static int sip_sdp_parse_m_image(const char *m_line, struct sip_dialog *dialog, int *image_port); +static int sip_sdp_parse_a_image(const char *a_line, struct sip_dialog *dialog); + +static int sip_sdp_parse_a_mode(const char *a_aline, int *mode); +static int sip_sdp_parse_a_crypto(const char *a_line, struct sip_dialog *dialog, struct ast_rtp_instance *rtp, + struct ast_sdp_srtp **sdp_srtp); +static int sip_sdp_parse_a_ice(const char *a_aline, struct sip_dialog *dialog, struct ast_rtp_instance *rtp, + int rtcp_mux); +static int sip_sdp_parse_a_rtcp_mux(const char *a_line, struct sip_dialog *dialog, int *rtcp_mux); + +static void sip_sdp_add_audio_codec(struct sip_dialog *dialog, struct ast_format *format, struct ast_str **m_line, + struct ast_str **a_lines, int *min_ptime, int *max_ptime); +static void sip_sdp_add_video_codec(struct sip_dialog *dialog, struct ast_format *format, + struct ast_str **m_line, struct ast_str **a_lines); +static void sip_sdp_add_text_codec(struct sip_dialog *dialog, struct ast_format *format, + struct ast_str **m_line, struct ast_str **a_lines); +static void sip_sdp_add_non_codec(struct sip_dialog *dialog, int format, struct ast_str **m_line, + struct ast_str **a_lines); +static void sip_sdp_add_ice(struct ast_rtp_instance *rtp, struct ast_str **a_lines); + +static const char *sip_sdp_get_a_mode(struct sip_dialog *dialog); +static char *sip_sdp_get_a_crypto(struct ast_sdp_srtp *sdp_srtp); + +static struct sip_sdp_media *sip_sdp_media_alloc(struct sip_dialog *dialog, const char *m_line); +static int sip_sdp_media_has_type(struct sip_dialog *dialog, int type); + +static void sip_sdp_set_rtcp_mux(struct sip_dialog *dialog, struct ast_rtp_instance *rtp, int which, int rtcp_mux); +static void sip_sdp_change_ice_rtcp_mux(struct sip_dialog *dialog, struct ast_rtp_instance *rtp, int rtcp_mux); +static void sip_sdp_start_ice(struct ast_rtp_instance *rtp, int offered); + +/* Determine whether a SIP message contains an SDP in its body Also updates message->sdp_start and message->sdp_end to + * indicate where the SDP lives in the message body */ +int sip_sdp_find(struct sip_message *message) +{ + char *content_type, *boundary; + int done, start, end; + + /* Content-Length of zero means there can't possibly be an SDP here, even if the Content-Type says there is */ + if (!atoi(sip_message_find_header(message, "Content-Length"))) { + return FALSE; + } + + content_type = ast_strdupa(sip_message_find_header(message, "Content-Type")); + + if (ast_strlen_zero(content_type)) { + return FALSE; + } + + content_type = sip_parse_content_type(content_type, &boundary); + + /* If the body contains only SDP, this is easy */ + if (!strcmp(content_type, "application/sdp")) { + message->sdp_start = 0; + message->sdp_end = message->content_count; + return message->sdp_end > 0; + } + + /* If it's not multipart/mixed, there cannot be an SDP */ + if (strcmp(content_type, "multipart/mixed") || ast_strlen_zero(boundary)) { + return FALSE; + } + + done = FALSE; + end = 0; + + while (!done) { + char *content_type; + int i; + + if ((start = sip_message_find_boundary(message, boundary, end, &done)) == -1) { + return FALSE; + } + + start += 1; + + if ((end = sip_message_find_boundary(message, boundary, start, &done)) == -1) { + return FALSE; + } + + content_type = ""; + + for (i = start; i < end; i++) { + if (!strncasecmp(message->content[i], "Content-Type:", 13)) { + content_type = ast_skip_blanks(message->content[i] + 13); + } else if (ast_strlen_zero(message->content[i])) { + start = i + 1; + break; + } + } + + if (!strcmp(content_type, "application/sdp")) { + message->sdp_start = start; + message->sdp_end = end; + return message->sdp_end > message->sdp_start; + } + } + + return FALSE; +} + +/* Set all IP media addresses for this call */ +static void sip_sdp_media_address(struct sip_dialog *dialog, int add_audio, struct ast_sockaddr *audio_address, + int add_video, struct ast_sockaddr *video_address, int add_text, struct ast_sockaddr *text_address) +{ + struct ast_sockaddr address; + + ast_sockaddr_setnull(audio_address); + ast_sockaddr_setnull(video_address); + ast_sockaddr_setnull(text_address); + + /* Now, try to figure out where we want them to send data Is this a re-invite to move the media out, then use + * the original offer from caller */ + if (add_audio && dialog->audio_rtp) { + if (!ast_sockaddr_isnull(&dialog->audio_redirect_address)) { /* If we have a redirection IP, use it */ + ast_sockaddr_copy(audio_address, &dialog->audio_redirect_address); + } else { + /* First, get our address */ + ast_rtp_instance_get_local_address(dialog->audio_rtp, &address); + + /* Audio Destination IP address: + * 1. Specifically configured media address. + * 2. Local address as specified by the RTP engine. + * 3. The local IP as defined by chan_sip */ + if (!ast_sockaddr_isnull(&sip_config.media_address)) { + ast_sockaddr_copy(audio_address, &sip_config.media_address); + } else if (!ast_sockaddr_is_any(&address) && !ast_sockaddr_cmp_addr(&dialog->our_address, &address)) { + /* If our real IP differs from the local address returned by the RTP engine, use it. + * The premise is that if we are already using that IP to communicate with the client, + * we should be using it for RTP too */ + ast_sockaddr_copy(audio_address, &address); + } else { + ast_sockaddr_copy(audio_address, &dialog->our_address); + } + + /* Audio Destination Port: Provided by the RTP engine */ + ast_sockaddr_set_port(audio_address, ast_sockaddr_port(&address)); + } + } + + if (add_video && dialog->video_rtp) { + /* Determine video destination */ + if (!ast_sockaddr_isnull(&dialog->video_redirect_address)) { + ast_sockaddr_copy(video_address, &dialog->video_redirect_address); + } else { + ast_rtp_instance_get_local_address(dialog->video_rtp, &address); + + /* Video Destination IP: + * 1. Specifically configured media address. + * 2. Local address as specified by the RTP engine. + * 3. The local IP as defined by chan_sip */ + if (!ast_sockaddr_isnull(&sip_config.media_address)) { + ast_sockaddr_copy(video_address, &sip_config.media_address); + } else if (!ast_sockaddr_is_any(&address) && !ast_sockaddr_cmp_addr(&dialog->our_address, &address)) { + /* If our real IP differs from the local address returned by the RTP engine, use it. + * The premise is that if we are already using that IP to communicate with the client, + * we should be using it for RTP too */ + ast_sockaddr_copy(video_address, &address); + } else { + ast_sockaddr_copy(video_address, &dialog->our_address); + } + + /* Video Destination Port: Provided by the RTP engine */ + ast_sockaddr_set_port(video_address, ast_sockaddr_port(&address)); + } + } + + if (add_text && dialog->text_rtp) { + /* Determine text destination */ + if (!ast_sockaddr_isnull(&dialog->text_redirect_address)) { + ast_sockaddr_copy(text_address, &dialog->text_redirect_address); + } else { + ast_rtp_instance_get_local_address(dialog->text_rtp, &address); + + /* Text Destination IP: + * 1. Specifically configured media address. + * 2. Local address as specified by the RTP engine. + * 3. The local IP as defined by chan_sip */ + if (!ast_sockaddr_isnull(&sip_config.media_address)) { + ast_sockaddr_copy(text_address, &sip_config.media_address); + } else if (!ast_sockaddr_is_any(&address) && !ast_sockaddr_cmp_addr(&dialog->our_address, &address)) { + /* If our real IP differs from the local address returned by the RTP engine, use it. + * The premise is that if we are already using that IP to communicate with the client, + * we should be using it for RTP too */ + ast_sockaddr_copy(text_address, &address); + } else { + ast_sockaddr_copy(text_address, &dialog->our_address); + } + + /* Text Destination Port: Provided by the RTP engine */ + ast_sockaddr_set_port(text_address, ast_sockaddr_port(&address)); + } + } +} + +static void sip_sdp_remove_unused_crypto(struct ast_sdp_srtp **sdp_srtp) +{ + if (!*sdp_srtp) { + return; + } + + /* Delete all but the first crypto line otherwise when a call is put on hold Asterisk will re-send the + * a:crytpo offers causing the call to fail */ + ast_sdp_srtp_destroy(AST_LIST_NEXT(*sdp_srtp, sdp_srtp_list)); + AST_LIST_NEXT(*sdp_srtp, sdp_srtp_list) = NULL; +} + +static const char *sip_sdp_next_line(struct sip_message *message, int *iter, int no_m_line) +{ + const char *line; + + if (!*iter) { + *iter = message->sdp_start; + } else if (*iter >= message->sdp_end) { + return NULL; + } + + line = message->content[*iter]; + + if (line[0] < 'a' || line[0] > 'z' || line[1] != '=') { + return NULL; + } else if (no_m_line && line[0] == 'm') { + return NULL; + } + + (*iter)++; + return line; +} + +/* Process SIP SDP offer, select formats and activate media channel. If offer is rejected, we will not change any + * properties of the call */ +int sip_sdp_parse(struct sip_dialog *dialog, struct sip_message *message, int add_image, int offered) +{ + RAII_VAR(struct ast_format_cap *, audio_format_cap, NULL, ao2_cleanup); + RAII_VAR(struct ast_format_cap *, video_format_cap, NULL, ao2_cleanup); + RAII_VAR(struct ast_format_cap *, text_format_cap, NULL, ao2_cleanup); + RAII_VAR(struct ast_format_cap *, format_cap, NULL, ao2_cleanup); + RAII_VAR(struct ast_format_cap *, joint_format_cap, NULL, ao2_cleanup); /* Negotiated capability */ + int res, audio_port, video_port, text_port, image_port, audio_non_format_cap, video_non_format_cap, + text_non_format_cap, joint_non_format_cap, audio_codec, video_codec, codec_count, mode, rtcp_mux_audio, + rtcp_mux_video, secure_audio, secure_video, secure_text, red_data_pt[10], red_generation, iter; + struct ast_sockaddr session_address, audio_address, video_address, text_address, image_address; + struct ast_rtp_codecs audio_codecs, video_codecs, text_codecs; + struct ast_str *format_names; + struct sip_sdp_media *sdp_media; + char red_fmtp[16]; + const char *line; + + res = -1; + + audio_format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + video_format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + text_format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + joint_format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + + if (!audio_format_cap || !video_format_cap || !text_format_cap || !format_cap || !joint_format_cap) { + goto cleanup; + } + + memset(&audio_codecs, 0, sizeof(audio_codecs)); + memset(&video_codecs, 0, sizeof(video_codecs)); + memset(&text_codecs, 0, sizeof(text_codecs)); + + if (ast_rtp_codecs_payloads_initialize(&audio_codecs) || + ast_rtp_codecs_payloads_initialize(&video_codecs) || + ast_rtp_codecs_payloads_initialize(&text_codecs)) { + goto cleanup; + } + + sip_sdp_media_destroy(dialog); + /* Update our last rtprx when we receive an SDP, too */ + dialog->last_rtp_received = dialog->last_rtp_sent = ast_tvnow(); + + codec_count = 0; + audio_codec = 255; + video_codec = 255; + red_generation = 0; + + /* For T.140 RED, actual attribute+codec is set by sip_sdp_parse_a_text */ + ast_copy_string(red_fmtp, "a=fmtp:0 ", sizeof(red_fmtp)); + + ast_sockaddr_setnull(&session_address); + ast_sockaddr_setnull(&audio_address); + ast_sockaddr_setnull(&video_address); + ast_sockaddr_setnull(&text_address); + ast_sockaddr_setnull(&image_address); + + audio_port = 0; + video_port = 0; + text_port = 0; + image_port = 0; + + mode = SIP_SDP_MODE_UNKNOWN; + + secure_audio = FALSE; + secure_video = FALSE; + secure_text = FALSE; + + rtcp_mux_audio = FALSE; + rtcp_mux_video = FALSE; + + /* Scan session level SDP parameters (lines before first media stream) */ + iter = 0; + + while ((line = sip_sdp_next_line(message, &iter, TRUE))) { + int parsed = FALSE; + + if (line[0] == 'o') { + /* If we end up receiving SDP that doesn't actually modify the session we don't want to treat + * this as a fatal error. We just want to ignore the SDP and let the rest of the packet be + * handled as normal */ + if (!sip_sdp_parse_o(line, dialog)) { + res = !dialog->sdp_changed ? 0 : -1; + goto cleanup; + } + + parsed = TRUE; + } else if (line[0] == 'c') { + if (sip_sdp_parse_c(line, &session_address)) { + ast_sockaddr_copy(&audio_address, &session_address); + ast_sockaddr_copy(&video_address, &session_address); + ast_sockaddr_copy(&text_address, &session_address); + ast_sockaddr_copy(&image_address, &session_address); + + parsed = TRUE; + } + } else if (line[0] == 'a') { + if (sip_sdp_parse_a_audio(line, dialog, &audio_codecs, &audio_codec, &codec_count)) { + parsed = TRUE; + } else if (sip_sdp_parse_a_video(line, dialog, &video_codecs, &video_codec, &codec_count)) { + parsed = TRUE; + } else if (sip_sdp_parse_a_text(line, dialog, &text_codecs, &codec_count, red_fmtp, + &red_generation, red_data_pt)) { + parsed = TRUE; + } else if (sip_sdp_parse_a_image(line, dialog)) { + parsed = TRUE; + } else if (sip_sdp_parse_a_mode(line, &mode)) { + parsed = TRUE; + } else { + if (sip_sdp_parse_a_ice(line, dialog, dialog->audio_rtp, 0)) { + parsed = TRUE; + } + + if (sip_sdp_parse_a_ice(line, dialog, dialog->video_rtp, 0)) { + parsed = TRUE; + } + + if (sip_sdp_parse_a_ice(line, dialog, dialog->text_rtp, 0)) { + parsed = TRUE; + } + } + } else if (line[0] == 'v' || line[0] == 's' || line[0] == 't') { + parsed = TRUE; + } + + ast_debug(3, "Parsing session-level SDP '%s': %s\n", line, parsed ? "OK" : "Unsupported or failed"); + } + + /* Default to novideo and notext set */ + dialog->no_video_support = TRUE; + dialog->no_text_support = TRUE; + + /* Scan media stream (m=) specific parameters loop */ + while ((line = sip_sdp_next_line(message, &iter, FALSE))) { + int rtcp_mux, mux_iter, audio_crypto, video_crypto, text_crypto; + const char *mux_line; + + if (line[0] != 'm') { + ast_log(LOG_WARNING, "Invalid SDP line (not m=): '%s'\n", line); + break; + } + + /* We need to check for mux support ahead of time, this is terrible */ + mux_iter = iter; + rtcp_mux = FALSE; + + while ((mux_line = sip_sdp_next_line(message, &mux_iter, TRUE))) { + if (mux_line[0] == 'a' && sip_sdp_parse_a_rtcp_mux(line, dialog, &rtcp_mux)) { + break; + } + } + + if (!(sdp_media = sip_sdp_media_alloc(dialog, line))) { + goto cleanup; + } + + /* Media stream formats */ + if (!strncmp(line, "m=audio ", 8)) { + if (sip_sdp_media_has_type(dialog, AST_MEDIA_TYPE_AUDIO)) { + ast_debug(1, "Declining additional audio stream: %s\n", line); + } else if (sip_sdp_parse_m_audio(line, dialog, &audio_codecs, &audio_port, &secure_audio)) { + sdp_media->type = AST_MEDIA_TYPE_AUDIO; + } + } else if (!strncmp(line, "m=video ", 8)) { + if (sip_sdp_media_has_type(dialog, AST_MEDIA_TYPE_VIDEO)) { + ast_debug(1, "Declining additional video stream: %s\n", line); + } else if (sip_sdp_parse_m_video(line, dialog, &video_codecs, &video_port, &secure_video)) { + sdp_media->type = AST_MEDIA_TYPE_VIDEO; + } + } else if (!strncmp(line, "m=text", 7)) { + if (sip_sdp_media_has_type(dialog, AST_MEDIA_TYPE_TEXT)) { + ast_debug(1, "Declining additional text stream: %s\n", line); + } else if (sip_sdp_parse_m_text(line, dialog, &text_codecs, &text_port, &secure_text)) { + sdp_media->type = AST_MEDIA_TYPE_TEXT; + } + } else if (!strncmp(line, "m=image ", 8)) { + if (sip_sdp_media_has_type(dialog, AST_MEDIA_TYPE_IMAGE)) { + ast_debug(1, "Declining additional image stream: %s\n", line); + } else if (sip_sdp_parse_m_image(line, dialog, &image_port)) { + sdp_media->type = AST_MEDIA_TYPE_IMAGE; + } + } else { + ast_log(LOG_WARNING, "Unsupported top-level media type in SDP: %s\n", line); + } + + audio_crypto = FALSE; + video_crypto = FALSE; + text_crypto = FALSE; + + /* Media stream specific parameters */ + while ((line = sip_sdp_next_line(message, &iter, TRUE))) { + int parsed = FALSE; + + if (line[0] == 'c') { + if (sdp_media->type == AST_MEDIA_TYPE_AUDIO && sip_sdp_parse_c(line, &audio_address)) { + parsed = TRUE; + } else if (sdp_media->type == AST_MEDIA_TYPE_VIDEO && sip_sdp_parse_c(line, &video_address)) { + parsed = TRUE; + } else if (sdp_media->type == AST_MEDIA_TYPE_TEXT && sip_sdp_parse_c(line, &text_address)) { + parsed = TRUE; + } else if (sdp_media->type == AST_MEDIA_TYPE_IMAGE && sip_sdp_parse_c(line, &image_address)) { + parsed = TRUE; + } + } else if (line[0] == 'a') { + /* Audio specific scanning */ + if (sdp_media->type == AST_MEDIA_TYPE_AUDIO) { + if (sip_sdp_parse_a_audio(line, dialog, &audio_codecs, &audio_codec, &codec_count)) { + parsed = TRUE; + } else if (sip_sdp_parse_a_mode(line, &mode)) { + parsed = TRUE; + } else if (!audio_crypto && sip_sdp_parse_a_crypto(line, dialog, + dialog->audio_rtp, &dialog->secure_audio_rtp)) { + audio_crypto = TRUE; + parsed = TRUE; + + if (!secure_audio) { + ast_log(AST_LOG_NOTICE, + "Processed audio crypto attribute without SAVP specified; accepting anyway\n"); + secure_audio = TRUE; + } + } else if (sip_sdp_parse_a_ice(line, dialog, dialog->audio_rtp, rtcp_mux)) { + parsed = TRUE; + } else if (sip_sdp_parse_a_rtcp_mux(line, dialog, &rtcp_mux_audio)) { + parsed = TRUE; + } + /* Video specific scanning */ + } else if (sdp_media->type == AST_MEDIA_TYPE_VIDEO) { + if (sip_sdp_parse_a_video(line, dialog, &video_codecs, &video_codec, &codec_count)) { + parsed = TRUE; + } else if (sip_sdp_parse_a_mode(line, &mode)) { + parsed = TRUE; + } else if (!video_crypto && sip_sdp_parse_a_crypto(line, dialog, + dialog->video_rtp, &dialog->secure_video_rtp)) { + video_crypto = TRUE; + parsed = TRUE; + + if (!secure_video) { + ast_log(AST_LOG_NOTICE, + "Processed video crypto attribute without SAVP specified; accepting anyway\n"); + secure_video = TRUE; + } + } else if (sip_sdp_parse_a_ice(line, dialog, dialog->video_rtp, rtcp_mux)) { + parsed = TRUE; + } else if (sip_sdp_parse_a_rtcp_mux(line, dialog, &rtcp_mux_video)) { + parsed = TRUE; + } + /* Text (T.140) specific scanning */ + } else if (sdp_media->type == AST_MEDIA_TYPE_TEXT) { + if (sip_sdp_parse_a_text(line, dialog, &text_codecs, &codec_count, red_fmtp, + &red_generation, red_data_pt)) { + parsed = TRUE; + } else if (!text_crypto && sip_sdp_parse_a_crypto(line, dialog, + dialog->text_rtp, &dialog->secure_text_rtp)) { + text_crypto = TRUE; + parsed = TRUE; + } else if (sip_sdp_parse_a_ice(line, dialog, dialog->text_rtp, rtcp_mux)) { + parsed = TRUE; + } + /* Image (T.38 FAX) specific scanning */ + } else if (sdp_media->type == AST_MEDIA_TYPE_IMAGE) { + if (sip_sdp_parse_a_image(line, dialog)) { + parsed = TRUE; + } + } + } else if (line[0] == 'b') { + parsed = TRUE; + } + + ast_debug(3, "Processing media-level (%s) SDP '%s': %s\n", + ast_codec_media_type2str(sdp_media->type), line, parsed ? "OK" : "Unsupported or failed."); + } + + /* Ensure crypto lines are provided where necessary */ + if (sdp_media->type == AST_MEDIA_TYPE_AUDIO && secure_audio && !audio_crypto) { + ast_log(LOG_WARNING, "Rejecting secure audio stream without encryption details\n"); + goto cleanup; + } else if (sdp_media->type == AST_MEDIA_TYPE_VIDEO && secure_video && !video_crypto) { + ast_log(LOG_WARNING, "Rejecting secure video stream without encryption details\n"); + goto cleanup; + } else if (sdp_media->type == AST_MEDIA_TYPE_TEXT && secure_text && !text_crypto) { + ast_log(LOG_WARNING, "Rejecting secure text stream without encryption details\n"); + goto cleanup; + } + } + + /* Sanity checks */ + if (ast_sockaddr_isnull(&audio_address) && ast_sockaddr_isnull(&video_address) && + ast_sockaddr_isnull(&text_address) && ast_sockaddr_isnull(&image_address)) { + ast_log(LOG_WARNING, "Insufficient information in SDP (c=)...\n"); + goto cleanup; + } + + if (audio_port == 0 && video_port == 0 && text_port == 0 && image_port == 0) { + ast_log(LOG_WARNING, "Failing due to no acceptable offer found\n"); + goto cleanup; + } + + if (image_port == 0) { + sip_fax_set_state(dialog, SIP_FAX_DISABLED); + } + + if (dialog->secure_audio_rtp && dialog->udptl && image_port > 0) { + ast_debug(1, "Terminating SRTP due to T.38 UDPTL\n"); + + ast_sdp_srtp_destroy(dialog->secure_audio_rtp); + dialog->secure_audio_rtp = NULL; + } + + if (secure_audio && !(dialog->secure_audio_rtp && ast_test_flag(dialog->secure_audio_rtp, AST_SRTP_CRYPTO_OFFER_OK))) { + ast_log(LOG_WARNING, "Unable to provide secure audio requested in SDP offer\n"); + goto cleanup; + } + + if (!secure_audio && dialog->secure_audio_rtp) { + ast_log(LOG_WARNING, "Failed to receive SDP offer/answer with required SRTP crypto attributes for audio\n"); + goto cleanup; + } + + if (secure_video && !(dialog->secure_video_rtp && ast_test_flag(dialog->secure_video_rtp, AST_SRTP_CRYPTO_OFFER_OK))) { + ast_log(LOG_WARNING, "Unable to provide secure video requested in SDP offer\n"); + goto cleanup; + } + + if (!dialog->no_video_support && !secure_video && dialog->secure_video_rtp) { + ast_log(LOG_WARNING, "Failed to receive SDP offer/answer with required SRTP crypto attributes for video\n"); + goto cleanup; + } + + if (!(secure_audio || secure_video || (dialog->udptl && image_port > 0)) && dialog->secure_media) { + ast_log(LOG_WARNING, "Matched device setup to use SRTP, but request was not\n"); + goto cleanup; + } + + /* If multiple crypto suites were sent remove all but the first one. sip_sdp_parse_a_crypto moved the chosen + * cipher suite to the head of the list */ + sip_sdp_remove_unused_crypto(&dialog->secure_audio_rtp); + sip_sdp_remove_unused_crypto(&dialog->secure_video_rtp); + sip_sdp_remove_unused_crypto(&dialog->secure_text_rtp); + + if (offered) { + /* Setup rx payload type mapping to prefer the mapping from the peer that the RFC says we SHOULD use */ + ast_rtp_codecs_payloads_xover(&audio_codecs, &audio_codecs, NULL); + ast_rtp_codecs_payloads_xover(&video_codecs, &video_codecs, NULL); + ast_rtp_codecs_payloads_xover(&text_codecs, &text_codecs, NULL); + } + + /* Now gather all of the codecs that we are asked for: */ + ast_rtp_codecs_payload_formats(&audio_codecs, audio_format_cap, &audio_non_format_cap); + ast_rtp_codecs_payload_formats(&video_codecs, video_format_cap, &video_non_format_cap); + ast_rtp_codecs_payload_formats(&text_codecs, text_format_cap, &text_non_format_cap); + + ast_format_cap_append_from_cap(format_cap, audio_format_cap, AST_MEDIA_TYPE_AUDIO); + ast_format_cap_append_from_cap(format_cap, video_format_cap, AST_MEDIA_TYPE_VIDEO); + ast_format_cap_append_from_cap(format_cap, text_format_cap, AST_MEDIA_TYPE_TEXT); + + ast_format_cap_get_compatible(dialog->format_cap, format_cap, joint_format_cap); + + if (!ast_format_cap_count(joint_format_cap) && image_port == 0) { + ast_log(LOG_NOTICE, "No compatible codecs, not accepting this offer!\n"); + /* Do NOT Change current setting */ + goto cleanup; + } + + joint_non_format_cap = dialog->non_format_cap & audio_non_format_cap; + + if (message->debug) { + struct ast_str *format_names, *audio_format_names, *video_format_names, *text_format_names, + *joint_format_names, *non_format_names, *remote_non_format_names, *joint_non_format_names; + + format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + audio_format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + video_format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + text_format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + joint_format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + non_format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + remote_non_format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + joint_non_format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + + ast_verb(3, "Format capabilities: local %s, remote audio %s, remote video %s, remote text %s, joint %s\n", + ast_format_cap_get_names(dialog->format_cap, &format_names), + ast_format_cap_get_names(audio_format_cap, &audio_format_names), + ast_format_cap_get_names(video_format_cap, &video_format_names), + ast_format_cap_get_names(text_format_cap, &text_format_names), + ast_format_cap_get_names(joint_format_cap, &joint_format_names)); + + ast_verb(3, "Non-codec format capabilities (dtmf): local %s, remote %s, joint %s\n", + ast_rtp_lookup_mime_multiple2(non_format_names, NULL, dialog->non_format_cap, 0, 0), + ast_rtp_lookup_mime_multiple2(remote_non_format_names, NULL, + audio_non_format_cap | video_non_format_cap | text_non_format_cap, 0, 0), + ast_rtp_lookup_mime_multiple2(joint_non_format_names, NULL, joint_non_format_cap, 0, 0)); + } + + /* When UDPTL is negotiated it is expected that there are no compatible codecs as audio or video is not being + * transported, thus we continue in this function further up if that is the case. If we receive an SDP answer + * containing both a UDPTL stream and another media stream however we need to check again to ensure that there + * is at least one joint codec instead of assuming there is one */ + if ((audio_port > 0 || video_port > 0 || text_port > 0) && ast_format_cap_count(joint_format_cap)) { + /* We are now ready to change the sip session and RTP structures with the offered codecs, since they + * are acceptable */ + RAII_VAR(struct ast_format *, format, NULL, ao2_cleanup); + unsigned int framing; + + /* Our joint codec profile for this call */ + ast_format_cap_remove_by_type(dialog->joint_format_cap, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(dialog->joint_format_cap, joint_format_cap, AST_MEDIA_TYPE_UNKNOWN); + dialog->joint_non_format_cap = joint_non_format_cap; /* DTMF capabilities */ + + /* The other side's capability in latest offer */ + ast_format_cap_remove_by_type(dialog->remote_format_cap, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(dialog->remote_format_cap, format_cap, AST_MEDIA_TYPE_UNKNOWN); + + format = ast_format_cap_get_format(dialog->joint_format_cap, 0); + framing = ast_format_cap_get_format_framing(dialog->joint_format_cap, format); + + /* Respond with single most preferred joint codec, limiting the other side's choice */ + if (dialog->peer->preferred_codec_only) { + ast_format_cap_remove_by_type(dialog->joint_format_cap, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append(dialog->joint_format_cap, format, framing); + } + + if (!ast_rtp_codecs_get_framing(&audio_codecs)) { + /* Peer did not force us to use a specific framing, so use our own */ + ast_rtp_codecs_set_framing(&audio_codecs, framing); + } + } + + /* Setup audio address and port */ + if (dialog->audio_rtp) { + if (!ast_sockaddr_isnull(&audio_address) && audio_port > 0) { + /* Start ICE negotiation here, only when it is response, and setting that we are conrolling + * agent, as we are offerer */ + sip_sdp_change_ice_rtcp_mux(dialog, dialog->audio_rtp, rtcp_mux_audio); + sip_sdp_start_ice(dialog->audio_rtp, message->response); + + ast_sockaddr_set_port(&audio_address, audio_port); + ast_rtp_instance_set_remote_address(dialog->audio_rtp, &audio_address); + + ast_debug(1, "Peer audio RTP is at port %s\n", ast_sockaddr_stringify(&audio_address)); + + ast_rtp_codecs_payloads_copy(&audio_codecs, + ast_rtp_instance_get_codecs(dialog->audio_rtp), dialog->audio_rtp); + /* Ensure RTCP is enabled since it may be inactive if we're coming back from a T.38 session */ + sip_sdp_set_rtcp_mux(dialog, dialog->audio_rtp, SIP_AUDIO_RTCP_FD, rtcp_mux_audio); + } else if (image_port > 0) { + ast_debug(1, "Got T.38 Re-invite without audio. Keeping RTP active during T.38 session\n"); + + /* Force media to go through us for T.38 */ + ast_sockaddr_setnull(&dialog->audio_redirect_address); + + /* Prevent audio RTCP reads */ + if (dialog->channel) { + ast_channel_set_fd(dialog->channel, SIP_AUDIO_RTCP_FD, -1); + } + + /* Silence RTCP while audio RTP is inactive */ + ast_rtp_instance_set_prop(dialog->audio_rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_DISABLED); + } else { + ast_rtp_instance_stop(dialog->audio_rtp); + ast_debug(1, "Peer doesn't provide audio\n"); + } + } + + /* Setup video address and port */ + if (dialog->video_rtp) { + if (!ast_sockaddr_isnull(&video_address) && video_port > 0) { + sip_sdp_change_ice_rtcp_mux(dialog, dialog->video_rtp, rtcp_mux_video); + sip_sdp_start_ice(dialog->video_rtp, message->response); + + ast_sockaddr_set_port(&video_address, video_port); + ast_rtp_instance_set_remote_address(dialog->video_rtp, &video_address); + + ast_debug(1, "Peer video RTP is at port %s\n", ast_sockaddr_stringify(&video_address)); + + ast_rtp_codecs_payloads_copy(&video_codecs, + ast_rtp_instance_get_codecs(dialog->video_rtp), dialog->video_rtp); + sip_sdp_set_rtcp_mux(dialog, dialog->video_rtp, SIP_VIDEO_RTCP_FD, rtcp_mux_video); + } else { + ast_rtp_instance_stop(dialog->video_rtp); + ast_debug(1, "Peer doesn't provide video\n"); + } + } + + /* Setup text address and port */ + if (dialog->text_rtp) { + if (!ast_sockaddr_isnull(&text_address) && text_port > 0) { + sip_sdp_start_ice(dialog->text_rtp, message->response); + + ast_sockaddr_set_port(&text_address, text_port); + ast_rtp_instance_set_remote_address(dialog->text_rtp, &text_address); + + ast_debug(1, "Peer T.140 RTP is at port %s\n", ast_sockaddr_stringify(&text_address)); + + if (ast_format_cap_iscompatible_format(dialog->joint_format_cap, ast_format_t140_red) != AST_FORMAT_CMP_NOT_EQUAL) { + dialog->fax_red = TRUE; + ast_rtp_red_init(dialog->text_rtp, 300, red_data_pt, 2); + } else { + dialog->fax_red = FALSE; + } + + ast_rtp_codecs_payloads_copy(&text_codecs, + ast_rtp_instance_get_codecs(dialog->text_rtp), dialog->text_rtp); + } else { + ast_rtp_instance_stop(dialog->text_rtp); + ast_debug(1, "Peer doesn't provide T.140\n"); + } + } + + /* Setup image address and port */ + if (dialog->udptl) { + if (!ast_sockaddr_isnull(&image_address) && image_port > 0) { + if (dialog->nat_rtp && dialog->peer->udptl_nat) { + ast_rtp_instance_get_remote_address(dialog->audio_rtp, &image_address); + + if (!ast_sockaddr_isnull(&image_address)) { + ast_debug(1, "Peer T.38 UDPTL is set behind NAT and with destination, destination address now %s\n", + ast_sockaddr_stringify(&image_address)); + } + } + + ast_sockaddr_set_port(&image_address, image_port); + ast_udptl_set_peer(dialog->udptl, &image_address); + + ast_debug(1, "Peer T.38 UDPTL is at port %s\n", ast_sockaddr_stringify(&image_address)); + + /* Verify the far max ifp can be calculated. this requires far max datagram to be set */ + if (!ast_udptl_get_far_max_datagram(dialog->udptl)) { + /* Setting to zero will force a default if none was provided by the SDP */ + ast_udptl_set_far_max_datagram(dialog->udptl, 0); + } + + /* Remote party offers T38, we need to update state */ + if (add_image == SIP_SDP_FAX_ACCEPT && dialog->fax_state == SIP_FAX_LOCAL_REINVITE) { + sip_fax_set_state(dialog, SIP_FAX_ENABLED); + } else if (add_image == SIP_SDP_FAX_INITIATE && dialog->channel && dialog->last_invite_cseq) { + /* T38 Offered in re-invite from remote party */ + sip_fax_set_state(dialog, SIP_FAX_REMOTE_REINVITE); + + /* If fax detection is enabled then send us off to the fax extension */ + if (dialog->peer->fax_detect & SIP_FAX_DETECT_T38) { + ast_channel_lock(dialog->channel); + + if (strcmp(ast_channel_exten(dialog->channel), "fax")) { + const char *context, *caller_number; + + context = ast_channel_context(dialog->channel); + + if (ast_channel_caller(dialog->channel)->id.number.valid) { + caller_number = ast_channel_caller(dialog->channel)->id.number.str; + } else { + caller_number = NULL; + } + + ast_channel_unlock(dialog->channel); + + if (ast_exists_extension(dialog->channel, context, "fax", 1, caller_number)) { + ast_verb(2, "Redirecting '%s' to fax extension due to peer T.38 re-INVITE\n", + ast_channel_name(dialog->channel)); + pbx_builtin_setvar_helper(dialog->channel, "FAXEXTEN", + ast_channel_exten(dialog->channel)); + + if (ast_async_goto(dialog->channel, context, "fax", 1)) { + ast_log(LOG_NOTICE, "Failed to async goto '%s' into fax of '%s'\n", + ast_channel_name(dialog->channel), context); + } + } else { + ast_log(LOG_NOTICE, "T.38 re-INVITE detected but no fax extension\n"); + } + } else { + ast_channel_unlock(dialog->channel); + } + } + } + } else { + sip_fax_set_state(dialog, SIP_FAX_DISABLED); + ast_udptl_stop(dialog->udptl); + + ast_debug(1, "Peer doesn't provide T.38 UDPTL\n"); + } + } + + if (audio_port == 0 && dialog->fax_state != SIP_FAX_DISABLED && dialog->fax_state != SIP_FAX_REJECTED) { + ast_debug(3, "Have T.38 but no audio, accepting offer anyway\n"); + res = 0; + goto cleanup; + } + + /* Ok, we're going with this offer */ + format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + ast_debug(2, "We're settling with these formats: %s\n", + ast_format_cap_get_names(dialog->joint_format_cap, &format_names)); + + /* There's no open channel owning us so we can return here. For a re-invite or so, we proceed */ + if (!dialog->channel) { + res = 0; + goto cleanup; + } + + ast_debug(4, "We have an owner, now see if we need to change this call\n"); + + if (ast_format_cap_has_type(dialog->joint_format_cap, AST_MEDIA_TYPE_AUDIO)) { + RAII_VAR(struct ast_format_cap *, format_cap, NULL, ao2_cleanup); + unsigned int framing; + + if (message->debug) { + struct ast_str *format_names, *joint_format_names; + + format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + joint_format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + + ast_debug(1, "Setting native-formats after processing SDP. peer joint formats %s, old native-formats %s\n", + ast_format_cap_get_names(dialog->joint_format_cap, &joint_format_names), + ast_format_cap_get_names(ast_channel_nativeformats(dialog->channel), &format_names)); + } + + if ((format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + RAII_VAR(struct ast_format *, format, NULL, ao2_cleanup); + + format = ast_format_cap_get_format(dialog->joint_format_cap, 0); + framing = ast_format_cap_get_format_framing(dialog->joint_format_cap, format); + + ast_format_cap_append(format_cap, format, framing); + ast_format_cap_append_from_cap(format_cap, video_format_cap, AST_MEDIA_TYPE_VIDEO); + ast_format_cap_append_from_cap(format_cap, text_format_cap, AST_MEDIA_TYPE_TEXT); + + ast_channel_nativeformats_set(dialog->channel, format_cap); + } + + ast_set_read_format(dialog->channel, ast_channel_readformat(dialog->channel)); + ast_set_write_format(dialog->channel, ast_channel_writeformat(dialog->channel)); + } + + if (dialog->onhold && + (!ast_sockaddr_isnull(&audio_address) || !ast_sockaddr_isnull(&video_address) || + !ast_sockaddr_isnull(&text_address) || !ast_sockaddr_isnull(&image_address)) && + (mode == SIP_SDP_MODE_SENDRECV || mode == SIP_SDP_MODE_UNKNOWN)) { + ast_queue_unhold(dialog->channel); + + /* Activate a re-invite */ + ast_queue_frame(dialog->channel, &ast_null_frame); + sip_dialog_change_onhold(dialog, SIP_ONHOLD_SENDRECV); + } else if (((ast_sockaddr_isnull(&audio_address) || ast_sockaddr_is_any(&audio_address)) && + (ast_sockaddr_isnull(&video_address) || ast_sockaddr_is_any(&video_address)) && + (ast_sockaddr_isnull(&text_address) || ast_sockaddr_is_any(&text_address)) && + (ast_sockaddr_isnull(&image_address) || ast_sockaddr_is_any(&image_address))) || + (mode == SIP_SDP_MODE_SENDONLY || mode == SIP_SDP_MODE_INACTIVE)) { + ast_queue_hold(dialog->channel, dialog->peer->moh_suggest); + ast_rtp_instance_stop(dialog->audio_rtp); + + /* Activate a re-invite, RTCP needs to go ahead, even if we're on hold */ + ast_queue_frame(dialog->channel, &ast_null_frame); + sip_dialog_change_onhold(dialog, mode == SIP_SDP_MODE_INACTIVE ? SIP_ONHOLD_INACTIVE : SIP_ONHOLD_RECVONLY); + } + + res = 0; + +cleanup: + if (res) { + sip_sdp_media_destroy(dialog); + } + + ast_rtp_codecs_payloads_destroy(&audio_codecs); + ast_rtp_codecs_payloads_destroy(&video_codecs); + ast_rtp_codecs_payloads_destroy(&text_codecs); + return res; +} + +static int sip_sdp_parse_o(const char *o_line, struct sip_dialog *dialog) +{ + int version, id; + char username[32], protocol[8], address[64], unique[128]; + + /* Store the SDP version number of remote UA. This will allow us to distinguish between session modifications + * and session refreshes. If the remote UA does not send an incremented SDP version number in a subsequent + * RE-INVITE then that means its not changing media session. The RE-INVITE may have been sent to update + * connected party, remote target or to refresh the session (Session-Timers). Asterisk must not change media + * session and increment its own version number in answer SDP in this case */ + dialog->sdp_changed = TRUE; + + /* o= */ + if (sscanf(o_line, "o=%31s %30d %30d IN %7s %63s", username, &id, &version, protocol, address) != 5) { + ast_log(LOG_WARNING, "Invalid o= line '%s'\n", o_line); + return FALSE; + } + + /* Copy all after session-version on top of session-version into unique. is a numeric string such + * that the tuple of , , , , and forms a globally + * unique identifier for the session. ie: all except the */ + snprintf(unique, sizeof(unique), "%s %d IN %s %s", username, id, protocol, address); + + /* We need to check the SDP version number the other end sent us; our rules for deciding what to accept are a + * bit complex. + * 1) if 'ignoresdpversion' has been set for this dialog, then we will just accept whatever they sent and assume + * it is a modification of the session, even if it is not + * 2) otherwise, if this is the first SDP we've seen from them + * we accept it; + * note that _them_ may change, in which case the sdp_unique will be different + * 3) otherwise, if the new SDP version number is higher than the old one, we accept it + * 4) otherwise, if this SDP is in response to us requesting a switch to T.38, we accept the SDP, but also + * generate a warning + * message that this peer should have the 'ignoresdpversion' option set, because it is not following the SDP + * offer/answer RFC; if we did not request a switch to T.38, then we stop parsing the SDP, as it has not + * changed from the previous version */ + if (ast_strlen_zero(dialog->sdp_unique)) { + ast_debug(1, "Got SDP version %d and unique parts '%s'\n", version, unique); + } else { + ast_debug(1, "Comparing SDP version %d to %d and unique '%s' to '%s'\n", + dialog->sdp_remote_version, version, dialog->sdp_unique, unique); + } + + if (version < dialog->sdp_remote_version && !strcmp(unique, dialog->sdp_unique)) { + dialog->sdp_changed = FALSE; + ast_debug(2, "Call '%s' responded to our reinvite without changing SDP version; ignoring SDP\n", + dialog->call_id); + return FALSE; + } + + dialog->sdp_remote_version = version; + ast_string_field_set(dialog, sdp_unique, unique); + return TRUE; +} + +static int sip_sdp_parse_c(const char *c_line, struct ast_sockaddr *address) +{ + char protocol[8], host[256]; + int family; + + if (sscanf(c_line, "c=IN %7s %255s", protocol, host) == 2) { + if (!strcmp("IP4", protocol)) { + family = AST_AF_INET; + } else if (!strcmp("IP6", protocol)) { + family = AST_AF_INET6; + } else { + ast_log(LOG_WARNING, "Unknown protocol '%s'\n", protocol); + return FALSE; + } + + if (ast_sockaddr_resolve_first_af(address, host, 0, family)) { + ast_log(LOG_WARNING, "Unable to lookup RTP host in c= line '%s'\n", c_line); + return FALSE; + } + } else { + ast_log(LOG_WARNING, "Invalid host in c= line '%s'\n", c_line); + return FALSE; + } + + return TRUE; +} + +static int sip_sdp_parse_m_audio(const char *m_line, struct sip_dialog *dialog, struct ast_rtp_codecs *audio_codecs, + int *audio_port, int *secure_audio) +{ + int codec, codecs_len, port, ports; + char protocol[32]; + const char *codecs; + + if (sscanf(m_line, "m=audio %30u %31s %n", &port, protocol, &codecs_len) == 2 || + sscanf(m_line, "m=audio %30u/%30u %31s %n", &port, &ports, protocol, &codecs_len) == 3) { + codecs = m_line + codecs_len; + + if (port == 0) { + ast_debug(1, "Ignoring audio media offer because port number is zero\n"); + return FALSE; + } + + /* Check number of ports offered for stream */ + if (ports > 1) { + ast_debug(1, "%u ports offered for video stream. Not supported, will try anyway\n", ports); + } + + if (!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "RTP/SAVPF")) { + *secure_audio = TRUE; + } else if (strcmp(protocol, "RTP/AVP") && strcmp(protocol, "RTP/AVPF")) { + ast_log(LOG_WARNING, "Unknown RTP profile in SDP audio: %s\n", m_line); + return FALSE; + } + + /* Scan through the RTP payload types specified in a "m=" line: */ + for (; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + codecs_len)) { + if (sscanf(codecs, "%30u%n", &codec, &codecs_len) != 1) { + ast_log(LOG_WARNING, "Invalid syntax in RTP audio format list: %s\n", codecs); + return FALSE; + } + + ast_debug(1, "Found RTP audio format %u\n", codec); + ast_rtp_codecs_payloads_set_m_type(audio_codecs, NULL, codec); + } + + *audio_port = port; + } else { + ast_log(LOG_WARNING, "Rejecting audio media offer due to invalid or unsupported syntax: %s\n", m_line); + return FALSE; + } + + return TRUE; +} + +static int sip_sdp_parse_a_audio(const char *a_line, struct sip_dialog *dialog, struct ast_rtp_codecs *audio_rtp, + int *audio_codec, int *codec_count) +{ + unsigned int codec, rate, ptime; + char type[128], fmtp[256]; + + if (sscanf(a_line, "a=ptime:%30d", &ptime) == 1) { + if (ptime && dialog->peer->auto_framing) { + ast_debug(1, "Setting framing to %d\n", ptime); + + ast_format_cap_set_framing(dialog->format_cap, ptime); + ast_rtp_codecs_set_framing(audio_rtp, ptime); + } + } else if (sscanf(a_line, "a=rtpmap:%30u %127[^/]/%30u", &codec, type, &rate) == 3) { + /* We have a rtpmap to handle */ + if (*codec_count < SIP_SDP_MAX_CODECS) { + if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(audio_rtp, NULL, codec, "audio", type, 0, rate))) { + ast_debug(1, "Found audio description format %s for ID %u\n", type, codec); + + *audio_codec = codec; + (*codec_count)++; + } else { + ast_rtp_codecs_payloads_unset(audio_rtp, NULL, codec); + ast_debug(1, "Found unknown media description format %s for ID %u\n", type, codec); + return FALSE; + } + } else { + ast_debug(1, "Discarded description format %s for ID %u\n", type, codec); + return FALSE; + } + } else if (sscanf(a_line, "a=fmtp:%30u %255[^\t\n]", &codec, fmtp) == 2) { + RAII_VAR(struct ast_format *, format, NULL, ao2_cleanup); + + if ((format = ast_rtp_codecs_get_payload_format(audio_rtp, codec))) { + RAII_VAR(struct ast_format *, new_format, NULL, ao2_cleanup); + + if ((new_format = ast_format_parse_sdp_fmtp(format, fmtp))) { + ast_rtp_codecs_payload_replace_format(audio_rtp, codec, new_format); + ao2_replace(format, new_format); + } else { + ast_rtp_codecs_payloads_unset(audio_rtp, NULL, codec); + return FALSE; + } + + if (ast_format_cmp(format, ast_format_g719) == AST_FORMAT_CMP_EQUAL) { + unsigned int bitrate; + + if (sscanf(fmtp, "bitrate=%30u", &bitrate) == 1 && bitrate != 64000) { + ast_log(LOG_WARNING, "Got G.719 offer at %u bps, but only 64000 bps supported; ignoring\n", + bitrate); + ast_rtp_codecs_payloads_unset(audio_rtp, NULL, codec); + return FALSE; + } + } + } + } else { + return FALSE; + } + + return TRUE; +} + +static int sip_sdp_parse_m_video(const char *m_line, struct sip_dialog *dialog, struct ast_rtp_codecs *video_codecs, + int *video_port, int *secure_video) +{ + int codec, codecs_len, port, ports; + char protocol[32]; + const char *codecs; + + if (sscanf(m_line, "m=video %30u/%30u %31s %n", &port, &ports, protocol, &codecs_len) == 3 || + sscanf(m_line, "m=video %30u %31s %n", &port, protocol, &codecs_len) == 2) { + codecs = m_line + codecs_len; + + if (port == 0) { + ast_debug(1, "Ignoring video stream offer because port number is zero\n"); + return FALSE; + } + + /* Check number of ports offered for stream */ + if (ports > 1) { + ast_debug(1, "%u ports offered for video stream. Not supported, will try anyway\n", ports); + } + + if (!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "RTP/SAVPF")) { + *secure_video = TRUE; + } else if (strcmp(protocol, "RTP/AVP") && strcmp(protocol, "RTP/AVPF")) { + ast_log(LOG_WARNING, "Unknown RTP profile in SDP video: %s\n", m_line); + return FALSE; + } + + /* Scan through the RTP payload types specified in a "m=" line: */ + for (; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + codecs_len)) { + if (sscanf(codecs, "%30u%n", &codec, &codecs_len) != 1) { + ast_log(LOG_WARNING, "Invalid syntax in RTP video format list: %s\n", codecs); + return FALSE; + } + + ast_debug(1, "Found RTP video format %u\n", codec); + ast_rtp_codecs_payloads_set_m_type(video_codecs, NULL, codec); + } + + *video_port = port; + dialog->no_video_support = FALSE; + } else { + ast_log(LOG_WARNING, "Rejecting video media offer due to invalid or unsupported syntax: %s\n", m_line); + return FALSE; + } + + return TRUE; +} + +static int sip_sdp_parse_a_video(const char *a_line, struct sip_dialog *dialog, struct ast_rtp_codecs *video_codecs, + int *video_codec, int *codec_count) +{ + unsigned int codec, rate; + char type[128], fmtp[256], imageattr[256]; + + if (sscanf(a_line, "a=rtpmap:%30u %127[^/]/%30u", &codec, type, &rate) == 3) { + /* We have a rtpmap to handle */ + if (*codec_count < SIP_SDP_MAX_CODECS) { + /* Note: should really look at the '#chans' params too */ + if (!strncmp(type, "H26", 3) || !strncmp(type, "MP4", 3) || !strncmp(type, "VP8", 3)) { + if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(video_codecs, NULL, codec, "video", type, 0, rate))) { + ast_debug(1, "Found video description format %s for ID %u\n", type, codec); + + *video_codec = codec; + (*codec_count)++; + } else { + ast_rtp_codecs_payloads_unset(video_codecs, NULL, codec); + ast_debug(1, "Found unknown media description format %s for codec %u\n", + type, codec); + return FALSE; + } + } + } else { + ast_debug(1, "Discarded description format %s for codec %u\n", type, codec); + return FALSE; + } + } else if (sscanf(a_line, "a=fmtp:%30u %255[^\t\n]", &codec, fmtp) == 2) { + RAII_VAR(struct ast_format *, format, NULL, ao2_cleanup); + + if ((format = ast_rtp_codecs_get_payload_format(video_codecs, codec))) { + RAII_VAR(struct ast_format *, new_format, NULL, ao2_cleanup); + + if ((new_format = ast_format_parse_sdp_fmtp(format, fmtp))) { + ast_rtp_codecs_payload_replace_format(video_codecs, codec, new_format); + ao2_replace(format, new_format); + } else { + ast_rtp_codecs_payloads_unset(video_codecs, NULL, codec); + return FALSE; + } + } + } else if (sscanf(a_line, "a=imageattr:%30u recv %255[^\t\n]", &codec, imageattr) == 2 || + (codec = *video_codec, sscanf(a_line, "a=imageattr:* recv %255[^\t\n]", imageattr) == 1)) { + RAII_VAR(struct ast_format *, format, NULL, ao2_cleanup); + + if ((format = ast_rtp_codecs_get_payload_format(video_codecs, codec))) { + RAII_VAR(struct ast_format *, new_format, NULL, ao2_cleanup); + + if ((new_format = ast_format_attribute_set(format, "imageattr", imageattr))) { + ast_rtp_codecs_payload_replace_format(video_codecs, codec, new_format); + ao2_replace(format, new_format); + } else { + ast_rtp_codecs_payloads_unset(video_codecs, NULL, codec); + } + } + } else { + return FALSE; + } + + return TRUE; +} + +static int sip_sdp_parse_m_text(const char *m_line, struct sip_dialog *dialog, struct ast_rtp_codecs *text_codecs, + int *text_port, int *secure_text) +{ + int codec, codecs_len, port, ports; + char protocol[32]; + const char *codecs; + + if (sscanf(m_line, "m=text %30u/%30u %31s %n", &port, &ports, protocol, &codecs_len) == 3 || + sscanf(m_line, "m=text %30u %31s %n", &port, protocol, &codecs_len) == 2) { + codecs = m_line + codecs_len; + + if (port == 0) { + ast_debug(1, "Ignoring text stream offer because port number is zero\n"); + return FALSE; + } + + /* Check number of ports offered for stream */ + if (ports > 1) { + ast_debug(1, "%u ports offered for text stream. Not supported, will try anyway\n", ports); + } + + if (!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "RTP/SAVPF")) { + *secure_text = TRUE; + } else if (strcmp(protocol, "RTP/AVP") && strcmp(protocol, "RTP/AVPF")) { + ast_log(LOG_WARNING, "Unknown RTP profile in SDP text: %s\n", m_line); + } + + /* Scan through the RTP payload types specified in a "m=" line: */ + for (; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + codecs_len)) { + if (sscanf(codecs, "%30u%n", &codec, &codecs_len) != 1) { + ast_log(LOG_WARNING, "Invalid syntax in RTP text format list: %s\n", codecs); + return FALSE; + } + + ast_debug(1, "Found RTP text format %u\n", codec); + ast_rtp_codecs_payloads_set_m_type(text_codecs, NULL, codec); + } + + *text_port = port; + dialog->no_text_support = FALSE; + } else { + ast_log(LOG_WARNING, "Rejecting text stream due to invalid or unsupported syntax: %s\n", m_line); + return FALSE; + } + + return TRUE; +} + +static int sip_sdp_parse_a_text(const char *a_line, struct sip_dialog *dialog, struct ast_rtp_codecs *text_codecs, + int *codec_count, char *red_fmtp, int *red_generation, int *red_data_pt) +{ + unsigned int codec, rate; + char type[128]; + + if (sscanf(a_line, "a=rtpmap:%30u %127[^/]/%30u", &codec, type, &rate) == 3) { + /* We have a rtpmap to handle */ + if (*codec_count < SIP_SDP_MAX_CODECS) { + if (!strncmp(type, "T140", 4)) { /* Text */ + if (dialog->text_rtp) { + ast_rtp_codecs_payloads_set_rtpmap_type_rate(text_codecs, NULL, codec, "text", + type, 0, rate); + } + } else if (!strncmp(type, "RED", 3)) { /* Text with Redudancy */ + if (dialog->text_rtp) { + ast_rtp_codecs_payloads_set_rtpmap_type_rate(text_codecs, NULL, codec, "text", + type, 0, rate); + snprintf(red_fmtp, 16, "a=fmtp:%u ", codec); + + ast_debug(1, "RED submimetype has payload type: %u\n", codec); + } + } + + (*codec_count)++; + } else { + ast_debug(1, "Discarded description format %s for ID %u\n", type, codec); + return FALSE; + } + } else if (!strncmp(a_line + 7, red_fmtp, strlen(red_fmtp))) { + char *red_pt, *red_pts; + + /* Count numbers of generations in fmtp */ + red_pts = ast_strdupa(a_line + strlen(red_fmtp)); + + do { + if ((red_pt = strsep(&red_pts, "/")) && + sscanf(red_pt, "%30u", (unsigned int *) &red_data_pt[*red_generation]) == 1) { + (*red_generation)++; + } + } while (red_pt && *red_generation < AST_RED_MAX_GENERATION); + } else { + return FALSE; + } + + return TRUE; +} + +static int sip_sdp_parse_m_image(const char *m_line, struct sip_dialog *dialog, int *image_port) +{ + int port; + + if (sscanf(m_line, "m=image %30u udptl t38", &port) == 1) { + if (port == 0) { + ast_debug(1, "Ignoring image stream offer because port number is zero\n"); + return FALSE; + } + + if (sip_fax_alloc(dialog)) { + return FALSE; + } + + if (dialog->fax_state != SIP_FAX_ENABLED) { + memset(&dialog->fax_remote_config, 0, sizeof(dialog->fax_remote_config)); + /* Default EC to none, the remote end should respond with the EC they want to use */ + ast_udptl_set_error_correction_scheme(dialog->udptl, UDPTL_ERROR_CORRECTION_NONE); + } + + *image_port = port; + } else { + ast_log(LOG_WARNING, "Rejecting image media offer due to invalid or unsupported syntax: %s\n", m_line); + return FALSE; + } + + return TRUE; +} + +static int sip_sdp_parse_a_image(const char *a_line, struct sip_dialog *dialog) +{ + unsigned int max_size, max_datagram, bitrate, fill_bit_removal, transcoding_mmr, transcoding_jbig, version; + + if (sip_fax_alloc(dialog)) { + return FALSE; + } + + if (sscanf(a_line, "a=T38FaxMaxBuffer:%30u", &max_size) == 1) { + return TRUE; + } else if (sscanf(a_line, "a=T38MaxBitrate:%30u", &bitrate) == 1) { + ast_debug(3, "T38MaxBitRate: %u\n", bitrate); + + switch (bitrate) { + case 14400: + dialog->fax_remote_config.rate = AST_T38_RATE_14400; + break; + case 12000: + dialog->fax_remote_config.rate = AST_T38_RATE_12000; + break; + case 9600: + dialog->fax_remote_config.rate = AST_T38_RATE_9600; + break; + case 7200: + dialog->fax_remote_config.rate = AST_T38_RATE_7200; + break; + case 4800: + dialog->fax_remote_config.rate = AST_T38_RATE_4800; + break; + case 2400: + dialog->fax_remote_config.rate = AST_T38_RATE_2400; + break; + } + } else if (sscanf(a_line, "a=T38FaxVersion:%30u", &version) == 1) { + dialog->fax_remote_config.version = version; + } else if (sscanf(a_line, "a=T38FaxMaxDatagram:%30u", &max_datagram) == 1) { + ast_udptl_set_far_max_datagram(dialog->udptl, max_datagram); + } else if (sscanf(a_line, "a=T38FaxFillBitRemoval:%30u", &fill_bit_removal) == 1) { + dialog->fax_remote_config.fill_bit_removal = fill_bit_removal; + } else if (sscanf(a_line, "a=T38FaxTranscodingMMR:%30u", &transcoding_mmr) == 1) { + dialog->fax_remote_config.transcoding_mmr = transcoding_mmr; + } else if (sscanf(a_line, "a=T38FaxTranscodingJBIG:%30u", &transcoding_jbig) == 1) { + dialog->fax_remote_config.transcoding_jbig = transcoding_jbig; + } else if (!strncmp(a_line, "a=T38FaxRateManagement:", 24)) { + if (!strcmp(a_line + 24, "localTCF")) { + dialog->fax_remote_config.rate_management = AST_T38_RATE_MANAGEMENT_LOCAL_TCF; + } else if (!strcmp(a_line + 24, "transferredTCF")) { + dialog->fax_remote_config.rate_management = AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF; + } + } else if (!strncmp(a_line, "a=T38FaxTdpEC:", 15)) { + if (!strcmp(a_line + 15, "t38UDPRedundancy")) { + ast_udptl_set_error_correction_scheme(dialog->udptl, UDPTL_ERROR_CORRECTION_REDUNDANCY); + } else if (!strcmp(a_line + 15, "t38UDPFEC")) { + ast_udptl_set_error_correction_scheme(dialog->udptl, UDPTL_ERROR_CORRECTION_FEC); + } else { + ast_udptl_set_error_correction_scheme(dialog->udptl, UDPTL_ERROR_CORRECTION_NONE); + } + } else { + return FALSE; + } + + return TRUE; +} + +static int sip_sdp_parse_a_mode(const char *a_line, int *mode) +{ + if (!strcmp(a_line, "a=sendrecv")) { + if (*mode == SIP_SDP_MODE_UNKNOWN) { + *mode = SIP_SDP_MODE_SENDRECV; + } + } else if (!strcmp(a_line, "a=sendonly")) { + if (*mode == SIP_SDP_MODE_UNKNOWN) { + *mode = SIP_SDP_MODE_SENDONLY; + } + } else if (!strcmp(a_line, "a=inactive")) { + if (*mode == SIP_SDP_MODE_UNKNOWN) { + *mode = SIP_SDP_MODE_INACTIVE; + } + } else { + return FALSE; + } + + return TRUE; +} + +static int sip_sdp_parse_a_crypto(const char *a_line, struct sip_dialog *dialog, struct ast_rtp_instance *rtp, + struct ast_sdp_srtp **sdp_srtp) +{ + /* If no RTP instance exists for this media stream don't bother processing the crypto line */ + if (!rtp) { + ast_debug(3, "Received offer with crypto line for media stream that is not enabled\n"); + return FALSE; + } + + if (strncmp(a_line, "a=crypto:", 9)) { + return FALSE; + } + + if (!*sdp_srtp) { + if (dialog->outgoing) { + ast_log(LOG_WARNING, "Ignoring unexpected crypto attribute in SDP answer\n"); + return FALSE; + } + + if (!(*sdp_srtp = ast_sdp_srtp_alloc())) { + return FALSE; + } + } + + if (!(*sdp_srtp)->crypto && !((*sdp_srtp)->crypto = ast_sdp_crypto_alloc())) { + return FALSE; + } + + /* Skip "crypto:" */ + if (ast_sdp_crypto_process(rtp, *sdp_srtp, a_line + 9) < 0) { + return FALSE; + } + + return TRUE; +} + +static int sip_sdp_parse_a_ice(const char *a_line, struct sip_dialog *dialog, struct ast_rtp_instance *rtp, int rtcp_mux) +{ + struct ast_rtp_engine_ice *ice; + char ufrag[256], pwd[256], foundation[32], transport[8], address[64], type[8], relay_address[64]; + struct ast_rtp_engine_ice_candidate candidate; + unsigned int port, relay_port; + + if (!rtp || !(ice = ast_rtp_instance_get_ice(rtp))) { + return FALSE; + } + + relay_address[0] = '\0'; + relay_port = 0; + memset(&candidate, 0, sizeof(candidate)); + + if (sscanf(a_line, "a=ice-ufrag:%255s", ufrag) == 1) { + ice->set_authentication(rtp, ufrag, NULL); + } else if (sscanf(a_line, "a=ice-pwd:%255s", pwd) == 1) { + ice->set_authentication(rtp, NULL, pwd); + } else if (sscanf(a_line, "a=candidate:%31s %30u %7s %30u %63s %30u typ %7s %*s %63s %*s %30u", + foundation, &candidate.id, transport, (unsigned int *) &candidate.priority, address, &port, + type, relay_address, &relay_port) >= 7) { + if (rtcp_mux && dialog->peer->rtcp_mux && candidate.id > 1) { + /* If we support RTCP-MUX and they offered it, don't consider RTCP candidates */ + return TRUE; + } + + candidate.foundation = foundation; + candidate.transport = transport; + + ast_sockaddr_parse(&candidate.address, address, PARSE_PORT_FORBID); + ast_sockaddr_set_port(&candidate.address, port); + + if (!strcmp(type, "host")) { + candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_HOST; + } else if (!strcmp(type, "srflx")) { + candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_SRFLX; + } else if (!strcmp(type, "relay")) { + candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_RELAYED; + } else { + return FALSE; + } + + 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(rtp, &candidate); + } else if (!strcmp(a_line, "a=ice-lite")) { + ice->ice_lite(rtp); + } else { + return FALSE; + } + + return TRUE; +} + +static int sip_sdp_parse_a_rtcp_mux(const char *a_line, struct sip_dialog *dialog, int *rtcp_mux) +{ + if (!strncmp(a_line, "a=rtcp-mux", 10)) { + *rtcp_mux = TRUE; + } else { + return FALSE; + } + + return TRUE; +} + +/* Add Session Description Protocol message. If old_sdp is TRUE, then the SDP version number is not incremented. This + * mechanism is used in Session-Timers where re-INVITEs are used for refreshing SIP sessions without modifying the media + * session in any way */ +int sip_sdp_build(struct sip_dialog *dialog, struct sip_message *message, int old_sdp_version, int add_media, int add_image) +{ + int add_audio, add_video, add_text, direct_media; + struct ast_sockaddr audio_address, video_address, text_address, image_address; + struct ast_str *audio_m_line, *audio_c_line, *video_m_line, *video_c_line, *text_m_line, *text_c_line, + *image_m_line, *image_c_line, *format_names; + RAII_VAR(struct ast_str *, audio_a_lines, NULL, ast_free_ptr); /* Attributes for audio */ + RAII_VAR(struct ast_str *, video_a_lines, NULL, ast_free_ptr); /* Attributes for video */ + RAII_VAR(struct ast_str *, text_a_lines, NULL, ast_free_ptr); /* Attributes for text */ + RAII_VAR(struct ast_str *, image_a_lines, NULL, ast_free_ptr); /* Attributes for image */ + RAII_VAR(struct ast_format_cap *, format_cap, NULL, ao2_cleanup); + RAII_VAR(struct ast_format_cap *, joint_format_cap, NULL, ao2_cleanup); + struct sip_sdp_media *sdp_media; + + if (!dialog->audio_rtp) { + ast_log(LOG_WARNING, "No way to add SDP without an RTP allocated\n"); + return -1; + } + + audio_m_line = ast_str_alloca(256); + audio_c_line = ast_str_alloca(64); + audio_a_lines = ast_str_create(256); + + video_m_line = ast_str_alloca(256); + video_c_line = ast_str_alloca(64); + video_a_lines = ast_str_create(256); + + text_m_line = ast_str_alloca(256); + text_c_line = ast_str_alloca(64); + text_a_lines = ast_str_create(256); + + image_m_line = ast_str_alloca(256); + image_c_line = ast_str_alloca(64); + image_a_lines = ast_str_create(256); + + if (!audio_a_lines || !video_a_lines || !text_a_lines || !image_a_lines) { + return -1; + } + + format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + joint_format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + + if (!format_cap || !joint_format_cap) { + return -1; + } + + format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + + /* FIXME: We should not change properties in the SIP dialog until we have acceptance of the offer if this is a + * re-invite Set RTP Session ID and version */ + if (!dialog->sdp_id) { + dialog->sdp_id = (int) ast_random(); + } else if (!old_sdp_version) { + dialog->sdp_version++; + } + + add_audio = FALSE; + add_video = FALSE; + add_text = FALSE; + direct_media = FALSE; + + if (add_media) { + int codec, non_codec, min_ptime, max_ptime; + const char *mode; + + if (!ast_sockaddr_isnull(&dialog->audio_redirect_address) && + (!ast_format_cap_has_type(dialog->joint_format_cap, AST_MEDIA_TYPE_VIDEO) + || !ast_sockaddr_isnull(&dialog->video_redirect_address)) && + (!ast_format_cap_has_type(dialog->joint_format_cap, AST_MEDIA_TYPE_TEXT) + || !ast_sockaddr_isnull(&dialog->text_redirect_address)) && + ast_format_cap_count(dialog->redirect_format_cap)) { + direct_media = TRUE; + + ast_format_cap_get_compatible(dialog->joint_format_cap, + dialog->redirect_format_cap, joint_format_cap); + + ast_debug(1, "Our native-bridge filtered format capability: %s\n", + ast_format_cap_get_names(joint_format_cap, &format_names)); + } else { + ast_format_cap_append_from_cap(joint_format_cap, dialog->joint_format_cap, AST_MEDIA_TYPE_UNKNOWN); + } + + /* Check if we need_ audio in this call */ + if (ast_format_cap_has_type(joint_format_cap, AST_MEDIA_TYPE_AUDIO)) { + add_audio = TRUE; + } + + /* Check if we need_ video in this call */ + if (ast_format_cap_has_type(joint_format_cap, AST_MEDIA_TYPE_VIDEO) && !dialog->no_video_support) { + if (direct_media && !ast_format_cap_has_type(joint_format_cap, AST_MEDIA_TYPE_VIDEO)) { + ast_debug(2, "This call needs video, but caller did not provide it\n"); + } else if (dialog->video_rtp) { + add_video = TRUE; + + ast_debug(2, "This call needs video\n"); + } else { + ast_debug(2, "This call needs video, but there's no video support enabled\n"); + } + } + + /* Check if we need_ text in this call */ + if (ast_format_cap_has_type(joint_format_cap, AST_MEDIA_TYPE_TEXT) && !dialog->no_text_support) { + if (dialog->text_rtp) { + add_text = TRUE; + + ast_debug(2, "This call needs text\n"); + } else { + ast_debug(2, "This call needs text, but there's no text support enabled\n"); + } + } + + sip_sdp_media_address(dialog, add_audio, &audio_address, add_video, &video_address, add_text, &text_address); + + ast_debug(1, "Our audio capability: %s, video: %s text: %s\n", + ast_format_cap_get_names(joint_format_cap, &format_names), + !dialog->no_video_support ? "yes" : "no", !dialog->no_text_support ? "yes" : "no"); + ast_debug(1, "Our preferred codec: %s\n", + ast_format_cap_get_names(dialog->outgoing_format_cap, &format_names)); + + if (dialog->onhold == SIP_ONHOLD_RECVONLY || dialog->onhold == SIP_ONHOLD_INACTIVE) { + direct_media = FALSE; + } + + if (add_audio) { + char *audio_crypto = sip_sdp_get_a_crypto(dialog->secure_audio_rtp); + + /* We break with the "recommendation" and send our IP, in order that our peer doesn't have to + * gethostbyname() us */ + ast_str_append(&audio_m_line, 0, "m=audio %d %s", ast_sockaddr_port(&audio_address), + ast_sdp_get_rtp_profile(audio_crypto ? TRUE : FALSE, dialog->audio_rtp, FALSE, FALSE)); + + ast_str_append(&audio_c_line, 0, "c=IN %s %s\r\n", + ast_sockaddr_is_ipv6(&audio_address) && !ast_sockaddr_is_ipv4_mapped(&audio_address) ? "IP6" : "IP4", + ast_sockaddr_stringify_addr_remote(&audio_address)); + + if (audio_crypto) { + ast_str_append(&audio_a_lines, 0, "%s", audio_crypto); + ast_free(audio_crypto); + } + + if (!direct_media && dialog->peer->ice_support) { + sip_sdp_add_ice(dialog->audio_rtp, &audio_a_lines); + } + + ast_debug(1, "Audio is at %s\n", ast_sockaddr_stringify_remote(&audio_address)); + } + + /* Ok, we need video. Let's add what we need for video and set codecs. Video is handled differently than + * audio since we can not transcode */ + if (add_video) { + char *video_crypto = sip_sdp_get_a_crypto(dialog->secure_video_rtp); + + ast_str_append(&video_m_line, 0, "m=video %d %s", ast_sockaddr_port(&video_address), + ast_sdp_get_rtp_profile(video_crypto ? TRUE : FALSE, dialog->video_rtp, FALSE, FALSE)); + + ast_str_append(&video_c_line, 0, "c=IN %s %s\r\n", + ast_sockaddr_is_ipv6(&video_address) && !ast_sockaddr_is_ipv4_mapped(&video_address) ? "IP6" : "IP4", + ast_sockaddr_stringify_addr_remote(&video_address)); + + if (video_crypto) { + ast_str_append(&video_a_lines, 0, "%s", video_crypto); + ast_free(video_crypto); + } + + if (!direct_media && dialog->peer->ice_support) { + sip_sdp_add_ice(dialog->video_rtp, &video_a_lines); + } + + ast_debug(1, "Video is at %s\n", ast_sockaddr_stringify_remote(&video_address)); + } + + /* Ok, we need text. Let's add what we need for text and set codecs. Text is handled differently than + * audio since we can not transcode */ + if (add_text) { + char *text_crypto = sip_sdp_get_a_crypto(dialog->secure_text_rtp); + + ast_str_append(&text_m_line, 0, "m=text %d %s", ast_sockaddr_port(&text_address), + ast_sdp_get_rtp_profile(text_crypto ? TRUE : FALSE, dialog->text_rtp, FALSE, FALSE)); + + ast_str_append(&text_c_line, 0, "c=IN %s %s\r\n", + ast_sockaddr_is_ipv6(&text_address) && !ast_sockaddr_is_ipv4_mapped(&text_address) ? "IP6" : "IP4", + ast_sockaddr_stringify_addr_remote(&text_address)); + + if (text_crypto) { + ast_str_append(&text_a_lines, 0, "%s", text_crypto); + ast_free(text_crypto); + } + + if (!direct_media && dialog->peer->ice_support) { + sip_sdp_add_ice(dialog->text_rtp, &text_a_lines); + } + + ast_debug(1, "Text is at %s\n", ast_sockaddr_stringify_remote(&text_address)); + } + + /* Start building generic SDP headers */ + if (dialog->sdp_relay_nearend) { + ast_str_append(&audio_a_lines, 0, "a=label:X-relay-nearend\r\n"); + } else if (dialog->sdp_relay_farend) { + ast_str_append(&audio_a_lines, 0, "a=label:X-relay-farend\r\n"); + } + + min_ptime = 0; + max_ptime = 0; + + /* Now, start adding audio codecs. These are added in this order: + * - First what was requested by the calling channel + * - Then our mutually shared capabilities, determined previous in joint_format_cap + * Unless otherwise configured, the prefcaps is added before the peer's configured codecs */ + if (!dialog->peer->ignore_outgoing_format) { + for (codec = 0; codec < ast_format_cap_count(dialog->outgoing_format_cap); codec++) { + RAII_VAR(struct ast_format *, format, NULL, ao2_cleanup); + + format = ast_format_cap_get_format(dialog->outgoing_format_cap, codec); + + if (ast_format_get_type(format) != AST_MEDIA_TYPE_AUDIO || + ast_format_cap_iscompatible_format(joint_format_cap, format) == AST_FORMAT_CMP_NOT_EQUAL) { + continue; + } + + sip_sdp_add_audio_codec(dialog, format, &audio_m_line, &audio_a_lines, + &min_ptime, &max_ptime); + ast_format_cap_append(format_cap, format, 0); + } + } + + /* Now send any other common codecs */ + for (codec = 0; codec < ast_format_cap_count(joint_format_cap); codec++) { + RAII_VAR(struct ast_format *, format, NULL, ao2_cleanup); + + format = ast_format_cap_get_format(joint_format_cap, codec); + + if (ast_format_cap_iscompatible_format(format_cap, format) != AST_FORMAT_CMP_NOT_EQUAL) { + continue; + } + + if (add_audio && ast_format_get_type(format) == AST_MEDIA_TYPE_AUDIO) { + sip_sdp_add_audio_codec(dialog, format, &audio_m_line, &audio_a_lines, + &min_ptime, &max_ptime); + } else if (add_video && ast_format_get_type(format) == AST_MEDIA_TYPE_VIDEO) { + sip_sdp_add_video_codec(dialog, format, &video_m_line, &video_a_lines); + } else if (add_text && ast_format_get_type(format) == AST_MEDIA_TYPE_TEXT) { + sip_sdp_add_text_codec(dialog, format, &text_m_line, &text_a_lines); + } + + ast_format_cap_append(format_cap, format, 0); + } + + /* Now add DTMF RFC2833 telephony-event as a codec */ + for (non_codec = 1LL; non_codec <= AST_RTP_MAX; non_codec <<= 1) { + if (!(dialog->joint_non_format_cap & non_codec)) { + continue; + } + + sip_sdp_add_non_codec(dialog, non_codec, &audio_m_line, &audio_a_lines); + } + + ast_debug(3, "Done with adding codecs to SDP\n"); + + if (!dialog->channel || ast_channel_timingfd(dialog->channel) == -1) { + ast_str_append(&audio_a_lines, 0, "a=silenceSupp:off - - - -\r\n"); + } + + /* 'ptime' only applies to audio codecs */ + if (min_ptime) { + ast_str_append(&audio_a_lines, 0, "a=ptime:%d\r\n", min_ptime); + } + + if (max_ptime) { + ast_str_append(&audio_a_lines, 0, "a=maxptime:%d\r\n", max_ptime); + } + + if (!dialog->outgoing) { + ast_debug(1, "Setting framing on incoming call: %u\n", min_ptime); + ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(dialog->audio_rtp), min_ptime); + } + + /* If we've got rtcp-mux enabled, just unconditionally offer it in all SDPs */ + if (dialog->peer->rtcp_mux) { + ast_str_append(&audio_a_lines, 0, "a=rtcp-mux\r\n"); + ast_str_append(&video_a_lines, 0, "a=rtcp-mux\r\n"); + } + + /* Start ICE negotiation, and setting that we are controlled agent, as this is response to offer */ + if (!direct_media && dialog->peer->ice_support && !message->response) { + sip_sdp_start_ice(dialog->audio_rtp, message->response); + + if (dialog->video_rtp) { + sip_sdp_start_ice(dialog->video_rtp, message->response); + } + } + + mode = sip_sdp_get_a_mode(dialog); /* Mode is the same for all streams */ + + if (add_audio) { + ast_str_append(&audio_m_line, 0, "\r\n"); + ast_str_append(&audio_a_lines, 0, "%s", mode); + } + + if (add_video) { + ast_str_append(&video_m_line, 0, "\r\n"); + ast_str_append(&video_a_lines, 0, "%s", mode); + } + + if (add_text) { + ast_str_append(&text_m_line, 0, "\r\n"); + ast_str_append(&text_a_lines, 0, "%s", mode); + } + } + + if (add_image) { + unsigned int rate; + const char *rate_management, *error_correction; + + /* Our T.38 end is */ + ast_udptl_get_us(dialog->udptl, &image_address); + + /* We don't use directmedia for T.38, so keep the destination the same as our IP address */ + ast_sockaddr_copy(&image_address, &dialog->our_address); + ast_sockaddr_set_port(&image_address, ast_sockaddr_port(&image_address)); + + ast_debug(1, "T.38 UDPTL is at %s port %d\n", + ast_sockaddr_stringify_addr(&dialog->our_address), ast_sockaddr_port(&image_address)); + + /* We break with the "recommendation" and send our IP, in order that our peer doesn't have + * to ast_gethostbyname() us */ + ast_str_append(&image_m_line, 0, "m=image %d udptl t38\r\n", ast_sockaddr_port(&image_address)); + + ast_str_append(&image_c_line, 0, "c=IN %s %s\r\n", + ast_sockaddr_is_ipv6(&image_address) && !ast_sockaddr_is_ipv4_mapped(&image_address) ? "IP6" : "IP4", + ast_sockaddr_stringify_addr_remote(&image_address)); + + ast_str_append(&image_a_lines, 0, "a=T38FaxVersion:%u\r\n", dialog->fax_config.version); + + switch (dialog->fax_config.rate) { + case AST_T38_RATE_2400: + rate = 2400; + break; + case AST_T38_RATE_4800: + rate = 4800; + break; + case AST_T38_RATE_7200: + rate = 7200; + break; + case AST_T38_RATE_9600: + rate = 9600; + break; + case AST_T38_RATE_12000: + rate = 12000; + break; + case AST_T38_RATE_14400: + rate = 14400; + break; + default: + rate = 0; + break; + } + + switch (dialog->fax_config.rate_management) { + case AST_T38_RATE_MANAGEMENT_LOCAL_TCF: + rate_management = "localTCF"; + break; + case AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF: + rate_management = "transferredTCF"; + break; + default: + rate_management = NULL; + break; + } + + switch (ast_udptl_get_error_correction_scheme(dialog->udptl)) { + case UDPTL_ERROR_CORRECTION_REDUNDANCY: + error_correction = "t38UDPRedundancy"; + break; + case UDPTL_ERROR_CORRECTION_FEC: + error_correction = "t38UDPFEC"; + break; + case UDPTL_ERROR_CORRECTION_NONE: + default: + error_correction = NULL; + break; + } + + ast_str_append(&image_a_lines, 0, "a=T38MaxBitRate:%u\r\n", rate); + ast_str_append(&image_a_lines, 0, "a=T38FaxMaxDatagram:%u\r\n", + ast_udptl_get_local_max_datagram(dialog->udptl)); + + if (!ast_strlen_zero(rate_management)) { + ast_str_append(&image_a_lines, 0, "a=T38FaxRateManagement:%s\r\n", rate_management); + } + + if (!ast_strlen_zero(error_correction)) { + ast_str_append(&image_a_lines, 0, "a=T38FaxUdpEC:%s\r\n", error_correction); + } + + ast_str_append(&image_a_lines, 0, "a=T38FaxFillBitRemoval:%u\r\n", dialog->fax_config.fill_bit_removal); + ast_str_append(&image_a_lines, 0, "a=T38FaxTranscodingMMR:%u\r\n", dialog->fax_config.transcoding_mmr); + ast_str_append(&image_a_lines, 0, "a=T38FaxTranscodingJBIG:%u\r\n", dialog->fax_config.transcoding_jbig); + } + + sip_message_add_header(message, "Content-Type", "application/sdp"); + sip_message_add_content(message, "v=0\r\n"); /* Version */ + + /* SDP owner, We don't use dest here but dialog->our_address because address in o= field must not change in + * reINVITE */ + sip_message_build_content(message, "o=%s %d %d IN %s %s\r\n", + ast_strlen_zero(sip_config.sdp_username) ? "-" : sip_config.sdp_username, + dialog->sdp_id, dialog->sdp_version, + ast_sockaddr_is_ipv6(&dialog->our_address) && !ast_sockaddr_is_ipv4_mapped(&dialog->our_address) ? "IP6" : "IP4", + ast_sockaddr_stringify_addr_remote(&dialog->our_address)); + + /* Set the SDP session name */ + sip_message_build_content(message, "s=%s\r\n", + ast_strlen_zero(sip_config.sdp_session) ? "-" : sip_config.sdp_session); + + /* Build max bitrate string, only if video response is appropriate */ + if (add_video && dialog->peer->max_call_bitrate && !dialog->peer->cisco_mode) { + sip_message_build_content(message, "b=CT:%d\r\n", dialog->peer->max_call_bitrate); + } + + /* Time the session is active */ + sip_message_add_content(message, "t=0 0\r\n"); + + /* If this is a response to an invite, order our offers properly */ + if (!AST_LIST_EMPTY(&dialog->sdp_media)) { + AST_LIST_TRAVERSE(&dialog->sdp_media, sdp_media, next) { + if (sdp_media->type == AST_MEDIA_TYPE_AUDIO && add_audio) { + sip_message_add_content(message, ast_str_buffer(audio_m_line)); + sip_message_add_content(message, ast_str_buffer(audio_c_line)); + sip_message_add_content(message, ast_str_buffer(audio_a_lines)); + } else if (sdp_media->type == AST_MEDIA_TYPE_VIDEO && add_video) { + sip_message_add_content(message, ast_str_buffer(video_m_line)); + sip_message_add_content(message, ast_str_buffer(video_c_line)); + sip_message_add_content(message, ast_str_buffer(video_a_lines)); + } else if (sdp_media->type == AST_MEDIA_TYPE_TEXT && add_text) { + sip_message_add_content(message, ast_str_buffer(text_m_line)); + sip_message_add_content(message, ast_str_buffer(text_c_line)); + sip_message_add_content(message, ast_str_buffer(text_a_lines)); + } else if (sdp_media->type == AST_MEDIA_TYPE_IMAGE && add_image) { + sip_message_add_content(message, ast_str_buffer(image_m_line)); + sip_message_add_content(message, ast_str_buffer(image_c_line)); + sip_message_add_content(message, ast_str_buffer(image_a_lines)); + } else { + sip_message_add_content(message, ast_str_buffer(sdp_media->decline_m_line)); + } + } + } else { + /* Generate new SDP from scratch, no offers */ + if (add_audio) { + sip_message_add_content(message, ast_str_buffer(audio_m_line)); + sip_message_add_content(message, ast_str_buffer(audio_c_line)); + sip_message_add_content(message, ast_str_buffer(audio_a_lines)); + } + + if (add_video) { + sip_message_add_content(message, ast_str_buffer(video_m_line)); + sip_message_add_content(message, ast_str_buffer(video_c_line)); + sip_message_add_content(message, ast_str_buffer(video_a_lines)); + } + + if (add_text) { + sip_message_add_content(message, ast_str_buffer(text_m_line)); + sip_message_add_content(message, ast_str_buffer(text_c_line)); + sip_message_add_content(message, ast_str_buffer(text_a_lines)); + } + + if (add_image) { + sip_message_add_content(message, ast_str_buffer(image_m_line)); + sip_message_add_content(message, ast_str_buffer(image_c_line)); + sip_message_add_content(message, ast_str_buffer(image_a_lines)); + } + } + + /* Update lastrtprx when we send our SDP */ + dialog->last_rtp_received = dialog->last_rtp_sent = ast_tvnow(); /* Why both ? */ + format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + + ast_debug(3, "Done building SDP with this capability: %s\n", + ast_format_cap_get_names(joint_format_cap, &format_names)); + /* We unlink this dialog and link again into the sip_dialogs_with_rtp container so its not in there twice */ + ao2_lock(sip_dialogs_with_rtp); + + ao2_unlink(sip_dialogs_with_rtp, dialog); + ao2_link(sip_dialogs_with_rtp, dialog); + + ao2_unlock(sip_dialogs_with_rtp); + return 0; +} + +/* Add codec offer to SDP offer/answer body in INVITE or 200 OK */ +static void sip_sdp_add_audio_codec(struct sip_dialog *dialog, struct ast_format *format, struct ast_str **m_line, + struct ast_str **a_lines, int *min_ptime, int *max_ptime) +{ + int rtp_code; + const char *mime; + unsigned int rate, framing, maximum_ms; + + ast_debug(1, "Adding codec %s to SDP\n", ast_format_get_name(format)); + + if (((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(dialog->audio_rtp), 1, format, 0)) == -1) || + !(mime = ast_rtp_lookup_mime_subtype2(1, format, 0, 0)) || !(rate = ast_rtp_lookup_sample_rate2(1, format, 0))) { + return; + } + + ast_str_append(m_line, 0, " %d", rtp_code); + + /* Opus mandates 2 channels in rtpmap */ + if (ast_format_cmp(format, ast_format_opus) == AST_FORMAT_CMP_EQUAL) { + ast_str_append(a_lines, 0, "a=rtpmap:%d %s/%u/2\r\n", rtp_code, mime, rate); + } else { + ast_str_append(a_lines, 0, "a=rtpmap:%d %s/%u\r\n", rtp_code, mime, rate); + } + + ast_format_generate_sdp_fmtp(format, rtp_code, a_lines); + + if (ast_format_cmp(format, ast_format_g723) == AST_FORMAT_CMP_EQUAL) { + /* Indicate that we don't support VAD (G.723.1 annex A) */ + ast_str_append(a_lines, 0, "a=fmtp:%d annexa=no\r\n", rtp_code); + } else if (ast_format_cmp(format, ast_format_g719) == AST_FORMAT_CMP_EQUAL) { + /* Indicate that we only expect 64Kbps */ + ast_str_append(a_lines, 0, "a=fmtp:%d bitrate=64000\r\n", rtp_code); + } + + /* Our first codec packetization processed cannot be zero */ + framing = ast_format_cap_get_format_framing(dialog->format_cap, format); + + if (framing && (*min_ptime == 0 || framing < *min_ptime)) { + *min_ptime = framing; + } + + maximum_ms = ast_format_get_maximum_ms(format); + + if (maximum_ms && (*max_ptime == 0 || maximum_ms < *max_ptime)) { + *max_ptime = maximum_ms; + } +} + +/* Add video codec offer to SDP offer/answer body in INVITE or 200 OK */ +static void sip_sdp_add_video_codec(struct sip_dialog *dialog, struct ast_format *format, + struct ast_str **m_line, struct ast_str **a_lines) +{ + int rtp_code; + const char *subtype; + unsigned int rate; + + if (!dialog->video_rtp) { + return; + } + + ast_debug(1, "Adding video codec %s to SDP\n", ast_format_get_name(format)); + + if (((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(dialog->video_rtp), 1, format, 0)) == -1) || + !(subtype = ast_rtp_lookup_mime_subtype2(1, format, 0, 0)) || !(rate = ast_rtp_lookup_sample_rate2(1, format, 0))) { + return; + } + + ast_str_append(m_line, 0, " %d", rtp_code); + + if (ast_format_cmp(format, ast_format_h264) == AST_FORMAT_CMP_EQUAL && dialog->peer->cisco_mode) { + /* Needs to be a large number otherwise video quality is poor */ + ast_str_append(a_lines, 0, "b=TIAS:4000000\r\n"); + } + + ast_str_append(a_lines, 0, "a=rtpmap:%d %s/%u\r\n", rtp_code, subtype, rate); + + /* VP8: add RTCP FIR support */ + if (ast_format_cmp(format, ast_format_vp8) == AST_FORMAT_CMP_EQUAL) { + ast_str_append(a_lines, 0, "a=rtcp-fb:* ccm fir\r\n"); + } + + ast_format_generate_sdp_fmtp(format, rtp_code, a_lines); + + if (ast_format_cmp(format, ast_format_h264) == AST_FORMAT_CMP_EQUAL) { + const char *imageattr = (const char *) ast_format_attribute_get(format, "imageattr"); + + if (ast_strlen_zero(imageattr) && dialog->peer->cisco_mode) { + imageattr = "[x=640,y=480,q=0.50]"; + } + + if (!ast_strlen_zero(imageattr)) { + ast_str_append(a_lines, 0, "a=imageattr:%d recv %s\r\n", rtp_code, imageattr); + } + } +} + +/* Add text codec offer to SDP offer/answer body in INVITE or 200 OK */ +static void sip_sdp_add_text_codec(struct sip_dialog *dialog, struct ast_format *format, + struct ast_str **m_line, struct ast_str **a_lines) +{ + int rtp_code; + + if (!dialog->text_rtp) { + return; + } + + ast_debug(1, "Adding text codec %s to SDP\n", ast_format_get_name(format)); + + if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(dialog->text_rtp), 1, format, 0)) == -1) { + return; + } + + ast_str_append(m_line, 0, " %d", rtp_code); + ast_str_append(a_lines, 0, "a=rtpmap:%d %s/%u\r\n", rtp_code, + ast_rtp_lookup_mime_subtype2(1, format, 0, 0), ast_rtp_lookup_sample_rate2(1, format, 0)); + + /* Add fmtp code here */ + if (ast_format_cmp(format, ast_format_t140_red) == AST_FORMAT_CMP_EQUAL) { + int t140_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(dialog->text_rtp), 1, ast_format_t140, 0); + + ast_str_append(a_lines, 0, "a=fmtp:%d %d/%d/%d\r\n", rtp_code, t140_code, t140_code, t140_code); + } +} + +/* Add RFC 2833 DTMF offer to SDP */ +static void sip_sdp_add_non_codec(struct sip_dialog *dialog, int non_codec, struct ast_str **m_line, + struct ast_str **a_lines) +{ + int rtp_code; + + ast_debug(1, "Adding non-codec %s to SDP\n", ast_rtp_lookup_mime_subtype2(0, NULL, non_codec, 0)); + + if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(dialog->audio_rtp), 0, NULL, non_codec)) == -1) { + return; + } + + ast_str_append(m_line, 0, " %d", rtp_code); + ast_str_append(a_lines, 0, "a=rtpmap:%d %s/%u\r\n", rtp_code, + ast_rtp_lookup_mime_subtype2(0, NULL, non_codec, 0), ast_rtp_lookup_sample_rate2(0, NULL, non_codec)); + + if (non_codec == AST_RTP_DTMF) { /* Indicate we support DTMF and FLASH.. */ + ast_str_append(a_lines, 0, "a=fmtp:%d 0-16\r\n", rtp_code); + } +} + +/* Add ICE attributes to SDP */ +static void sip_sdp_add_ice(struct ast_rtp_instance *rtp, struct ast_str **a_lines) +{ + struct ast_rtp_engine_ice *ice; + const char *username, *password; + struct ao2_container *candidates; + struct ao2_iterator iter; + RAII_VAR(struct ast_rtp_engine_ice_candidate *, candidate, NULL, ao2_cleanup); + + /* If no ICE support is present we can't very well add the attributes */ + ice = ast_rtp_instance_get_ice(rtp); + + if (!ice || !(candidates = ice->get_local_candidates(rtp))) { + return; + } + + if ((username = ice->get_ufrag(rtp))) { + ast_str_append(a_lines, 0, "a=ice-ufrag:%s\r\n", username); + } + if ((password = ice->get_password(rtp))) { + ast_str_append(a_lines, 0, "a=ice-pwd:%s\r\n", password); + } + + iter = ao2_iterator_init(candidates, 0); + + while ((candidate = ao2_iterator_next(&iter))) { + ast_str_append(a_lines, 0, "a=candidate:%s %u %s %d ", + candidate->foundation, candidate->id, candidate->transport, candidate->priority); + ast_str_append(a_lines, 0, "%s ", ast_sockaddr_stringify_addr_remote(&candidate->address)); + ast_str_append(a_lines, 0, "%s typ ", ast_sockaddr_stringify_port(&candidate->address)); + + if (candidate->type == AST_RTP_ICE_CANDIDATE_TYPE_HOST) { + ast_str_append(a_lines, 0, "host"); + } else if (candidate->type == AST_RTP_ICE_CANDIDATE_TYPE_SRFLX) { + ast_str_append(a_lines, 0, "srflx"); + } else if (candidate->type == AST_RTP_ICE_CANDIDATE_TYPE_RELAYED) { + ast_str_append(a_lines, 0, "relay"); + } + + if (!ast_sockaddr_isnull(&candidate->relay_address)) { + ast_str_append(a_lines, 0, " raddr %s rport %s", + ast_sockaddr_stringify_addr_remote(&candidate->relay_address), + ast_sockaddr_stringify_port(&candidate->relay_address)); + } + + ast_str_append(a_lines, 0, "\r\n"); + ao2_ref(candidate, -1); + } + + ao2_iterator_destroy(&iter); +} + +static const char *sip_sdp_get_a_mode(struct sip_dialog *dialog) +{ + if (dialog->onhold == SIP_ONHOLD_RECVONLY) { + return "a=recvonly\r\n"; + } else if (dialog->onhold == SIP_ONHOLD_INACTIVE) { + return "a=inactive\r\n"; + } else { + return "a=sendrecv\r\n"; + } +} + +static char *sip_sdp_get_a_crypto(struct ast_sdp_srtp *sdp_srtp) +{ + struct ast_str *crypto = ast_str_alloca(512); + + if (!sdp_srtp) { + return NULL; + } + + do { + ast_str_append(&crypto, 0, "a=crypto:%s\r\n", ast_sdp_srtp_get_attrib(sdp_srtp, FALSE, FALSE)); + } while ((sdp_srtp = AST_LIST_NEXT(sdp_srtp, sdp_srtp_list))); + + return ast_strdup(ast_str_buffer(crypto)); +} + +/* When receiving an SDP offer, it is important to take note of what media types were offered. By doing this, even if + * we don't want to answer a particular media stream with something meaningful, we can still put an m= line in our + * answer with the port set to 0. If we wanted to be 100% correct, we would keep a list of all media streams offered. + * That way we could respond even to unknown media types, and we could respond to multiple streams of the same type. + * Such large-scale changes are not a good idea for released branches, though, so we're compromising by just making sure + * that for the common cases: audio and video, audio and T.38, and audio and text, we give the appropriate response to + * both media streams */ +static struct sip_sdp_media *sip_sdp_media_alloc(struct sip_dialog *dialog, const char *m_line) +{ + struct sip_sdp_media *sdp_media; + char type[16], protocol[32]; + int port, ports, codecs_len; + const char *codecs; + + if (sscanf(m_line, "m=%15s %30u %31s %n", type, &port, protocol, &codecs_len) != 3 && + sscanf(m_line, "m=%15s %30u/%30u %31s %n", type, &port, &ports, protocol, &codecs_len) != 4) { + ast_log(LOG_WARNING, "Unable to parse m= line: %s\n", m_line); + return NULL; + } + + codecs = m_line + codecs_len; + + if (!(sdp_media = ast_calloc(1, sizeof(*sdp_media)))) { + return NULL; + } + + if (!(sdp_media->decline_m_line = ast_str_create(64))) { + ast_free(sdp_media); + return NULL; + } + + sdp_media->type = AST_MEDIA_TYPE_UNKNOWN; + + /* Create zero-port m-line so that we can decline this stream if needed */ + ast_str_set(&sdp_media->decline_m_line, 0, "m=%s 0 %s %s", type, protocol, codecs); + AST_LIST_INSERT_TAIL(&dialog->sdp_media, sdp_media, next); + return sdp_media; +} + +/* Check the media stream list to see if the given type already exists */ +static int sip_sdp_media_has_type(struct sip_dialog *dialog, int type) +{ + struct sip_sdp_media *sdp_media; + + AST_LIST_TRAVERSE(&dialog->sdp_media, sdp_media, next) { + if (sdp_media->type == type) { + return TRUE; + } + } + + return FALSE; +} + +/* Destroy SDP media offer list */ +void sip_sdp_media_destroy(struct sip_dialog *dialog) +{ + struct sip_sdp_media *sdp_media; + + while ((sdp_media = AST_LIST_REMOVE_HEAD(&dialog->sdp_media, next))) { + ast_free(sdp_media->decline_m_line); + ast_free(sdp_media); + } +} + +/* Start ICE negotiation on an RTP instance */ +static void sip_sdp_start_ice(struct ast_rtp_instance *rtp, int offered) +{ + struct ast_rtp_engine_ice *ice; + + if (!(ice = ast_rtp_instance_get_ice(rtp))) { + return; + } + + /* If we are the offerer then we are the controlling agent, otherwise they are */ + ice->set_role(rtp, offered ? AST_RTP_ICE_ROLE_CONTROLLING : AST_RTP_ICE_ROLE_CONTROLLED); + ice->start(rtp); +} + +static void sip_sdp_set_rtcp_mux(struct sip_dialog *dialog, struct ast_rtp_instance *rtp, int which, int rtcp_mux) +{ + int fd; + + if (dialog->peer->rtcp_mux && rtcp_mux) { + ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_MUX); + fd = -1; + } else { + ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_STANDARD); + fd = ast_rtp_instance_fd(rtp, 1); + } + + if (dialog->channel) { + ast_channel_set_fd(dialog->channel, which, fd); + } +} + +static void sip_sdp_change_ice_rtcp_mux(struct sip_dialog *dialog, struct ast_rtp_instance *rtp, int rtcp_mux) +{ + struct ast_rtp_engine_ice *ice; + + if (!(ice = ast_rtp_instance_get_ice(rtp))) { + return; + } + + if (dialog->peer->rtcp_mux && rtcp_mux) { + /* We both support RTCP mux. Only one ICE component necessary */ + ice->change_components(rtp, 1); + } else { + /* They either don't support RTCP mux or we don't know if they do yet */ + ice->change_components(rtp, 2); + } +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/security_events.c asterisk-22.6.0/channels/sip/security_events.c --- asterisk-22.6.0.orig/channels/sip/security_events.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/security_events.c 2025-10-21 18:38:17.468217666 +1300 @@ -0,0 +1,328 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012, Digium, Inc. + * + * Michael L. Young + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/channel.h" +#include "asterisk/callerid.h" +#include "asterisk/indications.h" +#include "asterisk/pbx.h" +#include "asterisk/security_events.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/config.h" +#include "include/utils.h" +#include "include/security_events.h" + +static void sip_security_event_inval_acct(const struct sip_dialog *dialog); +static void sip_security_event_failed_acl(const struct sip_dialog *dialog, const char *acl_name); +static void sip_security_event_inval_password(const struct sip_dialog *dialog, const char *response_challenge, + const char *response_hash); +static void sip_security_event_auth_success(const struct sip_dialog *dialog, uint32_t using_password); +static void sip_security_event_session_limit(const struct sip_dialog *dialog); +static void sip_security_event_chal_resp_failed(const struct sip_dialog *dialog, const char *response, + const char *expected_response); +static void sip_security_event_chal_sent(const struct sip_dialog *dialog); +static void sip_security_event_inval_transport(const struct sip_dialog *dialog, const char *transport); + +/* Determine transport type used to receive request*/ +static void sip_security_event_inval_acct(const struct sip_dialog *dialog) +{ + char session_id[32]; + struct ast_security_event_inval_acct_id inval_acct_id = { + .common.event_type = AST_SECURITY_EVENT_INVAL_ACCT_ID, + .common.version = AST_SECURITY_EVENT_INVAL_ACCT_ID_VERSION, + .common.service = "SIP", + .common.account_id = dialog->to_user, + .common.local_addr = { + .addr = &dialog->our_address, + .transport = dialog->socket.transport, + }, + .common.remote_addr = { + .addr = &dialog->address, + .transport = dialog->socket.transport, + }, + .common.session_id = session_id, + }; + + snprintf(session_id, sizeof(session_id), "%p", dialog); + ast_security_event_report(AST_SEC_EVT(&inval_acct_id)); +} + +static void sip_security_event_failed_acl(const struct sip_dialog *dialog, const char *acl_name) +{ + char session_id[32]; + struct ast_security_event_failed_acl failed_acl = { + .common.event_type = AST_SECURITY_EVENT_FAILED_ACL, + .common.version = AST_SECURITY_EVENT_FAILED_ACL_VERSION, + .common.service = "SIP", + .common.account_id = dialog->to_user, + .common.local_addr = { + .addr = &dialog->our_address, + .transport = dialog->socket.transport, + }, + .common.remote_addr = { + .addr = &dialog->address, + .transport = dialog->socket.transport, + }, + .common.session_id = session_id, + .acl_name = acl_name, + }; + + snprintf(session_id, sizeof(session_id), "%p", dialog); + ast_security_event_report(AST_SEC_EVT(&failed_acl)); +} + +static void sip_security_event_inval_password(const struct sip_dialog *dialog, const char *response_challenge, + const char *response_hash) +{ + char session_id[32]; + struct ast_security_event_inval_password inval_password = { + .common.event_type = AST_SECURITY_EVENT_INVAL_PASSWORD, + .common.version = AST_SECURITY_EVENT_INVAL_PASSWORD_VERSION, + .common.service = "SIP", + .common.account_id = dialog->to_user, + .common.local_addr = { + .addr = &dialog->our_address, + .transport = dialog->socket.transport, + }, + .common.remote_addr = { + .addr = &dialog->address, + .transport = dialog->socket.transport, + }, + .common.session_id = session_id, + + .challenge = dialog->nonce, + .received_challenge = response_challenge, + .received_hash = response_hash, + }; + + snprintf(session_id, sizeof(session_id), "%p", dialog); + ast_security_event_report(AST_SEC_EVT(&inval_password)); +} + +static void sip_security_event_auth_success(const struct sip_dialog *dialog, uint32_t using_password) +{ + char session_id[32]; + struct ast_security_event_successful_auth successful_auth = { + .common.event_type = AST_SECURITY_EVENT_SUCCESSFUL_AUTH, + .common.version = AST_SECURITY_EVENT_SUCCESSFUL_AUTH_VERSION, + .common.service = "SIP", + .common.account_id = dialog->to_user, + .common.local_addr = { + .addr = &dialog->our_address, + .transport = dialog->socket.transport, + }, + .common.remote_addr = { + .addr = &dialog->address, + .transport = dialog->socket.transport, + }, + .common.session_id = session_id, + .using_password = using_password, + }; + + snprintf(session_id, sizeof(session_id), "%p", dialog); + ast_security_event_report(AST_SEC_EVT(&successful_auth)); +} + +static void sip_security_event_session_limit(const struct sip_dialog *dialog) +{ + char session_id[32]; + struct ast_security_event_session_limit session_limit = { + .common.event_type = AST_SECURITY_EVENT_SESSION_LIMIT, + .common.version = AST_SECURITY_EVENT_SESSION_LIMIT_VERSION, + .common.service = "SIP", + .common.account_id = dialog->to_user, + .common.local_addr = { + .addr = &dialog->our_address, + .transport = dialog->socket.transport, + }, + .common.remote_addr = { + .addr = &dialog->address, + .transport = dialog->socket.transport, + }, + .common.session_id = session_id, + }; + + snprintf(session_id, sizeof(session_id), "%p", dialog); + ast_security_event_report(AST_SEC_EVT(&session_limit)); +} + +static void sip_security_event_chal_resp_failed(const struct sip_dialog *dialog, const char *response, + const char *expected_response) +{ + char session_id[32], account_id[256]; + struct ast_security_event_chal_resp_failed chal_resp_failed = { + .common.event_type = AST_SECURITY_EVENT_CHAL_RESP_FAILED, + .common.version = AST_SECURITY_EVENT_CHAL_RESP_FAILED_VERSION, + .common.service = "SIP", + .common.account_id = account_id, + .common.local_addr = { + .addr = &dialog->our_address, + .transport = dialog->socket.transport, + }, + .common.remote_addr = { + .addr = &dialog->address, + .transport = dialog->socket.transport, + }, + .common.session_id = session_id, + + .challenge = dialog->nonce, + .response = response, + .expected_response = expected_response, + }; + + if (!ast_strlen_zero(dialog->from)) { /* When dialing, show account making call */ + ast_copy_string(account_id, dialog->from, sizeof(account_id)); + } else { + ast_copy_string(account_id, dialog->to_user, sizeof(account_id)); + } + + snprintf(session_id, sizeof(session_id), "%p", dialog); + ast_security_event_report(AST_SEC_EVT(&chal_resp_failed)); +} + +static void sip_security_event_chal_sent(const struct sip_dialog *dialog) +{ + char session_id[32], account_id[256]; + struct ast_security_event_chal_sent chal_sent = { + .common.event_type = AST_SECURITY_EVENT_CHAL_SENT, + .common.version = AST_SECURITY_EVENT_CHAL_SENT_VERSION, + .common.service = "SIP", + .common.account_id = account_id, + .common.local_addr = { + .addr = &dialog->our_address, + .transport = dialog->socket.transport, + }, + .common.remote_addr = { + .addr = &dialog->address, + .transport = dialog->socket.transport, + }, + .common.session_id = session_id, + .challenge = dialog->nonce, + }; + + if (!ast_strlen_zero(dialog->from)) { /* When dialing, show account making call */ + ast_copy_string(account_id, dialog->from, sizeof(account_id)); + } else { + ast_copy_string(account_id, dialog->to_user, sizeof(account_id)); + } + + snprintf(session_id, sizeof(session_id), "%p", dialog); + ast_security_event_report(AST_SEC_EVT(&chal_sent)); +} + +static void sip_security_event_inval_transport(const struct sip_dialog *dialog, const char *transport) +{ + char session_id[32]; + struct ast_security_event_inval_transport inval_transport = { + .common.event_type = AST_SECURITY_EVENT_INVAL_TRANSPORT, + .common.version = AST_SECURITY_EVENT_INVAL_TRANSPORT_VERSION, + .common.service = "SIP", + .common.account_id = dialog->to_user, + .common.local_addr = { + .addr = &dialog->our_address, + .transport = dialog->socket.transport, + }, + .common.remote_addr = { + .addr = &dialog->address, + .transport = dialog->socket.transport, + }, + .common.session_id = session_id, + .transport = transport, + }; + + snprintf(session_id, sizeof(session_id), "%p", dialog); + ast_security_event_report(AST_SEC_EVT(&inval_transport)); +} + +int sip_security_event(const struct sip_dialog *dialog, const struct sip_message *request, const int res) +{ + RAII_VAR(struct sip_peer *, peer, NULL, ao2_cleanup); + + if (!ast_strlen_zero(dialog->to_user)) { + peer = sip_peer_find(dialog->to_user, TRUE, FALSE); /* 'to_user' contains the username */ + } else { + peer = sip_peer_address_find(&dialog->socket.address, dialog->socket.transport, TRUE, FALSE); + } + + switch (res) { + case SIP_AUTHORIZATION_DONT_KNOW: + break; + case SIP_AUTHORIZATION_SUCCESS: + if (peer) { + sip_security_event_auth_success(dialog, + !ast_strlen_zero(peer->secret) || !ast_strlen_zero(peer->md5_secret)); + } + + break; + case SIP_AUTHORIZATION_CHALLENGE_SENT: + sip_security_event_chal_sent(dialog); + break; + case SIP_AUTHORIZATION_SECRET_FAILED: + sip_security_event_inval_password(dialog, dialog->authorization_nonce, dialog->authorization_response); + break; + case SIP_AUTHORIZATION_USERNAME_MISMATCH: + if (peer) { + sip_security_event_chal_resp_failed(dialog, dialog->authorization_username, peer->name); + } + + break; + case SIP_AUTHORIZATION_NOT_FOUND: + sip_security_event_inval_acct(dialog); + sip_security_event_failed_acl(dialog, "no_extension_match"); + break; + case SIP_AUTHORIZATION_UNKNOWN_DOMAIN: + sip_security_event_failed_acl(dialog, "domain_must_match"); + break; + case SIP_AUTHORIZATION_PEER_NOT_DYNAMIC: + sip_security_event_failed_acl(dialog, "peer_not_dynamic"); + break; + case SIP_AUTHORIZATION_ACL_FAILED: + sip_security_event_failed_acl(dialog, "device_must_match_acl"); + break; + case SIP_AUTHORIZATION_INVALID_TRANSPORT: + sip_security_event_inval_transport(dialog, ast_transport2str(dialog->socket.transport)); + break; + case SIP_AUTHORIZATION_RTP_FAILED: + break; + case SIP_AUTHORIZATION_SESSION_LIMIT: + sip_security_event_session_limit(dialog); + break; + } + + return res; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/session_timer.c asterisk-22.6.0/channels/sip/session_timer.c --- asterisk-22.6.0.orig/channels/sip/session_timer.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/session_timer.c 2025-10-21 18:38:17.469217640 +1300 @@ -0,0 +1,242 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/netsock2.h" +#include "asterisk/strings.h" +#include "asterisk/channel.h" +#include "asterisk/indications.h" +#include "asterisk/pbx.h" + +#include "include/sip.h" +#include "include/monitor.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/config.h" +#include "include/utils.h" +#include "include/manager.h" +#include "include/fax.h" + +static int __sip_session_timer_start(const void *data); +static int __sip_session_timer_stop(const void *data); +static int sip_session_timer_timeout(const void *data); + +/* Check Session Timers for an INVITE request */ +int sip_session_timer_handle_invite(struct sip_dialog *dialog, struct sip_message *request, int reinvite) +{ + /* Session-Timers */ + if (dialog->supported_options & SIP_OPTION_TIMER) { + /* The UAC has requested session-timers for this session. Negotiate the session refresh interval and + * who will be the refresher */ + ast_debug(2, "Incoming INVITE with 'timer' option supported\n"); + + /* Parse the Session-Expires header */ + if (sip_parse_session_expires(dialog, request)) { + sip_response_send_reliable(dialog, "400 Bad Request", request); + return -1; + } + + if (dialog->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_UAC) { + dialog->session_timer_refresher = SIP_SESSION_TIMER_REFRESHER_UAS; + } else { + dialog->session_timer_refresher = SIP_SESSION_TIMER_REFRESHER_UAC; + } + + /* Parse the Min-SE header */ + if (sip_parse_min_se(dialog, request)) { + sip_response_send_reliable(dialog, "400 Bad Request", request); + return -1; + } + + switch (dialog->peer->session_timer_mode) { + case SIP_SESSION_TIMER_MODE_ACCEPT: + case SIP_SESSION_TIMER_MODE_ORIGINATE: + if (dialog->session_timer_expires < dialog->peer->session_timer_min_expires) { + sip_response_send_with_min_se(dialog, request, dialog->peer->session_timer_min_expires); + return -1; + } + + if (dialog->session_timer_expires > dialog->peer->session_timer_max_expires) { + dialog->session_timer_expires = dialog->peer->session_timer_max_expires; + } + + dialog->session_timer_active = TRUE; + dialog->session_timer_remote_active = TRUE; + + break; + case SIP_SESSION_TIMER_MODE_REFUSE: + if (dialog->require_options & SIP_OPTION_TIMER) { + sip_response_send_with_unsupported(dialog, request, "timer"); + ast_debug(1, "Received SIP INVITE with supported but disabled option: timer\n"); + return -1; + } + + break; + default: + break; + } + } else if (dialog->peer->session_timer_mode == SIP_SESSION_TIMER_MODE_ORIGINATE) { + /* The UAC did not request session-timers. Asterisk (UAS), will now decide (based on session-timer mode + * whether to run session-timers for this dialog */ + dialog->session_timer_refresher = SIP_SESSION_TIMER_REFRESHER_UAC; + dialog->session_timer_expires = dialog->peer->session_timer_max_expires; + + dialog->session_timer_active = TRUE; + dialog->session_timer_remote_active = !!(dialog->supported_options & SIP_OPTION_TIMER); + } + + return 0; +} + +/* Process session refresh timeout event */ +static int sip_session_timer_timeout(const void *data) +{ + struct sip_dialog *dialog; + int active; + + dialog = (struct sip_dialog *) data; + active = FALSE; + + if (!dialog->channel || !dialog->session_timer_active || ast_channel_state(dialog->channel) != AST_STATE_UP) { + goto error; + } + + if (dialog->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_UAC) { + active = TRUE; + sip_request_send_reinvite_with_sdp(dialog, TRUE, dialog->fax_state == SIP_FAX_ENABLED); + } else { + struct ast_channel *channel; + + if ((channel = sip_dialog_lock_with_channel(dialog))) { + ast_verb(3, "Disconnecting '%s' due to session timer expiration\n", ast_channel_name(channel)); + + sip_publish_session_timeout(channel, "SIPSessionTimer"); + ast_softhangup_nolock(channel, AST_SOFTHANGUP_DEV); + + ast_channel_unlock(channel); + ast_channel_unref(channel); + } + + ao2_unlock(dialog); + } + +error: + if (!active) { + /* Session timer processing is no longer needed */ + ast_debug(2, "Session timer stopped on '%s'\n", dialog->call_id); + + /* Don't pass go, don't collect $200.. we are the scheduled callback. We can rip ourself out here */ + dialog->session_timer_sched_id = -1; + dialog->session_timer_active = FALSE; + + /* If we are not asking to be rescheduled, then we need to release our reference to the dialog */ + ao2_ref(dialog, -1); + } + + return active; +} + +/* Run by the sched thread */ +static int __sip_session_timer_stop(const void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->session_timer_sched_id, ao2_cleanup(dialog)); + ao2_ref(dialog, -1); + return 0; +} + +/* Stop session timer */ +void sip_session_timer_stop(struct sip_dialog *dialog) +{ + if (!dialog->session_timer_active) { + return; + } + + dialog->session_timer_active = FALSE; + + if (ast_sched_add(sip_sched_context, 0, __sip_session_timer_stop, ao2_bump(dialog)) == -1) { + ao2_ref(dialog, -1); + } +} + +/* Run by the sched thread */ +static int __sip_session_timer_start(const void *data) +{ + struct sip_dialog *dialog; + unsigned int when; + + dialog = (struct sip_dialog *) data; + /* RFC 4028 section 10: If the side not performing refreshes does not receive a session refresh request before + * the session expiration, it SHOULD send a BYE to terminate the session, slightly before the session expiration */ + when = dialog->session_timer_expires * 1000; + + if (dialog->session_timer_refresher == SIP_SESSION_TIMER_REFRESHER_UAC) { + when /= 2; + } else { + /* The minimum of 32 seconds and one third of the session interval is RECOMMENDED */ + when -= MIN(when / 3, 32 * 1000); + } + + ast_debug(2, "Session timer started on '%s' expires in %ums\n", dialog->call_id, when); + + if ((dialog->session_timer_sched_id = ast_sched_add(sip_sched_context, when, sip_session_timer_timeout, + ao2_bump(dialog))) == -1) { + ao2_ref(dialog, -1); + } + + ao2_ref(dialog, -1); + return 0; +} + +/* Start session timer */ +void sip_session_timer_start(struct sip_dialog *dialog) +{ + dialog->session_timer_active = TRUE; + + if (ast_sched_add(sip_sched_context, 0, __sip_session_timer_start, ao2_bump(dialog)) == -1) { + ao2_ref(dialog, -1); + } +} + +/* Restart session timer */ +void sip_session_timer_restart(struct sip_dialog *dialog) +{ + if (dialog->session_timer_active) { + sip_session_timer_start(dialog); + } +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/transfer.c asterisk-22.6.0/channels/sip/transfer.c --- asterisk-22.6.0.orig/channels/sip/transfer.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/transfer.c 2025-10-21 18:38:17.469217640 +1300 @@ -0,0 +1,193 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/module.h" +#include "asterisk/logger.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/utils.h" +#include "asterisk/bridge.h" +#include "asterisk/callerid.h" +#include "asterisk/causes.h" +#include "asterisk/indications.h" +#include "asterisk/pbx.h" +#include "asterisk/devicestate.h" +#include "asterisk/message.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/domains.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/channel_tech.h" +#include "include/transfer.h" + +/* Callback called on new outbound channel during blind transfer. We use this opportunity to populate the channel with + * data from the REFER so that, if necessary, we can include proper information on any new INVITE we may send out */ +void sip_transfer_blind(struct ast_channel *channel, struct transfer_channel_data *channel_data, + enum ast_transfer_type transfer_type) +{ + struct sip_transfer_blind_data *transfer_data; + struct ast_party_redirecting redirecting; + struct ast_set_party_redirecting update; + + transfer_data = (struct sip_transfer_blind_data *) channel_data->data; + + pbx_builtin_setvar_helper(channel, "SIP_TRANSFER_REPLACES", transfer_data->replaces); + pbx_builtin_setvar_helper(channel, "SIP_TRANSFER_REFERRED_BY", transfer_data->referred_by); + pbx_builtin_setvar_helper(channel, "SIP_TRANSFER_DOMAIN", transfer_data->domain); + + ast_party_redirecting_init(&redirecting); + memset(&update, 0, sizeof(update)); + + if (!ast_strlen_zero(transfer_data->from_name)) { + update.from.name = TRUE; + redirecting.from.name.valid = TRUE; + redirecting.from.name.str = (char *) transfer_data->from_name; + } + + if (!ast_strlen_zero(transfer_data->from_number)) { + update.from.number = TRUE; + redirecting.from.number.valid = TRUE; + redirecting.from.number.str = (char *) transfer_data->from_number; + } + + if (!ast_strlen_zero(transfer_data->to_name)) { + update.to.name = TRUE; + redirecting.to.name.valid = TRUE; + redirecting.to.name.str = (char *) transfer_data->to_name; + } + + if (!ast_strlen_zero(transfer_data->to_number)) { + update.to.number = TRUE; + redirecting.to.number.valid = TRUE; + redirecting.to.number.str = (char *) transfer_data->to_number; + } + + if (!ast_strlen_zero(transfer_data->tag)) { + redirecting.from.tag = (char *) transfer_data->tag; + redirecting.to.tag = (char *) transfer_data->tag; + } + + redirecting.reason.code = transfer_data->code; + redirecting.reason.str = (char *) transfer_data->reason; + + ast_channel_update_redirecting(channel, &redirecting, &update); +} + +/* Find all call legs and bridge transferee with target. This function assumes two locks to begin with, dialog and + * channel Additional locks are held at the beginning of the function, target_dialog, and transfer_dialog and + * transfer_channel.These 2 locks _MUST_ be let go by the end of the function. Do not be confused into thinking a + * dialog's channel is the same thing as the channels locked at the beginning of this function, after the masquerade + * this may not be true. Be consistent and unlock only the exact same pointers that were locked to begin with. If this + * function is successful, only the dialog lock will remain on return. Setting no_unlock indicates to + * sip_handle_incoming() that the dialog's channel it locked does not require an unlock */ +int sip_transfer_attended(struct sip_dialog *dialog, struct ast_channel *channel, uint32_t cseq, int *no_unlock) +{ + RAII_VAR(struct sip_dialog *, transfer_dialog, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, transfer_channel, NULL, ao2_cleanup); + enum ast_transfer_result transfer_res; + + /* Check if the call ID of the replaces header does exist locally */ + if (!(transfer_dialog = sip_dialog_find_with_channel(dialog->replaces_call_id, + dialog->replaces_to_tag, dialog->replaces_from_tag, &transfer_channel))) { + if (dialog->local_transfer) { + /* We did not find the refered call. Sorry, can't accept then */ + /* Let's fake a response from someone else in order to follow the standard */ + sip_request_send_notify_with_sipfrag(dialog, cseq, "481 Call Leg/Transaction Does Not Exist"); + + dialog->transferring_call = FALSE; + dialog->refer_state = SIP_REFER_FAILED; + return -1; + } + + /* Fall through for remote transfers that we did not find locally */ + ast_debug(3, "Attended transfer: Not our call generating INVITE with replaces\n"); + return 0; + } + + if (!transfer_channel) { /* No active channel */ + ast_debug(4, "Attended transfer: Error: No owner of target call\n"); + sip_request_send_notify_with_sipfrag(dialog, cseq, "503 Service Unavailable"); /* Cancel transfer */ + + dialog->transferring_call = FALSE; + dialog->refer_state = SIP_REFER_FAILED; + return -1; + } + + dialog->defer_bye_on_transfer = TRUE; /* Delay hangup */ + + /* Cisco phones need a different response code */ + if (transfer_dialog->peer->cisco_mode) { + transfer_dialog->transfer_response_error = TRUE; + } + + ao2_unlock(dialog); + ast_channel_unlock(channel); + + *no_unlock = TRUE; + transfer_res = ast_bridge_transfer_attended(channel, transfer_channel); + ao2_lock(dialog); + + switch (transfer_res) { + case AST_BRIDGE_TRANSFER_SUCCESS: + dialog->refer_state = SIP_REFER_SUCCESS; + sip_request_send_notify_with_sipfrag(dialog, cseq, "200 OK"); + return 0; + case AST_BRIDGE_TRANSFER_FAIL: + dialog->refer_state = SIP_REFER_FAILED; + dialog->defer_bye_on_transfer = FALSE; + sip_request_send_notify_with_sipfrag(dialog, cseq, "500 Internal Server Error"); + return -1; + case AST_BRIDGE_TRANSFER_INVALID: + dialog->refer_state = SIP_REFER_FAILED; + dialog->defer_bye_on_transfer = FALSE; + sip_request_send_notify_with_sipfrag(dialog, cseq, "503 Service Unavailable"); + return -1; + case AST_BRIDGE_TRANSFER_NOT_PERMITTED: + dialog->refer_state = SIP_REFER_FAILED; + dialog->defer_bye_on_transfer = FALSE; + sip_request_send_notify_with_sipfrag(dialog, cseq, "403 Forbidden"); + return -1; + default: + break; + } + + return -1; +} diff '--color=auto' -durN asterisk-22.6.0.orig/channels/sip/utils.c asterisk-22.6.0/channels/sip/utils.c --- asterisk-22.6.0.orig/channels/sip/utils.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/channels/sip/utils.c 2025-10-21 18:38:17.469217640 +1300 @@ -0,0 +1,1880 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * 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. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/utils.h" +#include "asterisk/sched.h" +#include "asterisk/netsock2.h" +#include "asterisk/tcptls.h" +#include "asterisk/channel.h" +#include "asterisk/callerid.h" +#include "asterisk/causes.h" +#include "asterisk/indications.h" +#include "asterisk/format.h" +#include "asterisk/format_cache.h" +#include "asterisk/pbx.h" +#include "asterisk/devicestate.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_endpoints.h" +#include "asterisk/astdb.h" +#include "asterisk/acl.h" +#include "asterisk/cli.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/message.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/session_timer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" + +static int sip_cmp_parameters(char *parameters1, char *parameters2, const char sep); +static int sip_parse_methods(char *allow); +static int sip_parse_options(char *supported, struct ast_str **unsupported); + +/* A per-thread buffer for methods to string conversion */ +AST_THREADSTORAGE(sip_methods2str_buf); +/* A per-thread buffer for options to string conversion */ +AST_THREADSTORAGE(sip_options2str_buf); + +const char *sip_reason2str(struct ast_party_redirecting_reason *reason) +{ + /* Use specific string if given */ + if (!ast_strlen_zero(reason->str)) { + return reason->str; + } + + switch (reason->code) { + case AST_REDIRECTING_REASON_USER_BUSY: + return "user-busy"; + case AST_REDIRECTING_REASON_NO_ANSWER: + return "no-answer"; + case AST_REDIRECTING_REASON_UNAVAILABLE: + return "unavailable"; + case AST_REDIRECTING_REASON_UNCONDITIONAL: + return "unconditional"; + case AST_REDIRECTING_REASON_TIME_OF_DAY: + return "time-of-day"; + case AST_REDIRECTING_REASON_DO_NOT_DISTURB: + return "do-not-disturb"; + case AST_REDIRECTING_REASON_DEFLECTION: + return "deflection"; + case AST_REDIRECTING_REASON_FOLLOW_ME: + return "follow-me"; + case AST_REDIRECTING_REASON_OUT_OF_ORDER: + return "out-of-service"; + case AST_REDIRECTING_REASON_AWAY: + return "away"; + case AST_REDIRECTING_REASON_CALL_FWD_DTE: + return "cf_dte"; /* Non-standard */ + case AST_REDIRECTING_REASON_SEND_TO_VM: + return "send_to_vm"; /* Non-standard */ + default: + break; + } + + return "unknown"; +} + +/* Convert SIP hangup causes to Asterisk hangup causes */ +int sip_hangup2cause(int cause) +{ + switch (cause) { + case 401: /* Unauthorized */ + return AST_CAUSE_CALL_REJECTED; + case 403: /* Not found */ + return AST_CAUSE_CALL_REJECTED; + case 404: /* Not found */ + return AST_CAUSE_UNALLOCATED; + case 405: /* Method not allowed */ + return AST_CAUSE_INTERWORKING; + case 407: /* Proxy authentication required */ + return AST_CAUSE_CALL_REJECTED; + case 408: /* No reaction */ + return AST_CAUSE_NO_USER_RESPONSE; + case 409: /* Conflict */ + return AST_CAUSE_NORMAL_TEMPORARY_FAILURE; + case 410: /* Gone */ + return AST_CAUSE_NUMBER_CHANGED; + case 411: /* Length required */ + return AST_CAUSE_INTERWORKING; + case 413: /* Request entity too large */ + return AST_CAUSE_INTERWORKING; + case 414: /* Request URI too large */ + return AST_CAUSE_INTERWORKING; + case 415: /* Unsupported media type */ + return AST_CAUSE_INTERWORKING; + case 420: /* Bad extension */ + return AST_CAUSE_NO_ROUTE_DESTINATION; + case 480: /* No answer */ + return AST_CAUSE_NO_ANSWER; + case 481: /* No answer */ + return AST_CAUSE_INTERWORKING; + case 482: /* Loop detected */ + return AST_CAUSE_INTERWORKING; + case 483: /* Too many hops */ + return AST_CAUSE_NO_ANSWER; + case 484: /* Address incomplete */ + return AST_CAUSE_INVALID_NUMBER_FORMAT; + case 485: /* Ambiguous */ + return AST_CAUSE_UNALLOCATED; + case 486: /* Busy everywhere */ + return AST_CAUSE_BUSY; + case 487: /* Request terminated */ + return AST_CAUSE_INTERWORKING; + case 488: /* No codecs approved */ + return AST_CAUSE_BEARERCAPABILITY_NOTAVAIL; + case 491: /* Request pending */ + return AST_CAUSE_INTERWORKING; + case 493: /* Undecipherable */ + return AST_CAUSE_INTERWORKING; + case 500: /* Server internal failure */ + return AST_CAUSE_FAILURE; + case 501: /* Call rejected */ + return AST_CAUSE_FACILITY_REJECTED; + case 502: + return AST_CAUSE_DESTINATION_OUT_OF_ORDER; + case 503: /* Service unavailable */ + return AST_CAUSE_CONGESTION; + case 504: /* Gateway timeout */ + return AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE; + case 505: /* SIP version not supported */ + return AST_CAUSE_INTERWORKING; + case 600: /* Busy everywhere */ + return AST_CAUSE_USER_BUSY; + case 603: /* Decline */ + return AST_CAUSE_CALL_REJECTED; + case 604: /* Does not exist anywhere */ + return AST_CAUSE_UNALLOCATED; + case 606: /* Not acceptable */ + return AST_CAUSE_BEARERCAPABILITY_NOTAVAIL; + default: + if (cause >= 400 && cause < 500) { + /* 4xx class error that is unknown - someting wrong with our request */ + return AST_CAUSE_INTERWORKING; + } else if (cause >= 500 && cause < 600) { + /* 5xx class error - problem in the remote end */ + return AST_CAUSE_CONGESTION; + } else if (cause >= 600 && cause < 700) { + /* 6xx - global errors in the 4xx class */ + return AST_CAUSE_INTERWORKING; + } + + return AST_CAUSE_NORMAL; + } +} + +/* Convert Asterisk hangup causes to SIP codes + * ISUP Cause value SIP response + * ---------------- ------------ + * 1 unallocated number 404 Not Found + * 2 no route to network 404 Not found + * 3 no route to destination 404 Not found + * 16 normal call clearing --- (*) + * 17 user busy 486 Busy Here + * 18 no user responding 408 Request Timeout + * 19 no answer from the user 480 Temporarily unavailable + * 20 subscriber absent 480 Temporarily unavailable + * 21 call rejected 403 Forbidden (+) + * 22 number changed (w/o diagnostic) 410 Gone + * 22 number changed (w/ diagnostic) 301 Moved Permanently + * 23 redirection to new destination 410 Gone + * 26 non-selected user clearing 404 Not Found (=) + * 27 destination out of order 502 Bad Gateway + * 28 address incomplete 484 Address incomplete + * 29 facility rejected 501 Not implemented + * 31 normal unspecified 480 Temporarily unavailable */ +const char *sip_cause2hangup(int cause) +{ + switch (cause) { + case AST_CAUSE_UNALLOCATED: + case AST_CAUSE_NO_ROUTE_DESTINATION: + case AST_CAUSE_NO_ROUTE_TRANSIT_NET: + return "404 Not Found"; + case AST_CAUSE_CONGESTION: + case AST_CAUSE_SWITCH_CONGESTION: + return "503 Service Unavailable"; + case AST_CAUSE_NO_USER_RESPONSE: + return "408 Request Timeout"; + case AST_CAUSE_NO_ANSWER: + case AST_CAUSE_UNREGISTERED: + return "480 Temporarily Unavailable"; + case AST_CAUSE_CALL_REJECTED: + return "403 Forbidden"; + case AST_CAUSE_NUMBER_CHANGED: + return "410 Gone"; + case AST_CAUSE_NORMAL_UNSPECIFIED: + return "480 Temporarily Unavailable"; + case AST_CAUSE_INVALID_NUMBER_FORMAT: + return "484 Address Incomplete"; + case AST_CAUSE_USER_BUSY: + return "486 Busy Here"; + case AST_CAUSE_FAILURE: + return "500 Internal Server Error"; + case AST_CAUSE_FACILITY_REJECTED: + return "501 Not Implemented"; + case AST_CAUSE_CHAN_NOT_IMPLEMENTED: + return "503 Service Unavailable"; + case AST_CAUSE_DESTINATION_OUT_OF_ORDER: + return "502 Bad Gateway"; + case AST_CAUSE_BEARERCAPABILITY_NOTAVAIL: /* Can't find codec to connect to host */ + return "488 Not Acceptable Here"; + case AST_CAUSE_INTERWORKING: /* Unspecified Interworking issues */ + return "500 Internal Server Error"; + case AST_CAUSE_NOTDEFINED: + default: + return NULL; + } +} + +/* Return protocol string for srv dns query */ +const char *sip_srv_protocol(enum ast_transport transport) +{ + switch (transport) { + case AST_TRANSPORT_UDP: + return "udp"; + case AST_TRANSPORT_TLS: + case AST_TRANSPORT_TCP: + return "tcp"; + case AST_TRANSPORT_WS: + case AST_TRANSPORT_WSS: + break; + } + + return "unknown"; +} + +/* Return service string for srv dns query */ +const char *sip_srv_service(enum ast_transport transport) +{ + switch (transport) { + case AST_TRANSPORT_TCP: + case AST_TRANSPORT_UDP: + return "sip"; + case AST_TRANSPORT_TLS: + return "sips"; + case AST_TRANSPORT_WS: + case AST_TRANSPORT_WSS: + break; + } + + return "unknown"; +} + +const char *sip_method2str(int method) +{ + switch (method) { + case SIP_METHOD_ACK: + return "ACK"; + case SIP_METHOD_BYE: + return "BYE"; + case SIP_METHOD_CANCEL: + return "CANCEL"; + case SIP_METHOD_INVITE: + return "INVITE"; + case SIP_METHOD_MESSAGE: + return "MESSAGE"; + case SIP_METHOD_NOTIFY: + return "NOTIFY"; + case SIP_METHOD_OPTIONS: + return "OPTIONS"; + case SIP_METHOD_PUBLISH: + return "PUBLISH"; + case SIP_METHOD_REFER: + return "REFER"; + case SIP_METHOD_REGISTER: + return "REGISTER"; + case SIP_METHOD_SUBSCRIBE: + return "SUBSCRIBE"; + case SIP_METHOD_UPDATE: + return "UPDATE"; + default: + return "UNKNOWN"; + } +} + +/* Find SIP method by name */ +int sip_find_method(const char *method) +{ + /* Ordered in most to least often received */ + if (!strcmp(method, "OPTIONS")) { + return SIP_METHOD_OPTIONS; + } else if (!strcmp(method, "NOTIFY")) { + return SIP_METHOD_NOTIFY; + } else if (!strcmp(method, "INVITE")) { + return SIP_METHOD_INVITE; + } else if (!strcmp(method, "ACK")) { + return SIP_METHOD_ACK; + } else if (!strcmp(method, "BYE")) { + return SIP_METHOD_BYE; + } else if (!strcmp(method, "CANCEL")) { + return SIP_METHOD_CANCEL; + } else if (!strcmp(method, "REGISTER")) { + return SIP_METHOD_REGISTER; + } else if (!strcmp(method, "REFER")) { + return SIP_METHOD_REFER; + } else if (!strcmp(method, "UPDATE")) { + return SIP_METHOD_UPDATE; + } else if (!strcmp(method, "PUBLISH")) { + return SIP_METHOD_PUBLISH; + } else if (!strcmp(method, "MESSAGE")) { + return SIP_METHOD_MESSAGE; + } else if (!strcmp(method, "INFO")) { + return SIP_METHOD_INFO; + } + + return 0; +} + +/* Parse list of method names to see which ones are allowed */ +static int sip_parse_methods(char *allow) +{ + char *method; + int methods; + + ast_debug(2, "Parsing methods '%s'\n", allow); + methods = 0; + + while ((method = strsep(&allow, ","))) { + method = ast_strip(method); + methods |= sip_find_method(method); + } + + return methods; +} + +char *sip_methods2str(unsigned int methods) +{ + struct ast_str *methods_buf; + + if (!(methods_buf = ast_str_thread_get(&sip_methods2str_buf, 128))) { + return ""; + } + + ast_str_reset(methods_buf); + + if (methods & SIP_METHOD_ACK) { + ast_str_append(&methods_buf, 0, "%s", "ACK"); + } + + if (methods & SIP_METHOD_BYE) { + ast_str_append(&methods_buf, 0, "%s%s", ast_str_strlen(methods_buf) ? "," : "", "BYE"); + } + + if (methods & SIP_METHOD_CANCEL) { + ast_str_append(&methods_buf, 0, "%s%s", ast_str_strlen(methods_buf) ? "," : "", "CANCEL"); + } + + if (methods & SIP_METHOD_INVITE) { + ast_str_append(&methods_buf, 0, "%s%s", ast_str_strlen(methods_buf) ? "," : "", "INVITE"); + } + + if (methods & SIP_METHOD_MESSAGE) { + ast_str_append(&methods_buf, 0, "%s%s", ast_str_strlen(methods_buf) ? "," : "", "MESSAGE"); + } + + if (methods & SIP_METHOD_NOTIFY) { + ast_str_append(&methods_buf, 0, "%s%s", ast_str_strlen(methods_buf) ? "," : "", "NOTIFY"); + } + + if (methods & SIP_METHOD_OPTIONS) { + ast_str_append(&methods_buf, 0, "%s%s", ast_str_strlen(methods_buf) ? "," : "", "OPTIONS"); + } + + if (methods & SIP_METHOD_PUBLISH) { + ast_str_append(&methods_buf, 0, "%s%s", ast_str_strlen(methods_buf) ? "," : "", "PUBLISH"); + } + + if (methods & SIP_METHOD_REFER) { + ast_str_append(&methods_buf, 0, "%s%s", ast_str_strlen(methods_buf) ? "," : "", "REFER"); + } + + if (methods & SIP_METHOD_REGISTER) { + ast_str_append(&methods_buf, 0, "%s%s", ast_str_strlen(methods_buf) ? "," : "", "REGISTER"); + } + + if (methods & SIP_METHOD_SUBSCRIBE) { + ast_str_append(&methods_buf, 0, "%s%s", ast_str_strlen(methods_buf) ? "," : "", "SUBSCRIBE"); + } + + if (methods & SIP_METHOD_UPDATE) { + ast_str_append(&methods_buf, 0, "%s%s", ast_str_strlen(methods_buf) ? "," : "", "UPDATE"); + } + + return ast_str_buffer(methods_buf); +} + +/* Parse Supported or Require header, any unsupported options are copied into the output string */ +static int sip_parse_options(char *supported, struct ast_str **unsupported) +{ + char *option; + int options; + + ast_debug(2, "Parsing options '%s'\n", supported); + options = 0; + + while ((option = strsep(&supported, ","))) { + /* Trim leading and trailing whitespace */ + option = ast_strip(option); + + if (!strcmp(option, "replaces")) { + options |= SIP_OPTION_REPLACES; /* RFC3891: Replaces: header for transfer */ + } else if (!strcmp(option, "timer")) { + options |= SIP_OPTION_TIMER; /* RFC4028: SIP Session-Timers */ + } else if (!strcmp(option, "norefersub")) { + continue; + } else if (!strncmp(option, "X-cisco-", 8)) { + continue; + } else { + ast_debug(3, "Unsupported option '%s'\n", option); + /* If option is not supported, add to unsupported out buffer */ + ast_str_append(unsupported, 0, "%s%s", ast_str_strlen(*unsupported) ? "," : "", option); + } + } + + ast_debug(3, "Supported options '%s'\n", sip_options2str(options)); + return options; +} + +/* Return supported options as a comma separated string */ +char *sip_options2str(unsigned int options) +{ + struct ast_str *options_buf; + + if (!(options_buf = ast_str_thread_get(&sip_options2str_buf, 64))) { + return ""; + } + + ast_str_reset(options_buf); + + if (options & SIP_OPTION_REPLACES) { + ast_str_append(&options_buf, 0, "%s", "replaces"); + } + + if (options & SIP_OPTION_TIMER) { + ast_str_append(&options_buf, 0, "%s%s", ast_str_strlen(options_buf) ? "," : "", "timer"); + } + + return ast_str_buffer(options_buf); +} + +/* Get the URI parse from a contact */ +char *sip_get_uri(char *uri) +{ + char *sep; + + ast_debug(1, "Parsing URI '%s'\n", uri); + uri = ast_skip_blanks(uri); + + if (ast_strlen_zero(uri)) { + return ""; + } + + /* Skip past name if present */ + if (*uri == '"') { + uri++; + + if (!(uri = strchr(uri, '"'))) { + ast_debug(2, "Missing '\"' in URI\n"); + return ""; + } + + uri++; + uri = ast_skip_blanks(uri); + } + + if (*uri != '<') { + ast_debug(2, "Missing '<' in URI\n"); + return ""; + } + + uri++; + + if (!(sep = strchr(uri, '>'))) { + ast_debug(2, "Missing '>' in URI\n"); + return ""; + } + + *sep = '\0'; + return uri; +} + +/* Parse a 'sip:user@domain;parameters?headers' URI into separate fields */ +int sip_parse_uri(char *uri, char **scheme, char **user, char **domain, char **parameters, char **headers) +{ + /* Initialize optional parameters */ + if (scheme) { + *scheme = ""; + } + + if (user) { + *user = ""; + } + + if (domain) { + *domain = ""; + } + + if (parameters) { + *parameters = ""; + } + + if (headers) { + *headers = ""; + } + + uri = ast_skip_blanks(uri); + ast_debug(2, "Parsing URI '%s'\n", uri); + + /* Get scheme */ + if (!strncmp(uri, "sip:", 4)) { + if (scheme) { + *scheme = uri; + } + + uri += 3; + *uri++ = '\0'; + } else if (!strncmp(uri, "sips:", 5)) { + if (scheme) { + *scheme = uri; + } + + uri += 4; + *uri++ = '\0'; + } else { + ast_debug(2, "URI scheme is not 'sip:' or 'sips:'\n"); + return -1; + } + + if (user) { + *user = uri; + } + + if (domain) { + *domain = uri; + } + + /* Get user or domain */ + while (*uri && *uri != '@' && *uri != ';' && *uri != '?') { + uri++; + } + + if (*uri == '@') { + *uri++ = '\0'; + + if (domain) { + *domain = uri; + } + } else { + /* No user */ + if (user) { + *user = ""; + } + } + + /* Get domain parameters or uri parameters */ + while (*uri && *uri != ';' && *uri != '?') { + uri++; + } + + if (*uri == ';') { + *uri++ = '\0'; + + if (parameters) { + *parameters = uri; + } + } + + /* Get uri parameters */ + while (*uri && *uri != '?') { + uri++; + } + + if (*uri == '?') { + *uri++ = '\0'; + + if (headers) { + *headers = uri; + } + } + + if (user && **user) { + ast_uri_decode(*user, ast_uri_sip_user); + } + + if (domain && **domain) { + ast_uri_decode(*domain, ast_uri_sip_user); + } + + return 0; +} + +/* Compare two URIs to see if they are the same */ +int sip_cmp_uri(const char *original_uri1, const char *original_uri2) +{ + char *uri1, *uri2, *scheme1, *scheme2, *user1, *user2, *domain1, *domain2, *parameters1, *parameters2, + *headers1, *headers2; + struct ast_sockaddr address1, address2; + int address1_parsed, address2_parsed; + + ast_debug(3, "Comparing URIs '%s' to '%s'\n", original_uri1, original_uri2); + + if (!original_uri1 || !original_uri2) { + return 1; + } + + uri1 = ast_strdupa(original_uri1); + uri2 = ast_strdupa(original_uri2); + + if (sip_parse_uri(uri1, &scheme1, &user1, &domain1, ¶meters1, &headers1)) { + return 1; + } + + if (sip_parse_uri(uri2, &scheme2, &user2, &domain2, ¶meters2, &headers2)) { + return 1; + } + + if (strcmp(scheme1, scheme2)) { + return 1; + } + + if (strcmp(user1, user2)) { + return 1; + } + + address1_parsed = ast_sockaddr_parse(&address1, domain1, 0); + address2_parsed = ast_sockaddr_parse(&address2, domain2, 0); + + if (address1_parsed != address2_parsed) { + /* One was an IP address and the other had a domain */ + return 1; + } else if (!address1_parsed) { + /* Both are domain names. A string comparison will work perfectly here */ + if (strcasecmp(domain1, domain2)) { + return 1; + } + } else if (ast_sockaddr_cmp(&address1, &address2)) { + return 1; + } + + if (sip_cmp_parameters(parameters1, parameters2, ';')) { + return 1; + } + + if (sip_cmp_parameters(headers1, headers2, '&')) { + return 1; + } + + return 0; +} + +/* Compare a delimited list of parameters with another list of parameters, only use to compare URI parameters */ +static int sip_cmp_parameters(char *original_parameters1, char *original_parameters2, char sep) +{ + char *parameters1[64], *parameters2[64], *parameters; + int count1, count2, i1, i2; + + ast_debug(3, "Comparing parameters '%s' to '%s'\n", original_parameters1, original_parameters2); + + /* No parameters to check. We can't compare lengths as the parameters may have been encoded */ + if (ast_strlen_zero(original_parameters1) && ast_strlen_zero(original_parameters2)) { + return 0; + } + + parameters = original_parameters1; + + for (count1 = 0; count1 < ARRAY_LEN(parameters1) && !ast_strlen_zero(parameters); count1++) { + parameters1[count1] = parameters; + + if ((parameters = strchr(parameters, sep))) { + *parameters++ = '\0'; + } + } + + parameters = original_parameters2; + + for (count2 = 0; count2 < ARRAY_LEN(parameters2) && !ast_strlen_zero(parameters); count2++) { + parameters2[count2] = parameters; + + if ((parameters = strchr(parameters, sep))) { + *parameters++ = '\0'; + } + } + + if (count1 == ARRAY_LEN(parameters1) || count2 == ARRAY_LEN(parameters2)) { + return -1; + } else if (count1 != count2) { + return 1; /* Different number of parameters therefore they are different */ + } + + /* Horrible O(N) check. Becase we have checked that the number of parameters is the same, then every parameter + * in the the first list must be in the second list */ + for (i1 = 0; i1 < count1; i1++) { + for (i2 = 0; i2 < count2; i2++) { + if (!strcasecmp(parameters1[i1], parameters2[i2])) { + break; + } + } + + /* Reached end of list, therefore no match */ + if (i2 == count2) { + return 1; + } + } + + return 0; +} + +/* Parse parameter values from a parameter list */ +int sip_parse_parameters(char *parameters, const char sep, ...) +{ + va_list args; + struct { + char *name; + char **value; + } variables[64]; + int count; + + va_start(args, sep); + + for (count = 0; count < ARRAY_LEN(variables); count++) { + if (!(variables[count].name = va_arg(args, char *))) { + break; + } + + variables[count].value = va_arg(args, char **); + *variables[count].value = ""; /* Initialize to an empty value */ + } + + va_end(args); + + if (count == ARRAY_LEN(variables)) { + return -1; + } else if (ast_strlen_zero(parameters)) { + /* No parameters specified, but we have set default values for the variables */ + return 0; + } + + ast_debug(2, "Parsing parameters '%s'\n", parameters); + + while (!ast_strlen_zero(parameters)) { + char *name, *value; + int i; + + name = parameters; + + while (*parameters && *parameters != '=' && *parameters != sep) { + parameters++; + } + + /* We have a value */ + if (*parameters == '=') { + *parameters++ = '\0'; + parameters = ast_skip_blanks(parameters); + + if (*parameters == '"') { + parameters++; + value = parameters; /* Quoted value */ + + while (*parameters && *parameters != '"') { + parameters++; + } + + if (*parameters != '"') { + ast_debug(1, "Missing '\"' from parameter value\n"); + return -1; + } + + *parameters++ = '\0'; + parameters = ast_skip_blanks(parameters); + + if (*parameters) { + /* Allow only whitespace after quote */ + if (*parameters != sep) { + ast_debug(1, "Invalid end of paramter value\n"); + return -1; + } + + *parameters++ = '\0'; + } + } else { + value = parameters; /* Unquoted value */ + + while (*parameters && *parameters != sep) { + parameters++; + } + + if (*parameters == sep) { + *parameters++ = '\0'; + } + + value = ast_strip(value); + } + + ast_uri_decode(value, ast_uri_sip_user); + } else { + value = NULL; /* Empty value */ + + while (*parameters && *parameters != sep) { + parameters++; + } + + if (*parameters == sep) { + *parameters++ = '\0'; + } + } + + name = ast_strip(name); + ast_uri_decode(name, ast_uri_sip_user); + + ast_debug(4, "Parameter '%s' is '%s'\n", name, value); + + for (i = 0; i < count; i++) { + if (!strcasecmp(variables[i].name, name)) { + /* Parameters without a value will be set to their name */ + if (value) { + *variables[i].value = value; + } else { + *variables[i].value = variables[i].name; + } + + break; + } + } + } + + return 0; +} + +/* Parse a '"Name" ;headers' contact into separate parts */ +int sip_parse_contact(char *contact, char **name, char **user, char **domain, char **parameters, char **headers) +{ + char *scheme; + + /* Initialize optional parameters */ + if (name) { + *name = ""; + } + + if (user) { + *user = ""; + } + + if (domain) { + *domain = ""; + } + + if (parameters) { + *parameters = ""; + } + + if (headers) { + *headers = ""; + } + + contact = ast_skip_blanks(contact); + ast_debug(2, "Parsing contact '%s'\n", contact); + + /* Get name */ + if (*contact == '"') { + *contact++ = '\0'; + + if (name) { + *name = contact; + } + + if (!(contact = strchr(contact, '"'))) { + ast_debug(2, "Missing '\"' in contact\n"); + return -1; + } + + *contact++ = '\0'; + contact = ast_skip_blanks(contact); + } else { + if (name) { + *name = contact; + } + + while (*contact && *contact != '<') { + contact++; + } + } + + if (*contact != '<') { + ast_debug(2, "Missing '<' in contact\n"); + return -1; + } + + *contact++ = '\0'; + + /* If name is empty, treat is as missing */ + if (name) { + *name = ast_strip(*name); + + if (ast_strlen_zero(*name)) { + *name = ""; + } + } + + scheme = strsep(&contact, ":"); + + /* Check for a valid scheme */ + if (strcmp(scheme, "sip") && strcmp(scheme, "sips")) { + ast_debug(2, "Invalid URI scheme: '%s'\n", scheme); + return -1; + } + + if (user) { + *user = contact; + } + + if (domain) { + *domain = contact; + } + + /* Get either user or domain */ + while (*contact && *contact != '>' && *contact != '@' && *contact != ';') { + contact++; + } + + if (!*contact) { + ast_debug(2, "Missing '>' in contact\n"); + return -1; + } + + /* Get domain */ + if (*contact == '@') { + *contact++ = '\0'; + + if (domain) { + *domain = contact; + } + } else { + /* Domain only */ + if (user) { + *user = ""; + } + } + + while (*contact && *contact != '>' && *contact != ';') { + contact++; + } + + /* Get parameters */ + if (*contact == ';') { + *contact++ = '\0'; + + if (parameters) { + *parameters = contact; + } + } + + while (*contact && *contact != '>') { + contact++; + } + + if (*contact != '>') { + ast_debug(2, "Missing '>' in contact\n"); + return -1; + } + + *contact++ = '\0'; + + /* Get headers */ + if (*contact == ';') { + *contact++ = '\0'; + + if (headers) { + *headers = contact; + } + } + + if (name && **name) { + ast_unescape_quoted(*name); + } + + if (user && **user) { + ast_uri_decode(*user, ast_uri_sip_user); + } + + if (domain && **domain) { + ast_uri_decode(*domain, ast_uri_sip_user); + } + + return 0; +} + +/* Get tag from header */ +char *sip_parse_tag(char *contact) +{ + char *tag, *headers; + + if (sip_parse_contact(contact, NULL, NULL, NULL, NULL, &headers)) { + return ""; + } + + sip_parse_parameters(headers, ';', "tag", &tag, NULL); + + if (ast_strlen_zero(tag)) { + return NULL; + } + + return tag; +} + +/* Parse the transport type from a contacts domain parameters */ +enum ast_transport sip_parse_transport(char *parameters) +{ + char *transport; + + sip_parse_parameters(parameters, ';', "transport", &transport, NULL); + + if (ast_strlen_zero(transport)) { + return 0; + } else if (!strcasecmp(transport, "udp")) { + return AST_TRANSPORT_UDP; + } else if (!strcasecmp(transport, "tcp")) { + return AST_TRANSPORT_TCP; + } else if (!strcasecmp(transport, "tls")) { + return AST_TRANSPORT_TLS; + } else { + ast_debug(2, "Unknown transport '%s'\n", transport); + return 0; + } +} + +int sip_parse_port(char *host, int *port) +{ + char *sep; + + *port = 0; + + if (!(sep = strchr(host, ':'))) { + return 0; + } + + *sep++ = '\0'; + + if (sscanf(sep, "%d", port) != 1 || *port < 1 || *port > 65535) { + return -1; + } + + return 0; +} + +/* Parse the content-type and boundary from a Content-Type header */ +char *sip_parse_content_type(char *content_type, char **boundary) +{ + char *parameters; + + if ((parameters = strchr(content_type, ';'))) { + *parameters++ = '\0'; + } + + content_type = ast_strip(content_type); + + if (!strcmp(content_type, "multipart/mixed")) { + sip_parse_parameters(parameters, ';', "boundary", boundary, NULL); + } else { + *boundary = ""; + } + + return content_type; +} + +/* Get address and port from a URI */ +int sip_get_uri_address(const char *original_uri, struct ast_sockaddr *address) +{ + char *uri, *domain, *parameters; + + /* Work on a copy */ + uri = ast_strdupa(original_uri); + + if (sip_parse_uri(uri, NULL, NULL, &domain, ¶meters, NULL)) { + ast_debug(1, "Unable to parse '%s'\n", original_uri); + return -1; + } + + if (ast_sockaddr_resolve_first_af(address, domain, 0, AST_AF_INET)) { + ast_debug(1, "Invalid host name in Contact: '%s' DNS lookup failed\n", domain); + return -1; + } + + /* Set port */ + if (!ast_sockaddr_port(address)) { + int use_tls = sip_parse_transport(parameters) == AST_TRANSPORT_TLS; + + ast_sockaddr_set_port(address, use_tls ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + } + + return 0; +} + +/* Parse VIA header into parts */ +int sip_parse_via(struct sip_message *message) +{ + char *via, *branch, *maddr, *ttl, *rport; + + via = ast_strdupa(sip_message_find_header(message, "Via")); + via = strsep(&via, ","); /* Only use the first, leftmost neader */ + + if (ast_strlen_zero(via)) { + ast_debug(1, "Missing 'Via:' header\n"); + return -1; + } + + if (strncmp(via, "SIP/2.0/", 8)) { + ast_debug(1, "Not a Via header: %s\n", via); + return -1; + } + + via += 8; + + via = ast_skip_nonblanks(via); + via = ast_skip_blanks(via); + + ast_free(message->via_sent_by); + ast_free(message->via_branch); + ast_free(message->via_maddr); + + message->via_sent_by = ast_strdup(strsep(&via, ";")); + sip_parse_parameters(via, ';', "branch", &branch, "maddr", &maddr, "ttl", &ttl, "rport", &rport, NULL); + + if (!ast_strlen_zero(branch)) { + message->via_branch = ast_strdup(branch); + } else { + message->via_branch = NULL; + } + + if (!ast_strlen_zero(maddr)) { + message->via_maddr = ast_strdup(maddr); + } else { + message->via_maddr = NULL; + } + + message->via_rport = !strcmp(rport, "rport"); /* Query, not answer */ + message->via_ttl = atoi(ttl); + return 0; +} + +/* Parse an Authorization header into parts */ +int sip_parse_authorization(struct sip_dialog *dialog, char *authorization) +{ + char *algorithm, *uri, *username, *realm, *domain, *nonce, *opaque, *qop, *response; + + if (strncmp(authorization, "Digest ", 7)) { + ast_debug(1, "Invalid Authorization: header '%s'\n", authorization); + return -1; + } + + authorization += 7; + sip_parse_parameters(authorization, ',', "algorithm", &algorithm, "uri", &uri, "username", &username, + "realm", &realm, "domain", &domain, "nonce", &nonce, "opaque", &opaque, "qop", &qop, + "response", &response, NULL); + + if (strcmp(algorithm, "MD5")) { + ast_debug(1, "Invalid Authorization: algorithm '%s'\n", algorithm); + return -1; + } + + ast_string_field_set(dialog, authorization_uri, uri); + ast_string_field_set(dialog, authorization_username, username); + ast_string_field_set(dialog, authorization_realm, realm); + ast_string_field_set(dialog, authorization_domain, domain); + ast_string_field_set(dialog, authorization_nonce, nonce); + ast_string_field_set(dialog, authorization_opaque, opaque); + ast_string_field_set(dialog, authorization_qop, qop); + ast_string_field_set(dialog, authorization_response, response); + + return 0; +} + +/* Parse the Allow header to see what methods the endpoint we are communicating with allows */ +int sip_parse_allow(struct sip_dialog *dialog, struct sip_message *message) +{ + char *allow = ast_strdupa(sip_message_find_header(message, "Allow")); + + if (ast_strlen_zero(allow)) { + return -1; + } + + dialog->allow_methods = sip_parse_methods(allow); + return 0; +} + +int sip_parse_supported(struct sip_dialog *dialog, struct sip_message *message) +{ + char *supported; + struct ast_str *unsupported; + + supported = ast_strdupa(sip_message_find_header(message, "Supported")); + + if (ast_strlen_zero(supported)) { + return -1; + } + + unsupported = ast_str_alloca(1024); + dialog->supported_options = sip_parse_options(supported, &unsupported); + ast_string_field_set(dialog, unsupported_options, ast_str_buffer(unsupported)); + + return 0; +} + +int sip_parse_require(struct sip_dialog *dialog, struct sip_message *message) +{ + char *require; + struct ast_str *unsupported; + + require = ast_strdupa(sip_message_find_header(message, "Require")); + + if (ast_strlen_zero(require)) { + return 0; /* No require header is fine */ + } + + unsupported = ast_str_alloca(1024); + dialog->require_options = sip_parse_options(require, &unsupported); + ast_string_field_set(dialog, unsupported_options, ast_str_buffer(unsupported)); + + if (!ast_strlen_zero(dialog->unsupported_options)) { + return -1; + } + + return 0; +} + +/* Parses SIP Reason header according to RFC3326 and sets channel's hangupcause if configured so and header present. + * This is used in BYE and CANCEL request and SIP response, but according to RFC3326 it could appear in any request, but + * makes not a lot of sense in others than BYE or CANCEL. Currently only implemented for Q.850 status codes */ +int sip_parse_reason(struct sip_dialog *dialog, struct sip_message *message) +{ + char *reason, *cause, *parameters; + + if (!dialog->channel || !dialog->peer->reason_support) { + return -1; + } + + reason = ast_strdupa(sip_message_find_header(message, "Reason")); + + if (ast_strlen_zero(reason)) { + return -1; + } + + if (!(parameters = strchr(reason, ';'))) { + return -1; + } + + *parameters++ = '\0'; + + if (strcmp(reason, "Q.850")) { + return -1; + } + + sip_parse_parameters(parameters, ';', "cause", &cause, NULL); + + if (ast_strlen_zero(cause)) { + return -1; + } + + ast_channel_hangupcause_set(dialog->channel, atoi(cause) & 0x7f); + + if (message->debug) { + ast_verb(3, "Using Reason header for hangup cause code: %d\n", ast_channel_hangupcause(dialog->channel)); + } + + return 0; +} + +/* Parse Path header RFC 3327 */ +int sip_parse_path(struct sip_peer *peer, struct sip_message *request) +{ + int iter = 0; + + sip_route_destroy(&peer->path); + + if (!peer->path_support) { + ast_debug(2, "Use of Path headers disabled\n"); + return -1; + } + + ast_debug(2, "Try to build pre-loaded route-set by parsing Path headers\n"); + + for (;;) { + const char *path = sip_message_next_header(request, "Path", &iter); + + if (ast_strlen_zero(path)) { + break; + } + + sip_route_parse(&peer->path, path, FALSE); + } + + /* Caches result for any dialog->route copied from peer->path */ + sip_route_is_strict(&peer->path); + return 0; +} + +/* Get name, number and presentation from Remote-Party-ID or P-Asserted-Identity header, returns true if a valid header + * was found and it was different from the current caller name/number so we can queue a connected line update */ +int sip_parse_identity(struct sip_dialog *dialog, struct sip_message *message) +{ + char *remote_party_id, *p_asserted_identity, *name, *user, *domain; + int presentation; + + if (!dialog->peer->allow_identity || dialog->peer->identity_support == SIP_IDENTITY_NONE) { + return FALSE; + } + + if (dialog->peer->identity_support == SIP_IDENTITY_REMOTE_PARTY) { + char *headers, *screen, *privacy; + + remote_party_id = ast_strdupa(sip_message_find_header(message, "Remote-Party-ID")); + + if (ast_strlen_zero(remote_party_id)) { + return FALSE; + } + + if (sip_parse_contact(remote_party_id, &name, &user, NULL, NULL, &headers)) { + ast_debug(2, "Unable to parse Remote-Party-ID:\n"); + return FALSE; + } + + sip_parse_parameters(headers, ';', "privacy", &privacy, "screen", &screen, NULL); + + if (!strcasecmp(privacy, "full")) { + if (!strcasecmp(screen, "yes")) { + presentation = AST_PRES_PROHIB_USER_NUMBER_PASSED_SCREEN; + } else { + presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; + } + } else if (!strcasecmp(screen, "yes")) { + presentation = AST_PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN; + } else { + presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED; + } + } else if (dialog->peer->identity_support == SIP_IDENTITY_P_ASSERTED) { + p_asserted_identity = ast_strdupa(sip_message_find_header(message, "P-Asserted-Identity")); + + if (ast_strlen_zero(p_asserted_identity)) { + return FALSE; + } + + if (sip_parse_contact(p_asserted_identity, &name, &user, &domain, NULL, NULL)) { + ast_debug(2, "Unable to parse P-Asserted-Identity:\n"); + return FALSE; + } + + if (!strcasecmp(sip_message_find_header(message, "Privacy"), "id")) { + presentation = AST_PRES_PROHIB_USER_NUMBER_PASSED_SCREEN; + } else if (!strcasecmp(user, "anonymous") && !strcasecmp(domain, "anonymous.invalid")) { + presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; + } else { + presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED; + } + } else { + return FALSE; + } + + if (sip_config.shrink_callerid && ast_is_shrinkable_phonenumber(user)) { + ast_shrink_phone_number(user); + } + + /* Only return true if the supplied caller id is different */ + if (!strcasecmp(dialog->caller_number, user) && + !strcasecmp(dialog->caller_name, name) && dialog->caller_presentation == presentation) { + return FALSE; + } + + ast_string_field_set(dialog, caller_name, name); + ast_string_field_set(dialog, caller_number, user); + dialog->caller_presentation = presentation; + + if (dialog->channel) { + ast_set_callerid(dialog->channel, user, name, NULL); + + ast_channel_caller(dialog->channel)->id.name.presentation = presentation; + ast_channel_caller(dialog->channel)->id.number.presentation = presentation; + } + + return TRUE; +} + +/* Parse a Divesrsion header */ +int sip_parse_diversion(struct sip_dialog *dialog, struct sip_message *message, int set_call_forward) +{ + char *from, *to, *contact, *diversion, *reason, *name, *user, *domain, *headers; + int code; + + reason = NULL; + code = 0; + + diversion = ast_strdupa(sip_message_find_header(message, "Diversion")); + + if (!ast_strlen_zero(diversion)) { + if (sip_parse_contact(diversion, &name, &user, &domain, NULL, &headers)) { + ast_debug(2, "Unable to parse Diversion:\n"); + return -1; + } + + if (dialog->channel) { + pbx_builtin_setvar_helper(dialog->channel, "__SIP_REDIRECT_DOMAIN", domain); + } + + /* Check if we have a reason parameter */ + sip_parse_parameters(headers, ';', "reason", &reason, NULL); + + if (!ast_strlen_zero(reason)) { + if (dialog->channel) { + pbx_builtin_setvar_helper(dialog->channel, "__SIP_REDIRECT_REASON", reason); + } + + if ((code = ast_redirecting_reason_parse(reason)) == -1) { + code = AST_REDIRECTING_REASON_UNKNOWN; + } else { + reason = NULL; /* Known code overrides reason */ + } + } + } else { + if (!message->response) { + return 0; + } + + from = ast_strdupa(sip_message_find_header(message, "From")); + + if (sip_parse_contact(from, &name, &user, NULL, NULL, NULL)) { + ast_debug(2, "Unable to parse From:\n"); + return -1; + } + } + + ast_string_field_set(dialog, redirecting_from_name, name); + ast_string_field_set(dialog, redirecting_from_number, user); + ast_string_field_set(dialog, redirecting_reason, reason); + dialog->redirecting_code = code; + + if (message->response) { + contact = ast_strdupa(sip_message_find_header(message, "Contact")); + + if (sip_parse_contact(contact, &name, &user, &domain, NULL, NULL)) { + ast_debug(2, "Unable to parse Contact:\n"); + return -1; + } + } else { + to = ast_strdupa(sip_message_find_header(message, "To")); + + if (sip_parse_contact(to, &name, &user, NULL, NULL, NULL)) { + ast_debug(2, "Unable to parse To:\n"); + return -1; + } + } + + ast_string_field_set(dialog, redirecting_to_name, name); + ast_string_field_set(dialog, redirecting_to_number, user); + + if (set_call_forward && dialog->channel) { + pbx_builtin_setvar_helper(dialog->channel, "SIP_DOMAIN", domain); + ast_channel_call_forward_set(dialog->channel, user); + + ast_debug(2, "Received '302 Redirect' to extension '%s' domain '%s'\n", user, domain); + } + + return 0; +} + +/* Parse Refer-To and Referred-By headers for transfer */ +int sip_parse_refer_to(struct sip_dialog *dialog, struct sip_message *message) +{ + char *refer_to, *referred_by, *user, *domain, *replaces, *headers; + const char *transfer_context; + + refer_to = ast_strdupa(sip_message_find_header(message, "Refer-To")); + refer_to = sip_get_uri(refer_to); + + if (ast_strlen_zero(refer_to)) { + return SIP_REFER_MISSING_HEADER; + } + + if (sip_parse_uri(refer_to, NULL, &user, &domain, NULL, &headers)) { + ast_debug(1, "Invalid Refer-To: skipping transfer\n"); + return SIP_REFER_INVALID_URI; + } + + domain = strsep(&domain, ":"); + + ast_string_field_set(dialog, refer_to_user, user); + ast_string_field_set(dialog, refer_to_domain, domain); + + sip_parse_parameters(headers, '&', "replaces", &replaces, NULL); + + /* Check for replaces */ + if (!ast_strlen_zero(replaces)) { + char *call_id, *from_tag, *to_tag; + + call_id = strsep(&replaces, ";"); + sip_parse_parameters(replaces, ';', "from-tag", &from_tag, "to-tag", &to_tag, NULL); + + if (!strcmp(dialog->call_id, call_id) && + !strcmp(dialog->remote_tag, from_tag) && !strcmp(dialog->local_tag, to_tag)) { + ast_debug(1, "Attempted to replace own Call-ID: '%s'\n", dialog->call_id); + return SIP_REFER_EXTEN_NOT_FOUND; + } + + ast_string_field_set(dialog, replaces_call_id, call_id); + ast_string_field_set(dialog, replaces_from_tag, from_tag); + ast_string_field_set(dialog, replaces_to_tag, to_tag); + + /* This is an attended transfer */ + dialog->attended_transfer = TRUE; + + ast_debug(2, "Attended transfer: Will use Replaces: Call-ID: '%s' From: tag='%s' To: tag='%s'\n", + dialog->replaces_call_id, dialog->replaces_from_tag, dialog->replaces_to_tag); + } + + /* Get referred by header if it exists */ + referred_by = ast_strdupa(sip_message_find_header(message, "Referred-By")); + referred_by = sip_get_uri(referred_by); + + if (!ast_strlen_zero(referred_by)) { + ast_uri_decode(referred_by, ast_uri_sip_user); + + if (strncasecmp(referred_by, "sip:", 4) && strncasecmp(referred_by, "sips:", 5)) { + ast_debug(1, "Invalid Referred-By: '%s', skipping\n", referred_by); + } else { + ast_string_field_build(dialog, referred_by, "<%s>", referred_by); + } + } else { + ast_string_field_set(dialog, referred_by, NULL); + } + + transfer_context = NULL; + + /* Give useful transfer information to the dialplan */ + if (dialog->channel) { + RAII_VAR(struct ast_channel *, bridge_channel, NULL, ast_channel_cleanup); + RAII_VAR(struct ast_channel *, relocked_channel, NULL, ast_channel_cleanup); + RAII_VAR(struct ast_channel *, channel, NULL, ast_channel_cleanup); + + /* Grab a reference to dialog->channel to prevent it from going away */ + channel = ast_channel_ref(dialog->channel); + + /* Established locking order here is bridge, channel, dialog and the bridge will be locked during + * ast_channel_bridge_peer */ + ast_channel_unlock(channel); + ao2_unlock(dialog); + + if ((bridge_channel = ast_channel_bridge_peer(channel))) { + pbx_builtin_setvar_helper(bridge_channel, "SIP_REFERRING_CONTEXT", S_OR(dialog->peer->context, NULL)); + pbx_builtin_setvar_helper(bridge_channel, "__SIP_REFERRED_BY", S_OR(referred_by, NULL)); + } + + if (!(relocked_channel = sip_dialog_lock_with_channel(dialog))) { + ast_debug(3, "Unable to reacquire owner channel lock, channel is gone\n"); + return SIP_REFER_EXTEN_NOT_FOUND; + } + + /* Determine transfer context. By default, use the context in the channel sending the REFER */ + transfer_context = pbx_builtin_getvar_helper(dialog->channel, "TRANSFER_CONTEXT"); + } + + if (ast_strlen_zero(transfer_context)) { + transfer_context = dialog->peer->context; + } + + ast_string_field_set(dialog, refer_to_context, transfer_context); + + /* Either an existing extension or the parking extension */ + if (dialog->attended_transfer || ast_exists_extension(NULL, transfer_context, user, 1, NULL)) { + ast_debug(1, "SIP transfer to extension '%s@%s' by '%s'\n", + user, transfer_context, S_OR(referred_by, "Unknown")); + /* We are ready to transfer to the extension */ + return SIP_REFER_EXTEN_FOUND; + } + + ast_debug(1, "Failed SIP Transfer to non-existing extension %s in context %s\n n", user, transfer_context); + /* Failure, we can't find this extension */ + return SIP_REFER_EXTEN_NOT_FOUND; +} + +/* Function for parsing Session-Expires header */ +int sip_parse_session_expires(struct sip_dialog *dialog, struct sip_message *message) +{ + char *session_expires, *parameters; + + session_expires = ast_strdupa(sip_message_find_header(message, "Session-Expires")); + + if (ast_strlen_zero(session_expires)) { + return -1; + } + + if ((parameters = strchr(session_expires, ';'))) { + *parameters++ = '\0'; + } + + if (sscanf(session_expires, "%30d", &dialog->session_timer_expires) != 1) { + ast_debug(1, "Invalid expires: %s\n", session_expires); + return -1; + } + + dialog->session_timer_refresher = SIP_SESSION_TIMER_REFRESHER_AUTO; + + if (!ast_strlen_zero(parameters)) { + char *refresher; + + sip_parse_parameters(parameters, ';', "refresher", &refresher, NULL); + + if (!ast_strlen_zero(refresher)) { + if (!strncasecmp(refresher, "uac", 3)) { + dialog->session_timer_refresher = SIP_SESSION_TIMER_REFRESHER_UAC; + } else if (!strncasecmp(refresher, "uas", 3)) { + dialog->session_timer_refresher = SIP_SESSION_TIMER_REFRESHER_UAS; + } else { + ast_debug(1, "Invalid session refresher '%s'\n", refresher); + return -1; + } + } + } + + return 0; +} + +/* Function for parsing Min-SE header */ +int sip_parse_min_se(struct sip_dialog *dialog, struct sip_message *message) +{ + const char *min_se = sip_message_find_header(message, "Min-SE"); + + if (ast_strlen_zero(min_se)) { + return -1; + } + + if (sscanf(min_se, "%30d", &dialog->session_timer_expires) != 1) { + ast_debug(1, "Parsing of Min-SE header failed '%s'\n", min_se); + return -1; + } + + return 0; +} + +/* Parse Cisco RTP stats headers if QRT is enabled */ +void sip_parse_rtp_stats(struct sip_dialog *dialog, struct sip_message *message) +{ + ast_string_field_set(dialog->peer, rtp_rx_stat, sip_message_find_header(message, "RTP-RxStat")); + ast_string_field_set(dialog->peer, rtp_tx_stat, sip_message_find_header(message, "RTP-TxStat")); + + if (dialog->send_qrt_url) { + sip_peer_send_qrt_url(dialog->peer); + } +} + +/* Set hangup source and cause. dialog and dialog->channel are locked */ +void sip_queue_hangup_cause(struct sip_dialog *dialog, int cause) +{ + struct ast_channel *channel = dialog->channel; + + /* Cannot hold any channel/private locks when calling */ + ast_channel_ref(channel); + ast_channel_unlock(channel); + ao2_unlock(dialog); + + ast_set_hangupsource(channel, ast_channel_name(channel), 0); + + if (cause) { + ast_queue_hangup_with_cause(channel, cause); + } else { + ast_queue_hangup(channel); + } + + ast_channel_unref(channel); + + /* Relock things */ + if ((channel = sip_dialog_lock_with_channel(dialog))) { + ast_channel_unref(channel); + } +} + +/* Try setting the codecs suggested by the SIP_CODEC channel variable */ +void sip_try_suggested_codec(struct sip_dialog *dialog) +{ + char *codecs, *codec; + int first_codec; + RAII_VAR(struct ast_format_cap *, joint_format_cap, NULL, ao2_cleanup); + + if (dialog->originated_call) { + codecs = ast_strdupa(S_OR(pbx_builtin_getvar_helper(dialog->channel, "SIP_CODEC_OUTBOUND"), "")); + } else { + codecs = ast_strdupa(S_OR(pbx_builtin_getvar_helper(dialog->channel, "SIP_CODEC_INBOUND"), "")); + + if (ast_strlen_zero(codecs)) { + codecs = ast_strdupa(S_OR(pbx_builtin_getvar_helper(dialog->channel, "SIP_CODEC"), "")); + } + } + + if (ast_strlen_zero(codecs)) { + return; + } + + if (!(joint_format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + return; + } + + ast_format_cap_append_from_cap(joint_format_cap, dialog->joint_format_cap, AST_MEDIA_TYPE_UNKNOWN); + first_codec = TRUE; + + while ((codec = strsep(&codecs, ","))) { + RAII_VAR(struct ast_format *, format, NULL, ao2_cleanup); + + codec = ast_strip(codec); + + if (!(format = ast_format_cache_get(codec))) { + ast_log(AST_LOG_NOTICE, + "Ignoring ${SIP_CODEC*} variable because of unknown codec '%s'\n", codec); + continue; + } + + if (ast_format_cap_iscompatible_format(joint_format_cap, format) != AST_FORMAT_CMP_NOT_EQUAL) { + if (first_codec) { + ast_verb(4, "Set codec to '%s' for this call because of ${SIP_CODEC*}\n", codec); + + ast_format_cap_remove_by_type(dialog->joint_format_cap, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append(dialog->joint_format_cap, format, 0); + + ast_format_cap_remove_by_type(dialog->format_cap, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append(dialog->format_cap, format, 0); + + first_codec = FALSE; + } else { + ast_verb(4, "Add codec to '%s' for this call because of ${SIP_CODEC*}\n", codec); + + /* Add the format to the capabilities structure */ + ast_format_cap_append(dialog->joint_format_cap, format, 0); + ast_format_cap_append(dialog->format_cap, format, 0); + } + } else { + ast_log(AST_LOG_NOTICE, + "Ignoring ${SIP_CODEC*} codec '%s' because it is not shared by both ends\n", codec); + } + } + + /* The original joint formats may have contained negotiated parameters (fmtp) like the Opus Codec or iLBC 20. + * The cached formats contain the default parameters, which could be different than the negotiated (joint) + * result */ + ast_format_cap_replace_from_cap(dialog->joint_format_cap, joint_format_cap, AST_MEDIA_TYPE_UNKNOWN); +} + +/* Find the channel that is causing the RINGING update, ref'd */ +struct ast_channel *sip_find_ringing_channel(struct ao2_container *device_state) +{ + struct ao2_iterator iter; + struct ast_device_state_info *device_state_info; + struct ast_channel *channel; + struct timeval creation_time; + + if (!device_state) { + return NULL; + } + + channel = NULL; + creation_time = ast_tv(0, 0); + + /* Iterate ringing devices and get the oldest of all causing channels */ + iter = ao2_iterator_init(device_state, 0); + + while ((device_state_info = ao2_iterator_next(&iter))) { + if (!device_state_info->causing_channel || + (device_state_info->device_state != AST_DEVICE_RINGING && + device_state_info->device_state != AST_DEVICE_RINGINUSE)) { + ao2_ref(device_state_info, -1); + continue; + } + + ast_channel_lock(device_state_info->causing_channel); + + if (ast_tvzero(creation_time) || + ast_tvcmp(ast_channel_creationtime(device_state_info->causing_channel), creation_time) < 0) { + channel = device_state_info->causing_channel; + creation_time = ast_channel_creationtime(channel); + } + + ast_channel_unlock(device_state_info->causing_channel); + ao2_ref(device_state_info, -1); + } + + ao2_iterator_destroy(&iter); + return channel ? ast_channel_ref(channel) : NULL; +} + +/* Set REDIRECTING information for a channel */ +void sip_set_redirecting(struct sip_dialog *dialog) +{ + struct ast_party_redirecting redirecting; + struct ast_set_party_redirecting update; + + ast_party_redirecting_init(&redirecting); + memset(&update, 0, sizeof(update)); + + if (!ast_strlen_zero(dialog->redirecting_from_name)) { + update.from.name = TRUE; + redirecting.from.name.str = (char *) dialog->redirecting_from_name; + redirecting.from.name.valid = TRUE; + } + + if (!ast_strlen_zero(dialog->redirecting_from_name)) { + update.from.number = TRUE; + redirecting.from.name.str = (char *) dialog->redirecting_from_name; + redirecting.from.name.valid = TRUE; + } + + if (!ast_strlen_zero(dialog->redirecting_to_name)) { + update.to.name = TRUE; + redirecting.to.name.str = (char *) dialog->redirecting_to_name; + redirecting.to.name.valid = TRUE; + } + + if (!ast_strlen_zero(dialog->redirecting_to_name)) { + update.to.number = TRUE; + redirecting.to.name.str = (char *) dialog->redirecting_to_name; + redirecting.to.name.valid = TRUE; + } + + if (!ast_strlen_zero(dialog->caller_tag)) { + redirecting.from.tag = (char *) dialog->caller_tag; + redirecting.to.tag = (char *) dialog->caller_tag; + } + + if (!ast_strlen_zero(dialog->redirecting_reason)) { + redirecting.reason.str = (char *) dialog->redirecting_reason; + } + + redirecting.reason.code = dialog->redirecting_code; + ast_channel_set_redirecting(dialog->channel, &redirecting, &update); +} diff '--color=auto' -durN asterisk-22.6.0.orig/configs/samples/res_parking.conf.sample asterisk-22.6.0/configs/samples/res_parking.conf.sample --- asterisk-22.6.0.orig/configs/samples/res_parking.conf.sample 2025-10-17 13:01:59.859455448 +1300 +++ asterisk-22.6.0/configs/samples/res_parking.conf.sample 2025-10-21 18:12:24.495603898 +1300 @@ -68,6 +68,7 @@ ; extensions if parkext is set ;parkingtime => 45 ; Number of seconds a call can be parked before returning +;remindertime => 0 ; Number of seconds before sending a reminder, 0 for no reminder. ;comebacktoorigin = yes ; Setting this option configures the behavior of call parking when the ; parked call times out (See the parkingtime option). The default value is 'yes'. diff '--color=auto' -durN asterisk-22.6.0.orig/configs/samples/sip.conf.sample asterisk-22.6.0/configs/samples/sip.conf.sample --- asterisk-22.6.0.orig/configs/samples/sip.conf.sample 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.6.0/configs/samples/sip.conf.sample 2025-10-21 18:12:24.496603871 +1300 @@ -0,0 +1,1170 @@ +; Calling a static or realtime peer: +; +; SIP/ +; SIP//[/proxy] +; +; Calling a URI using 'authentication' realm credentials in '[general]': + +; SIP/@ +; SIP/@[:port][/proxy] +; +; Calling a URI with specific authetication credentials: +; +; SIP/[:secret[:md5secret[:authuser[:udp|tcp|tls]]]]@[:port][/proxy] + +[general] +; debug = yes|no +; Enable SIP debugging by default, from the moment the channel loads this +; configuration. Cannot be disabled using the CLI. Default is 'no'. +debug=no + +; udpbindaddr =
[:port] +; Aaddress and optional port bind UDP listen socket to. The address family +; (either IPv4 or IPv6) changes how DNS lookups are performed. When using an +; IPv4 address only 'A' records are used, for an IPv6 excluding the wildcard +; adress '::' only 'AAAA' records are used, otherwise when using '::' then +; 'A' and 'AAAA' records are used. +udpbindaddr=0.0.0.0 + +; rtpbindaddr =
+; Address to bind RTP listen socket to. When not specified 'udpbindaddr' is +; used. Default is unspecified +;rtpbindaddr=0.0.0.0 + +; tcpenable = yes|no +; Enable incoming TCP connections, default is 'no'. +tcpenable=no + +; tcpbindaddr =
[:port] +; Address for TCP server to bind to, see 'udpbindaddr' for examples. Default +; port is 5060. +tcpbindaddr=0.0.0.0 + +; tcpauthtimeout = +; Specifies the maximum number of seconds a client has to authenticate. If the +; client does not authenticate before this timeout expires, the client will be +; disconnected. Default is 30. +tcpauthtimeout=30 + +; tcpauthlimit = +; Specifies the maximum number of unauthenticated sessions that will be allowed +; at any given time. Default is 100. +tcpauthlimit=100 + +; tlsenable = yes|no +; Enable incoming TLS connections, default is 'no'. +tlsenable=no + +; tlsbindaddr =
[:port] +; Address for TCP server to bind to, see 'udpbindaddr' for examples. Default +; port is 5061. +tlsbindaddr=0.0.0.0 + +; tlscertfile = +; Certificate chain (PEM format only) file to use for TLS connections. The +; certificates must be sorted starting with the subject's certificate and +; followed by intermediate CA certificates if applicable. If the file name ends +; in '_rsa' such as 'asterisk_rsa.pem' then the additional files +; 'asterisk_dsa.pem' and/or 'asterisk_ecc.pem' are loaded (certificate, +; intermediates, private key), to support multiple algorithms for server +; authentication (RSA, DSA, ECDSA). If the chains are different. OpenSSL 1.0.2 +; or newer is required. Default is 'asterisk.pem' in current directory. +;tlscertfile=asterisk.pem + +; tlsprivatekey = +; Private key file (PEM format only) for TLS connections. If no 'tlsprivatekey' +; is specified 'tlscertfile' is used for both the public and private key. +;tlsprivatekey=asterisk.pem + +; tlscafile = +; If the server your connecting to uses a self signed certificate you should +; have their certificate installed here so the code can verify the authenticity +; of their certificate. Not chan_sip does not support verifying client +; certificates (why not?). +;tlscafile= + +; tlscapath = +; A directory full of CA certificates, the files must be named with +; the CA subject name hash value. +;tlscapath= + +; tlsdontverifyserver = yes|no +; Verify the server's certificate when acting as a client. If you don't have the +; server's CA certificate you can set this and it will connect without requiring +; 'tlscafile' to be set. Default is 'no'. + +; tlsverifyclient = yes|no +; Verify the client's certificate when acting as a server. If you don't have the +; server's CA certificate you can set this and it will connect without requiring +; 'tlscafile' to be set. Default is 'no'. +;tlsverifyclient=no + +; tlscipher = +; Specify which SSL ciphers are allowed or not. A list of valid SSL cipher +; strings can be found at: http://www.openssl.org/docs/apps/ciphers.html#CIPHER_STRINGS +;tlscipher= + +; tlsservercipherorder = yes|no +; Use the servers cipher order insead of cipher order specified in 'tlscipher'. +;tlsservercipherorder=yes + +; timert1 = +; Normal round-trip time to a peer. Default is '500' or the measured response +; time if 'maxqualify=yes' has been set on peer. +;timert1=500 + +; timert1min = +; Minimum roundtrip time for messages to monitored hosts. Defaults is '100'. +;timert1min=100 + +; timerb = +; Call setup timer. If a provisional response is not received in this amount of +; time, the call will auto-congest, default is 'timert1' * 64. +;timerb=32000 + +; rtptimeout = +; Hangup call if no RTP or RTCP activity on the audio channel has been received +; in this time period. Set to '0' to disable. +;rtptimeout=60 + +; rtpholdtimeout = +; Hangup call if no RTP or RTCP activity on the audio channel has been received +; in this time period and the call is on hold. Set to '0' to disable. +;rtpholdtimeout=300 + +; rtpkeepalive = +; Send RTP keepalive every 'rtpkeepalive' seconds to keep NAT pinhole open, set +; to '0' to disable. Default is '0'. +;rtokeepalive=0 + +; nat = no|rtp|auto_rtp[,force_rport|,auto_force_rport] +; Override the address/port information specified in the SIP/SDP messages, +; and use the information (sender address) supplied by the network instead. +; However, this is only useful if the external traffic can reach us. +; +; The nat settings can be combined. For example, to set both 'force_rport' and +; 'rtp' one would set 'nat=forcerport,rtp'. If one of the 'auto' settings is +; used in conjunction with its non-auto counterpart then the the non-auto +; option will be ignored. +; +; The RFC 3581-defined 'rport' parameter allows a client to request that +; Asterisk send SIP responses to it via the source address and port from which +; the request originated instead of the address/port listed in the top-most Via: +; header. This is useful if a client knows that it is behind a NAT and therefore +; cannot guess from what address/port its request will be sent. Asterisk will +; always honor the 'rport' parameter if it is sent. The 'forcerport' setting +; causes Asterisk to always send responses back to the address and port from +; which it received requests; even if the other side doesn't support +; adding the 'rport' parameter. +; +; NAT RTP handling refers to the technique of sending RTP to the port that the +; other endpoint's RTP arrived from, This method is used to accomodate endpoints +; that may be located behind NAT devices, and as such the address/port they tell +; Asterisk to send RTP packets to for their media streams is not the actual +; address/port that will be used on the nearer side of the NAT. +; +; IT IS IMPORTANT TO NOTE that if the nat setting in the general section differs +; from the 'nat' setting in a peer definition, then the peer username will be +; discoverable by outside parties as Asterisk will respond to different ports +; for defined and undefined peers. For this reason it is recommended to only set +; 'nat' in the [general] section. +; 'rtp' Send media to the port Asterisk received it from +; regardless of where the SDP says to send it. +; 'auto_rtp' Set the 'rtp' option if NAT is detected. +; 'force_rport' Pretend there was an rport parameter even if it was not +; present in the Via header. +; 'auto_force_rport' Set the 'forcerport' option if NAT is detected. +;nat=no + +; localnetwork =
[/mask] +; A list network addresses that are considered "inside" of the NAT-ted network. +; Multiple 'localnet=...' entries may be specified. If 'localnet' is not set, +; the external address will not be set correctly. +;localnetwork=192.168.0.0/16 + +; externaladdr =
[:port] +; The externally visible address and an optional port number to be used when +; talking to a host outside the NAT. +;externaladdr= + +; externalhost = hostname +; DNS lookup hostname to get address to use as 'externaladdr'. +;externalhost= + +; externalexpires = +; Re-lookup DNS for 'externalhost' after this number of seconds. +;externalexpires=60 + +; externaltcpport = +; The externally mapped TCP port, when Asterisk is behind a static NAT. +;externaltcpport=5060 + +; externaltlsport = +; The externally mapped TCP port, when Asterisk is behind a static NAT. +;externaltlsport=5061 + +; mediaaddr =
+; The address used for media (audio, video, and text) in the SDP. This does +; not change the listen address for RTP, it only changes the advertised address +; in the SDP. The Asterisk RTP engine will still listen on the standard address. +;mediaaddr= + +; directrtpsetup = yes|no +; Enable the new experimental direct RTP setup. This sets up the call directly +; with media peer-2-peer without re-INVITEs. DOES NOT work for video and cases +; where the callee sends RTP payloads and fmtp headers in the 200 OK that does +; not match the caller's INVITE. This will also fail if directmedia is enabled +; when the device is behind NAT. Default is 'no'. +;directrtpsetup=no + +; defaultexpires = +; Default expiry time for inbound and outbound messages, default is '120'. +;defaultexpires=120 + +; registerminexpires = +; Minimum expiry time for registrations, default is '60'. +;registerminexpires=60 + +; registermaxexpires=3600 +; Maximum expiry time for iregistations, default is '3600'. +;registermaxexpires=3600 + +; subscribeminexpires = +; Minimum expiry time for subscriptions, default is 'registerminexpires'. +;subscribeminexpires=60 + +; subscribemaxexpires = +; Maximum expiry time for subscriptions, default is 'registermaxexpires'. +;subscribemaxexpires=3600 + +; language = +; Default language setting for peers. +;language=en + +; tonezone = +; Default inband indication tonezone for peers. +;tonezone=us + +; mohinterpret = +; Default Music on Hold class when being placed on hold. +;mohinterpret=default + +; mohsuggest = +; Default Music on Hold class when placing a call on hold +;mohsuggest=default + +; shrinkcallerid = yes|no +; Removes '(', ' ', ')', non-trailing '.', and '-' not in square brackets from +; incoming SIP user. Default is 'yes'. +;shrinkcallerid=yes + +; allowearlymedia = yes|no +; Some ISDN links send empty media frames before the call is in ringing or +; progress state. The SIP channel will then send 183 indicating early media +; which will be empty so the user will hear no ringing tones. Set to 'no' +; to stop any media before Call Progress has been recieved. Default is 'yes'. +;allowearlymedia=yes + +; srvlookup = yes|no +; Enable DNS SRV lookups calls. Default is 'no'. +;srvlookup=no + +; relaxdtmf = yes|no +; Relax detection of DTMF digits. Default is 'no' +;relaxdtmf=no + +; useragent = +; Set the Useragent header, default is 'Asterisk PBX '. +;useragent=Asterisk PBX + +; fromdomain = +; Domain to use in the From: header for outgoing requests instead of Asterisk's +; address. +;fromdomain= + +; sdpsession = +; Set the SDP session name in 's='. Default is 'Asterisk PBX '. +;sdpsession=Asterisk PBX + +; sdpusername = +; Set the SDP username field in 'o=', field cannot contain spaces. Default is +; 'Asterisk-SIP'. +;sdpusername=Asterisk-SIP + +; matchauthusername = yes|no +; If present, match peer name entry using the username= parameter in the +; Authorization header intead of the From: user. Default is no. +;matchauthusername=no + +; authentication = :[secret]:[md5secret]@ +; Global authentication credentials for peers, these credentials override +; credentials in peer/register definition if realm is matched. Multiple entries +; may be specified. +; authentication= + +; alwaysauthreject = yes|no +; When an incoming INVITE or REGISTER is to be rejected, for any reason, always +; reject with an identical response equivalent to valid username and invalid +; password instead of letting the requester know whether there was a matching +; peer for their request. Default is 'yes'. +;alwaysauthreject=yes + +; authfailureevents = yes|no +; Send manager "SIPPeerStatus" events when peer fails to authenticate. Default +; is 'no'. +;authfailureevents=no + +; authoptionsrequests = yes|no +; Require OPTIONS requests to be authenticated, default is 'no'. +;authoptions=no + +; allowmessage = yes|no +; Allow message equests outside of an existing call. Default is 'yes' +;allowmessage=no + +; notifycallerid = yes|no +; whether caller ID information is sent along with dialog-info+xml notifications +; for ringing events. Default is 'no.' +;notifycallerid=no + +; pickupcontext = yes|no +; Whether to use the ringing channel's context when doing a directed call +; pickup, otherwise PICKUPMARK is used. +;pickupcontext=no + +; tossip = +; Set Type of Service (ToS) for SIP packets. Default is 'cs3'. +;tossip=cs3 + +; tosaudio = +; Set Type of Service (ToS) for RTP audio packets. Default is 'ef'. +;tosaudio=ef + +; tosvideo = +; Set Type of Service (ToS) for RTP video packets. Default is 'af41'. +;tosvideo=af41 + +; tostext = +; Set Type of Service (ToS) for RTP text packets. Default is 'af41'. +;tosvideo=af41 + +;cossip = +; Set 802.1p priority for SIP packets. Default is '3'. +;cossip=3 + +;cosaudio = +; Set 802.1p priority for RTP audio packets. Default is '5'. +;cosaudio=5 + +;cosvideo = +; Set 802.1p priority for RTP video packets. Default is '4'. +;cosaudio=5 + +;costext = +; Set 802.1p priority for RTP text packets. Default is '3'. +;cosaudio=5 + +; allow = [,codec]* +; Which media (audio, video) codecs to allow. When Asterisk is receiving a call, +; the codec will initially be set to the first codec in the allowed codecs +; defined for the user receiving the call that the caller also indicates that +; it supports. But, after the caller starts sending RTP, Asterisk will switch to +; using whatever codec the caller is sending. +; +; When Asterisk is placing a call, the codec used will be the first codec in +; the allowed codecs that the callee indicates that it supports. Asterisk will +; *not* switch to whatever codec the callee is sending. +;allow= + +; disallow = [,codec]* +; Which media (audio, video) codecs are not allowed. +;disallow= + +; realm = +; Realm for digest authentication, default is 'asterisk'. If you set a system +; name in asterisk.conf, it defaults to that system name. Realms MUST be +; globally unique according to RFC 3261. +;realm=asterisk + +; domainsasrealm = yes|no +; Use domains list as realms, you can serve multiple Realms specifying several +; 'domain=...' directives (see below). In this case Realm will be based on +; request 'From:' or 'To:' header and should match one of domain names, +; Otherwise default 'realm=...' will be used. Default is 'no'. +;domainsasrealm=no + +; domain = [,context] +; Incoming INVITE and REFER messages can be matched against a list of 'allowed' +; domains, each of which can direct the call to a specific context if desired. +; By default, all domains are accepted and sent to the default context or the +; context associated with the user/peer placing the call. REGISTER to non-local +; domains will be automatically denied if a domain list is configured. +;domain= + +; allowexternaldomains = yes|no +; Disallow requests for domains not serviced by this server. Default is 'yes' +;allowexternaldomains=yes + +; maxqualify = yes|no| +; Maximum time in milliseconds for a peer to respond to our OPTIONS request to +; be considered reachable. When set to 'yes' then the default of '2000' is +; used. Default is '2000'. +;maxqualify=2000 + +; qualifyexpires = +; How ofen to send an OPTIONS request to a peer to check that it is still +; reachable. Default is '60'. +;qualifyexpires=60 + +; qualifygap = +; Gap in milliseconds between sending OPTIONS requests to peers when config is +; loaded/reloaded. Default is '100'. +;qualifygap=100 + +; qualifypeers = +; Number of peers to qualify at one time per 'qualifygap' period when config +; is loaded/re-loaded. Default is '1' +;qualifypeers=1 + +; networkchangeevent = yes|no +; Through the use of the res_stun_monitor module, Asterisk has the ability to +; detect when the perceived external network address has changed. When 'yes' +; chan_sip will renew all outbound registrations when the monitor detects any +; sort of network change has occurred. Default is 'no'. +;networkchangeevent=no + +; proxy = [transport://]
[:port][,force] +; Send initial outgoing request through proxy address and port. 'force' option +; prevents removal of proxy after a response is received. +;proxy= + +; register = [transport://][@domain[:port]][:secret[:md5secret[:authuser]]]@[:port][/exten][~expiry] +; Register a contact to another SIP provider. Note for calls to or from this +; provider you still need a peer entry. If your secret is all digits then a +; domain port needs to be specified otherwise the parser will set the port as +; the secret. Multiple entries may be specified. +;register= + +; registertimeout = +; Retry failed registrations attempts after this period. Default is '20'. +;registertimeout=20 + +; registerattempts = +; Number of failed registration attempts before giving up. Setting to '0' will +; never give up. Default is '10'. +;registerattempts=10 + +; registerretryforbidden = yes|no +; Keep attempts to register even if a 403 response has been received. +;registerretryforbidden=no + +; mwi => [transport://][:secret[:authuser]]@[:port]/[@context] +; Subscribe to MWI notifications from another SIP server. Multiple entries may +; be specified. +;mwi= + +; jbenable = yes|no +; Enables the use of a jitter-buffer on the receiving side of the call. Jitter +; buffer will be used only if the sending side can create and the receiving +; side can not accept jitter. Becase SIP can accept jitter it will only be used +; if it is forced. Default is 'no' +;jbenable=no + +; jbforce = yes|no +; Forces the use of a jitter-buffer on the receive side of a SIP call. Default +; is 'no' +;jbforce=no + +; jbimpl = fixed|adaptive +; Jitter-buffer implementation, 'fixed' uses 'jbmaxsize' or 'adaptive' which +; will dynamically change, default is 'fixed'. +;jbimpl=fixed + +; jbmaxsize = +; Maximum size of the jitter-buffer in milliseconds. Default is '1000'. +;jbmaxsize=1000 + +; jbresyncthreshold = +; Jump in the frame timestamps over which the jitterbuffer is resynchronized. +; Useful to improve the quality of the voice, with big jumps with timestamps +; and programs. Defaults is '1000'. +;jbresyncthreshold=1000 + +; jbtargetextra = +; The number of milliseconds with which the new jitter buffer will pad its size. +; Only applies if 'jbimpl' is 'adaptive'. Default is '40'. +;jbtargetextra=40 + +; jblog = yes|no +; Enable jitter-buffer frame logging. Default is 'no'. +;jblog=no + +; realtimecachepeers = yes|no +; Cache realtime peers by adding them to the list of statically configured +; peers. Default is 'no' +;realtimecachepeers=no + +; realtimeupdate = yes|no +; Update realtime peer information in the database. Default is 'yes'. +;realtimeupdate=yes + +; realtimeautoclear = yes|no | +; Auto remove realtime peers from the list of peers until it is loaded again +; after that number of seconds. If set to 'yes' then that period wil be '120', +; default is 'no'. +;realtimeautoclear=no + +; realtimeignoreexpire = yes|no +; When the peer is retrieved from realtime storage the registration information +; will be used regardless of whether it has expired or not. Default is 'no'. +;realtimeignoreexpire=no + +; realtimesavesysname = yes|no +; Save systemname from asterisk.conf in realtime database at registration, +; default is 'no'. +;realtimesavesysname=no + +;------------------------------------------------------------------------------- + +; [notify-type] Send custom NOTIFY messages to a phone. Both 'header' and +; 'content' lines supports variable expansion via '${}', the name of the peer +; being sent the notification will be set to '${PEERNAME}'. +; +; Note: this is only a tempplate '(!)' containing all notify options. +[notify-type](!) +; type = notify +; All notifications MUST have a a type of 'notify' + +; header = name: value +; Add header 'name' with 'value' to the NOTIFY message, must be separated by +; a ':' character. + +; context = [text] +; Add line of test to the NOTIFY message. Multiple 'context' entries are +; supported. + +;[clear-mwi] +;type=notify +;header=Event: message-summary +;header=Content-Type: application/simple-message-summary +;content=Messages-Waiting: no +;content=Message-Account: sip:asterisk@127.0.0.1 +;content=Voice-Message: 0/0 + +[cisco-service-control](!) +type=notify +header=Event: service-control +header=Subscription-State: active +header=Content-Type: text/plain + +[cisco-restart](cisco-service-control) +content=action=restart +content=RegisterCallId={${SIP_PEER(${PEERNAME},register_callid)}} +content=ConfigVersionStamp={00000000-0000-0000-0000-000000000000} +content=DialplanVersionStamp={00000000-0000-0000-0000-000000000000} +content=SoftkeyVersionStamp={00000000-0000-0000-0000-000000000000} +content=FeatureControlVersionStamp={00000000-0000-0000-0000-000000000000} + +[cisco-reset](cisco-service-control) +content=action=reset +content=RegisterCallId={${SIP_PEER(${PEERNAME},register_callid)}} +content=ConfigVersionStamp={00000000-0000-0000-0000-000000000000} +content=DialplanVersionStamp={00000000-0000-0000-0000-000000000000} +content=SoftkeyVersionStamp={00000000-0000-0000-0000-000000000000} +content=FeatureControlVersionStamp={00000000-0000-0000-0000-000000000000} + +[cisco-prt-report](cisco-service-control) +content=action=prt-report +content=RegisterCallId={${SIP_PEER(${PEERNAME},register_callid)}} + +;[linksys-reboot] +;type=notify +;header=Event: reboot_now + +;[linksys-restart] +;type=notify +;header=Event: restart_now + +;[aastra-check-sync] +;type=notify +;header=Event: check-sync + +;[aastra-xml] +;type=notify +;header=Event: aastra-xml + +;[polycom-check-sync] +;type=notify +;header=Event: check-sync + +;[snom-check-sync] +;type=notify +;header=Event: check-sync\;reboot=false + +;[snom-reboot] +;type=notify +;header=Event: check-sync\;reboot=true + +;------------------------------------------------------------------------------- + +; [peer-name] For incoming requests Asterisk will try to match the user name to +; a peer on the 'peer-name', if no match is found Asterisk will try and match on +; the peers current address and port. +; +; Note this is only a template '(!)' containing all peer options, you should +; define multiple templates for default and model specific settings and then +; apply those templates to a peer-specific section. +; +; Example Cisco-specific phone templates are shown further below. +[peer-name](!) +; type = peer +; All peers MUST have have a type of 'peer' +;type=peer + +; description = +; Text description of peer. +;description= + +; transport = [,transport]* +; Sets the allowed transport types, the first transport specified is the default +; outgoing transport which is used until the peer registers using a different +; transport. Valid transports types are: +; 'udp' UDP. +; 'tcp' TCP, 'tcpenable=yes' must be set to accept TCP connections. +; 'tls' TLS, 'tlsenable=yes' must be set to accept TLS connections. +;transport=udp + +; nat = no|comedia|auto_comedia[,force_rport|,auto_forc_erport] +; Peer specific setting of 'nat' in [general]. +;nat=no + +; host = dynamic |
+; Aaddress for the peer, use 'dynamic' if the peer registers with Asterisk. +;host=dynamic + +; context = +; Sets the default context for incoming calls. +;context=default + +; username = +; User te peer will use for outbound authentication. Not used for inbound +; authentication as that matches on 'peer-name' or 'host'. +;username= + +; secret = +; Secret the peer will use to authenticate. +;secret= + +; md5secret = +; MD5 hash of the secret the peer will use to authenticate. +;md5secret= + +; remotesecret = +; Secret that Asterisk will use to authenticate with peer. Default is +; option 'secret=...'. +;remotesecret= + +; authentication = :[secret:[md5_secret]@ +; Peer specific setting of 'authentication' in [general]. +;authentication= + +; callerid = +; Full caller ID that overrides that received in the INVITE request. +;callerid= + +; callername = +; Caller ID name that overrides that received in the INVITE request. +;callername= + +; callernumber = +; Caller ID number that overrides that received in the INVITE request. +;callernumber= + +; callertag = +; Caller ID tag. +;callertag= + +; callerpresentation = +; Set caller ID presentation. +;callerpresentation= + +; amaflags = +; Set channel AMA flags. +;amaflags= + +; accountcode = +; Set channel account code. +;accountcode= + +; subscribecontext = +; Specific context to find hints when subscribing to extension state. +;subscribecontext=default + +; messagecontext = +; Context to use for out-of-call MESSAGE requests. +;messagecontext=default + +; fromuser = +; Set the user part of the From: header for outgouing calls. +;fromuser= + +; fromdomain = +; Set the domain part of the From: header for outgouing calls. +;fromdomain= + +; parkinglot = +; Sets the default context for parked calls. +;parkinglot=default + +; allowoverlap = yes|no|invite|dtmf +; Enable RFC3578 overlap dialing support. When set to 'yes' or 'invite' use +; the Incomplete application to collect the needed digits from an ambiguous +; dialplan match. When set to 'dtmf' (inband, RFC2833) digits are collected +; in the early media phase. Default is 'no'. +;allowoverlap=no + +; dtmfmode = rfc2833 | inband | auto +; Sets how DTMF digits are collected, default is 'rfc2833'. +; +; 'rfc2833' Digits send as RTP telephone-event. +; 'inband' Inband audio, requires ulaw, alaw or g722 codec. +; 'auto' 'rfc2833' if offered, othwerise 'inband'. +;dtmfmode=rfc2833 + +; allowtransfer = yes|no +; Allow calls to be transferred via REFER or an INVITE with a Replaces: header. +; Dial() options 't' and 'T' are not related as to whether SIP transfers are +; allowed or not. Default is 'yes'. +;allowtransfer=yes + +; allowsubscribe = yes|no +; Allow peers to SUBSCRIBE to get extension state events. Default is 'yes'. +;allowsubscribe=yes + +; usereqphone = yes|no +; Add ';user=phone' to URI if the user part only contains digits. Default is +; 'no'. +;usereqphone=no + +; maxforwards = +; Set the Max-Forwards: header. Default is '70'. +;maxforwards=70 + +; maxcallbitrate = +; Maximum bitrate for video calls. Default is '384' +;maxcallbitrate=384 + +; maxqualify = yes|no| +; Peer specific setting of 'maxqualify' in [general]. +;maxqualify=yes + +; qualifyfrequency = +; Peer specific setting of 'qualifyfrequency' in [general]. +;qualifyfrequency=60 + +; callgroup = [|-][,|-]* +; Set channel callgroup, these calls can be picked-up if the channel has a +; matching 'pickupgroup'. 'number' is between 1 and 31, a range can be specified +; the group as 'start-end'. +;callgroup= + +; pickupgroup = [|-][,|-]* +; Set channel pickupgroup, allows pickup if a channel has a matching 'callgroup'. +;pickupgroup= + +; namedcallgroup = [,name]* +; Set channel named callgroup, these calls can be picked-up if the channel has a +; matching 'namedpickupgroup'. +;namedcallgroup= + +; namedpickupgroup = [,name]* +; Se channel named pickup group, allows pickup if a channel has a matching +; 'namedcallgroup'. +;namedpickupgroup= + +; variable = = +; Set a channel variable. Multiple entries may be specified. +;variable= + +; header = : Value +; Add a custom header. Multiple entries may be specified. +;header= + +; maxcalls = +; Maximum number of concurrent calls for peer. +;maxcalls= + +; busylevel = +; Number of concurrent calls after which the peer's device state will be busy. +; Must not be greater than 'maxcalls'. +;busylevel= + +; mwiexten = +; Sets the Message-Account for MWI NOTIFY messages. Defaults to 'asterisk'. +;vmexten=asterisk + +; subscribemwi = yes|no +; Only send MWI notifications to device if it has subscribed to that event. +;subscribemwi=no + +; mailbox = @[mailbox@context]* +; Voice mailbox for MWI events, 'context' is required. Multiple may be specified. +;mailbox= + +; unsolictedmailbox = [@context] +; Remote mailbox name to use when an unsolicted MWI notification is received. If +; no context is specified 'default' is used. +;unsolictedmailbox= + +; autoframing = yes|no +; Set packetization based on the remote peer's SDP ptime value. Default is 'no'. +;autoframing=no + +; timert1 = +; Peer specific setting of 'timert1' in [general]. +;timert1=500 + +; timerb = +; Peer specific setting of 'timerb' in [general]. +;timert1min=100 + +; timerb = +; Call setup timer. If a provisional response is not received in this amount of +; time, the call will auto-congest, default is 'timert1' * 64. +;timerb=32000 + +; mohinterpret = +; Peer specific setting of 'mohinterpret' in [general]. + +; mohsuggest = +; Peer specific setting of 'mohsuggest' in [general]. + +; allow = [,codec]* +; Peer specific setting of 'allow' in [general]. +;allow= + +; disallow = [,codec]* +; Peer specific setting of 'disallow' in [general]. +;disallow= + +; preferredcodeconly = yes|no +; Only add the most preferred codec to SDP response to limit what the remote +; peer can choose. Default is 'no'. +;preferredcodeconly=no + +; ignoreoutgoingcodec = yes|no +; Ignore the requested codec and determine the preferred codec from the peer +; configuration. +;ignoreoutgoingcodec=no + +; video = yes|no +; Enable video support, default is 'no'. +;video=no + +; text = yes|no +; Enable text support, default is 'no'. +;text=no + +; path = yes|no +; Enable support for Path: header. This will send outgoing requests via the +; specified by the hosts in the Path: headers. +;path=no + +; reason = yes|no +; Parse Q.850 hangup cause from Reason: header. Default is 'no'. +;reason=no + +; identity = yes|remote_party|p_asserted[,allow][,trust]|no +; Which identity header to the to send to the peer when updating the caller +; information. Default is 'no'. +; 'yes' or 'remote_party' Use Remote-Party-ID header. +; 'p_asserted' Use P-Asserted-Identity header. +; 'allow' Allow inbound header to overwrite Caller ID. +; 'trusted' Send outbound information even if caller +; presentation is prohibited. +; 'no' Do not use identity information. +;identity=no + +; diversion = yes|no +; Add Diversion: header for calls that have been forwarded. Default is 'yes'. +;diversion=yes + +; rtcpmux = yes|no +; Enable support for RFC 5761 RTCP multiplexing. Default is 'no'. +;rtcpmux=no + +; encryption = yes|no +; Whether to require encrypted RTP (SRTP) for this peer. Default is 'no'. +;encryption=no + +; ice = yes|no +; Enable ICE support for NAT traversal, default is 'no'. +;icesupport=no + +; permit = [,mask] +;permit= + +; deny =
[,mask] +;deny= + +; contactpermit =
[,mask] +; Specify which addresses allowed in peer registration Contact:. Multiple +; entries are allowed. +;contactpermit= + +; contactdeny =
[,mask] +; Specify which addresses that are not allowed in registration Contact:. +; Multiple entries are allowed. +;contactdeny= + +; contactacl = +; Specify which addresses and allowed/not using a named acl in acl.conf. +; Multiple entries are allowed. +;contactacl= + +; directmedia = [yes|no-nat][,update][,outgoing]|no +; Asterisk tries to re-invite media streams if there is no no reason for +; Asterisk to stay in the media path, the media will be redirected. This does +; not really work well in the case where Asterisk is outside and the clients are +; on the inside of a NAT. In that case, you want to set 'directmedia=no_nat'. +; 'yes' Enable direct media. +; 'no_nat' Only redirect media if both peers are known not beind NAT. The +; RTP core can detect this based on the received address. +; 'update' Use UPDATE instead of INVITE when redirecting media. +; 'outgoing' Delay sending re-INVITE if the peer is likely to attempt to +; redirect the media itself. +; 'no' Disable direct media. +;directmedia=no + +; directmediapermit =
[,mask] +; Restrict which addresses are allowed to do direct media. Multiple entries +; are allowed. +;directmediapermit= + +; directmediadeny =
[,mask] +; Restrict which addresses are not allowed to direct media. Multiple entries +; may are allowed. +;directmediadeny= + +; directmediaacl = +; Specify which addresses are allowed/not allowed using a named acl in +; acl.conf. Multiple entries are allowed. +;directmedaacl= + +; cisco = yes|no +; Enable Cisco phone features, this is required to make call-forwarding, DND +; BLF, multiple-lines, conferencing, parking and other features work. Do not set +; on any non-Cisco phone peer. Default is 'no'. +;cisco=no + +; busywhendnd = yes|no +; Automatically send a busy signal if this peer is called and has DND enabled. +; Default is 'no'. +;busywhendnd= + +; huntgroupdefault = yes|no +; Whether the peer is logged into the hunt-group by default. +;huntgroupdefault=no + +; pickupnotify = no|[from][,to][,beep] +; Notify a Cisco phone that a call is available for pickup. Options may be +; combined together eg: 'from,beep'. Default is 'no'. +; 'from' Display 'From ' +; 'to' Display 'To ' +; 'beep' Play a beep tone. +; 'no' Disable notification. +;pickupnotify=no + +; pickupnotifytimer = +; Number of seconds to display From and/or To notification for 'pickupnotify'. +;pickupnotifytimer=5 + +; parknotifytimer = +; Number of seconds to display parked call extension notification. +;parknotifytimer=5 + +; keepconference = yes|no +; Keep an ad-hoc conference going even if there are no more participants that +; are adminstrators. The peer that created the conference is an adminstrator. +;keepconference=no + +; multiadminconference = yes|no +; If a participant added to an ad-hoc conference has 'ciscosupport' enabled then +; make them an adminstrator as well which allows that peer to also add, mute and +; remove participants from the conference. +;multiadminconference=yes + +; qrturl = +; URL to send to the peer when Quality Reporting Tool has been requested. +;qrturl= + +;subscribe = [@context],[exten[@context]]* +; Add a subscription to a Cisco phone as they do not send a SUBSCRIBE for BLF +; line keys. Multiple entries may be specified via a ',' or by adding additional +; 'subscribe' lines. +;subscribe= + +;register = [,peer]* +; Add a secondary line to a Cisco phone as they only send a REGISTER for the +; first (primary) line. Multiple entries may be specified via a ',' or by adding +; additional 'register' lines. +;register= + +; proxy = [transport://][,force] +; Peer specific setting of 'proxy' in [general]. +;proxy= + +; timersupport = originate|accept|refuse +; Session timers end-to-end keep-alive mechanism for active SIP sessions. This +; can detect and reclaim SIP channels that do not terminate through normal +; signaling procedures. Default is 'refuse'. +; +; 'originate' Request and run session-timers always. +; 'accept' Run session-timers only when requested by other side. +; 'refuse' Do not run session timers in any case. +;timersupport=refuse + +; timerrefresher = uas|uac +; Which side is responsible for refreshing the session. Due to recommendations +; in RFC 4028, Asterisk will always honor the other side's preference for who +; will handle refreshes. Default is 'uas'. +; 'uac' The caller. +; 'uas' The callee. +;timerrefresher=uas + +; timerminexpires = +; Minimum session refresh interval in seconds. Default is '90'. +;timerminexpires=90 + +; timermaxexpires = +; Maximum session refresh interval in seconds. Default is '1800'. +;timermaxexpires=1800 + +; faxsupport = no|yes[,fec][,redundancy][,none][,max_datagram=][,nat] +; Enable fax support and specify fax parameters, multiple parameters can be +; combined. Default is 'no'. +; 'yes' Enable fax support. +; 'fec' FEC error correction. +; 'redundancy' Redundancy error correction. +; 'none' No error correction. +; 'max_datagram=' Max datagram of 'size' bytes. +; 'nat' Send RTP to recieved address instead of SDP address. +; 'no' Disable fax support. +;faxsupport=no + +; faxdetect = yes|cng|t38|no +; FAX detection will cause the call to go to the 'fax' extension (if it exists) +; based one or more events being detected. Default is 'no'. +; 'cng' Detect comfort noise generation. +; 't38' re-INVITE with T.38 in SDP. +; 'yes' Both 'cng' and 't38'. +; 'no' Disable fax detection. +;faxdetect=no + +; Example templates. +;[non-secure-mode](!) +;transport=tcp + +;[authenticated-mode](!) +;transport=tls + +;[encrypted-mode](!) +;transport=tls +; The res_srtp module must be loaded. +;encryption=yes + +;[cisco-defaults](!) +;transport=tcp +;nat=no +;identitysupport=remote_party +;allowsubscribe=yes +;allowtransfer=yes +;subscribemwi=no +;dtmfmode=rfc2833 +;diversionsupport=yes +;reasonsupport=yes +;videosupport=no +;textsupport=no +;ciscosupport=yes +;keepconference=no +;multiadminconference=yes +;busywhendnd=yes +;huntgroupdefault=no + +;[cisco-6901](cisco-defaults) +;maxcalls=1 +;busylevel=1 + +;[cisco-6921](cisco-6901) + +;[cisco-6941](cisco-defaults) +;maxcalls=2 +;busylevel=1 + +;[cisco-6945](cisco-6941) + +;[cisco-6961](cisco-defaults) +;maxcalls=5 +;busylevel=4 + +;[cisco-7811](cisco-defaults) +;maxcalls=2 +;busylevel=1 + +;[cisco-7821](cisco-7811) + +;[cisco-7832](cisco-defaults) +;maxcalls=2 +;busylevel=1 + +;[cisco-7841](cisco-defaults) +;maxcalls=4 +;busylevel=3 + +;[cisco-7861](cisco-7841) + +;[cisco-7911](cisco-defaults) +;maxcalls=2 +;busylevel=2 +;huntgroupdefault=yes + +;[cisco-7941](!,cisco-defaults) +;maxcalls=4 +;busylevel=3 +;huntgroupdefault=yes + +;[cisco-7942](cisco-7941) +;[cisco-7945](cisco-7941) +;[cisco-7961](cisco-7941) +;[cisco-7962](cisco-7941) +;[cisco-7965](cisco-7941) + +;[cisco-7970](!,cisco-defaults) +;maxcalls=6 +;busylevel=5 + +;[cisco-7971](cisco-7970) +;[cisco-7975](cisco-7970) + +;[cisco-8811](cisco-defaults) +;maxcalls=5 +;busylevel=4 + +;[cisco-8821](cisco-defaults) +;maxcalls=2 +;busylevel=1 + +;[cisco-8832](cisco-defaults) +;maxcalls=2 +;busylevel=1 + +;[cisco-8841](cisco-8811) + +;[cisco-8845] +;maxcalls=5 +;busylevel=4 +;videosupport=yes + +;[cisco-8851](cisco-8811) +;[cisco-8861](cisco-8811) +;[cisco-8865](cisco-8845) diff '--color=auto' -durN asterisk-22.6.0.orig/contrib/realtime/mysql/mysql_config.sql asterisk-22.6.0/contrib/realtime/mysql/mysql_config.sql --- asterisk-22.6.0.orig/contrib/realtime/mysql/mysql_config.sql 2025-10-17 13:01:59.868455208 +1300 +++ asterisk-22.6.0/contrib/realtime/mysql/mysql_config.sql 2025-10-21 18:12:24.496603871 +1300 @@ -10,91 +10,91 @@ name VARCHAR(40) NOT NULL, ipaddr VARCHAR(45), port INTEGER, - regseconds INTEGER, - defaultuser VARCHAR(40), - fullcontact VARCHAR(80), - regserver VARCHAR(20), + expires INTEGER, + sysname VARCHAR(20), + contact VARCHAR(80), useragent VARCHAR(20), - lastms INTEGER, + lastqualify INTEGER, host VARCHAR(40), - type ENUM('friend','user','peer'), + type ENUM('peer'), context VARCHAR(40), + subscribecontext VARCHAR(40), + messagecontext VARCHAR(40), permit VARCHAR(95), deny VARCHAR(95), secret VARCHAR(40), md5secret VARCHAR(40), remotesecret VARCHAR(40), - transport ENUM('udp','tcp','tls','ws','wss','udp,tcp','tcp,udp'), - dtmfmode ENUM('rfc2833','info','shortinfo','inband','auto'), - directmedia ENUM('yes','no','nonat','update'), - nat VARCHAR(29), + transport ENUM('udp','tcp','tls','udp,tcp','tcp,udp'), + dtmfmode ENUM('rfc2833','inband','auto'), + directmedia VARCHAR(40), + nat VARCHAR(40), callgroup VARCHAR(40), pickupgroup VARCHAR(40), language VARCHAR(40), disallow VARCHAR(200), allow VARCHAR(200), - insecure VARCHAR(40), - trustrpid ENUM('yes','no'), - progressinband ENUM('yes','no','never'), - promiscredir ENUM('yes','no'), - useclientcode ENUM('yes','no'), accountcode VARCHAR(40), - setvar VARCHAR(200), + variable VARCHAR(200), + header VARCHAR(200), callerid VARCHAR(40), + callerpresentation ENUM('allowed_not_screened','allowed_passed_screen','allowed_failed_screen','allowed','prohib_not_screened','prohib_passed_screen','prohib_failed_screen','prohib'), amaflags VARCHAR(40), - callcounter ENUM('yes','no'), + maxcalls INTEGER, busylevel INTEGER, - allowoverlap ENUM('yes','no'), + allowoverlap ENUM('yes','invite','dtmf','no'), allowsubscribe ENUM('yes','no'), - videosupport ENUM('yes','no'), + video ENUM('yes','no'), maxcallbitrate INTEGER, - rfc2833compensate ENUM('yes','no'), mailbox VARCHAR(40), - `session-timers` ENUM('accept','refuse','originate'), - `session-expires` INTEGER, - `session-minse` INTEGER, - `session-refresher` ENUM('uac','uas'), - t38pt_usertpsource VARCHAR(40), - regexten VARCHAR(40), + timer ENUM('accept','refuse','originate'), + timermaxexpiry INTEGER, + timerminexpiry INTEGER, + timerrefresher ENUM('uac','uas'), fromdomain VARCHAR(40), fromuser VARCHAR(40), - `qualify` VARCHAR(40), - defaultip VARCHAR(45), + maxqualify VARCHAR(40), + qualifyexpiry INTEGER, rtptimeout INTEGER, rtpholdtimeout INTEGER, - sendrpid ENUM('yes','no'), - outboundproxy VARCHAR(40), - callbackextension VARCHAR(40), + rtpkeepalive INTEGER, + identity VARCHAR(40), + reason ENUM('yes','no'), + diversion ENUM('yes','no'), + proxy VARCHAR(40), timert1 INTEGER, timerb INTEGER, - qualifyfreq INTEGER, - constantssrc ENUM('yes','no'), contactpermit VARCHAR(95), contactdeny VARCHAR(95), usereqphone ENUM('yes','no'), - textsupport ENUM('yes','no'), + text ENUM('yes','no'), faxdetect ENUM('yes','no'), - buggymwi ENUM('yes','no'), - auth VARCHAR(40), - fullname VARCHAR(40), - trunkname VARCHAR(40), - cid_number VARCHAR(40), - callingpres ENUM('allowed_not_screened','allowed_passed_screen','allowed_failed_screen','allowed','prohib_not_screened','prohib_passed_screen','prohib_failed_screen','prohib'), + authentication VARCHAR(40), mohinterpret VARCHAR(40), mohsuggest VARCHAR(40), parkinglot VARCHAR(40), - hasvoicemail ENUM('yes','no'), subscribemwi ENUM('yes','no'), - vmexten VARCHAR(40), + mwiexten VARCHAR(40), + encryption ENUM('yes','no'), autoframing ENUM('yes','no'), - rtpkeepalive INTEGER, - `call-limit` INTEGER, - g726nonstandard ENUM('yes','no'), - ignoresdpversion ENUM('yes','no'), allowtransfer ENUM('yes','no'), dynamic ENUM('yes','no'), - path VARCHAR(256), - supportpath ENUM('yes','no'), + lastpath VARCHAR(256), + path ENUM('yes','no'), + cisco ENUM('yes','no'), + busywhendnd ENUM('yes','no'), + donotdisturb ENUM('yes','no'), + callforward VARCHAR(40), + huntgroup ENUM('yes','no'), + hungroupdefault ENUM('yes','no'), + register VARCHAR(200), + subscribe VARCHAR(200), + qrturl VARCHAR(200), + keepconference ENUM('yes','no'), + multiadminconference ENUM('yes','no'), + pickupnotify VARCHAR(40), + pickupnotifytimer INTEGER, + parknotifytimer INTEGER, PRIMARY KEY (id), UNIQUE (name) ); diff '--color=auto' -durN asterisk-22.6.0.orig/contrib/realtime/postgresql/postgresql_config.sql asterisk-22.6.0/contrib/realtime/postgresql/postgresql_config.sql --- asterisk-22.6.0.orig/contrib/realtime/postgresql/postgresql_config.sql 2025-10-17 13:01:59.870455154 +1300 +++ asterisk-22.6.0/contrib/realtime/postgresql/postgresql_config.sql 2025-10-21 18:12:24.497603844 +1300 @@ -7,38 +7,37 @@ -- Running upgrade -> 4da0c5f79a9c -CREATE TYPE type_values AS ENUM ('friend', 'user', 'peer'); - -CREATE TYPE sip_transport_values AS ENUM ('udp', 'tcp', 'tls', 'ws', 'wss', 'udp,tcp', 'tcp,udp'); +CREATE TYPE yes_no_values AS ENUM ('yes', 'no'); -CREATE TYPE sip_dtmfmode_values AS ENUM ('rfc2833', 'info', 'shortinfo', 'inband', 'auto'); +CREATE TYPE sip_type_values AS ENUM ('peer'); -CREATE TYPE sip_directmedia_values AS ENUM ('yes', 'no', 'nonat', 'update'); +CREATE TYPE sip_transport_values AS ENUM ('udp', 'tcp', 'tls', 'udp,tcp', 'tcp,udp'); -CREATE TYPE yes_no_values AS ENUM ('yes', 'no'); +CREATE TYPE sip_dtmfmode_values AS ENUM ('rfc2833', 'info', 'auto'); -CREATE TYPE sip_progressinband_values AS ENUM ('yes', 'no', 'never'); +CREATE TYPE sip_callerpresentation_values AS ENUM ('allowed_not_screened', 'allowed_passed_screen', 'allowed_failed_screen', 'allowed', 'prohib_not_screened', 'prohib_passed_screen', 'prohib_failed_screen', 'prohib'); -CREATE TYPE sip_session_timers_values AS ENUM ('accept', 'refuse', 'originate'); +CREATE TYPE sip_directmedia_values AS ENUM ('yes', 'no', 'no_nat', 'update'); -CREATE TYPE sip_session_refresher_values AS ENUM ('uac', 'uas'); +CREATE TYPE sip_timer_values AS ENUM ('accept', 'refuse', 'originate'); -CREATE TYPE sip_callingpres_values AS ENUM ('allowed_not_screened', 'allowed_passed_screen', 'allowed_failed_screen', 'allowed', 'prohib_not_screened', 'prohib_passed_screen', 'prohib_failed_screen', 'prohib'); +CREATE TYPE sip_timerrefresher_values AS ENUM ('uac', 'uas'); CREATE TABLE sippeers ( id SERIAL NOT NULL, name VARCHAR(40) NOT NULL, ipaddr VARCHAR(45), port INTEGER, - regseconds INTEGER, - defaultuser VARCHAR(40), - fullcontact VARCHAR(80), - regserver VARCHAR(20), + expires INTEGER, + contact VARCHAR(80), + sysname VARCHAR(20), useragent VARCHAR(20), - lastms INTEGER, + lastqualify INTEGER, host VARCHAR(40), - type type_values, + type sip_type_values, context VARCHAR(40), + subscribecontext VARCHAR(40), + messagecontext VARCHAR(40), permit VARCHAR(95), deny VARCHAR(95), secret VARCHAR(40), @@ -47,74 +46,73 @@ transport sip_transport_values, dtmfmode sip_dtmfmode_values, directmedia sip_directmedia_values, - nat VARCHAR(29), + nat VARCHAR(40), callgroup VARCHAR(40), pickupgroup VARCHAR(40), language VARCHAR(40), disallow VARCHAR(200), allow VARCHAR(200), insecure VARCHAR(40), - trustrpid yes_no_values, - progressinband sip_progressinband_values, - promiscredir yes_no_values, - useclientcode yes_no_values, accountcode VARCHAR(40), - setvar VARCHAR(200), + variable VARCHAR(200), callerid VARCHAR(40), + callerpresentation sip_callerpresentation_values, amaflags VARCHAR(40), - callcounter yes_no_values, + maxcalls INTEGER, busylevel INTEGER, allowoverlap yes_no_values, allowsubscribe yes_no_values, - videosupport yes_no_values, + video yes_no_values, maxcallbitrate INTEGER, - rfc2833compensate yes_no_values, mailbox VARCHAR(40), - "session-timers" sip_session_timers_values, - "session-expires" INTEGER, - "session-minse" INTEGER, - "session-refresher" sip_session_refresher_values, - t38pt_usertpsource VARCHAR(40), - regexten VARCHAR(40), + timer sip_timer_values, + timermaxexpires INTEGER, + timernminexpires INTEGER, + timerrefresher sip_tiemrrefresher_values, fromdomain VARCHAR(40), fromuser VARCHAR(40), - "qualify" VARCHAR(40), - defaultip VARCHAR(45), + maxqualify VARCHAR(40), + qualifyexpiry INTEGER, rtptimeout INTEGER, rtpholdtimeout INTEGER, - sendrpid yes_no_values, - outboundproxy VARCHAR(40), - callbackextension VARCHAR(40), + rtpkeepalive INTEGER, + identity VARCHAR(40), + diversion yes_no_values, + reason yes_no_values, + proxy VARCHAR(40), timert1 INTEGER, timerb INTEGER, - qualifyfreq INTEGER, - constantssrc yes_no_values, contactpermit VARCHAR(95), contactdeny VARCHAR(95), usereqphone yes_no_values, - textsupport yes_no_values, + "text" yes_no_values, faxdetect yes_no_values, - buggymwi yes_no_values, - auth VARCHAR(40), - fullname VARCHAR(40), - trunkname VARCHAR(40), - cid_number VARCHAR(40), - callingpres sip_callingpres_values, + authorization VARCHAR(40), mohinterpret VARCHAR(40), mohsuggest VARCHAR(40), parkinglot VARCHAR(40), - hasvoicemail yes_no_values, subscribemwi yes_no_values, - vmexten VARCHAR(40), + mwiexten VARCHAR(40), + encryption yes_no_values, autoframing yes_no_values, - rtpkeepalive INTEGER, - "call-limit" INTEGER, - g726nonstandard yes_no_values, - ignoresdpversion yes_no_values, allowtransfer yes_no_values, dynamic yes_no_values, - path VARCHAR(256), - supportpath yes_no_values, + lastpath VARCHAR(256), + "path" yes_no_values, + cisco yes_no_values, + busywhendnd yes_no_values, + donotdisturb yes_no_values, + callforward VARCHAR(40), + huntgroup yes_no_values, + huntgroupdefault yes_no_values, + register VARCHAR(200), + subscribe VARCHAR(200), + qrturl VARCHAR(200), + keepconference yes_no_values, + multiadminconference yes_no_values, + pickupnotify VARCHAR(40), + pickupnotifytimer INTEGER, + parknotifytimer INTEGER, PRIMARY KEY (id), UNIQUE (name) ); diff '--color=auto' -durN asterisk-22.6.0.orig/include/asterisk/callerid.h asterisk-22.6.0/include/asterisk/callerid.h --- asterisk-22.6.0.orig/include/asterisk/callerid.h 2025-10-17 13:01:59.878454941 +1300 +++ asterisk-22.6.0/include/asterisk/callerid.h 2025-10-21 18:12:24.498603818 +1300 @@ -557,7 +557,11 @@ /*! Update from call transfer(active) (Party has already answered) */ AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, /*! Update from call transfer(alerting) (Party has not answered yet) */ - AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING + AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING, + /*! Update from a call being parked */ + AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL, + /*! Update from a call joining a conference */ + AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE }; /*! diff '--color=auto' -durN asterisk-22.6.0.orig/include/asterisk/parking.h asterisk-22.6.0/include/asterisk/parking.h --- asterisk-22.6.0.orig/include/asterisk/parking.h 2025-10-17 13:01:59.881454861 +1300 +++ asterisk-22.6.0/include/asterisk/parking.h 2025-10-21 18:12:24.499603791 +1300 @@ -50,6 +50,7 @@ PARKED_CALL_UNPARKED, PARKED_CALL_FAILED, PARKED_CALL_SWAP, + PARKED_CALL_REMINDER, }; /*! diff '--color=auto' -durN asterisk-22.6.0.orig/main/callerid.c asterisk-22.6.0/main/callerid.c --- asterisk-22.6.0.orig/main/callerid.c 2025-10-17 13:01:59.898454408 +1300 +++ asterisk-22.6.0/main/callerid.c 2025-10-21 18:12:24.500603764 +1300 @@ -1472,7 +1472,9 @@ { AST_CONNECTED_LINE_UPDATE_SOURCE_DIVERSION, "diversion", "Call Diversion (Deprecated, use REDIRECTING)" }, { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, "transfer_active", "Call Transfer(Active)" }, { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, "transfer", "Call Transfer(Active)" },/* Old name must come after new name. */ - { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING, "transfer_alerting", "Call Transfer(Alerting)" } + { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING, "transfer_alerting", "Call Transfer(Alerting)" }, + { AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL, "parked_call", "Call Parked" }, + { AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE, "conference", "Conference" } /* *INDENT-ON* */ }; diff '--color=auto' -durN asterisk-22.6.0.orig/main/cel.c asterisk-22.6.0/main/cel.c --- asterisk-22.6.0.orig/main/cel.c 2025-10-17 13:01:59.899454382 +1300 +++ asterisk-22.6.0/main/cel.c 2025-10-21 18:12:24.500603764 +1300 @@ -1158,6 +1158,8 @@ case PARKED_CALL_SWAP: reason = "ParkedCallSwap"; break; + case PARKED_CALL_REMINDER: + return; } if (parked_payload->retriever) { diff '--color=auto' -durN asterisk-22.6.0.orig/main/pbx.c asterisk-22.6.0/main/pbx.c --- asterisk-22.6.0.orig/main/pbx.c 2025-10-17 13:01:59.904454248 +1300 +++ asterisk-22.6.0/main/pbx.c 2025-10-21 18:12:24.502603711 +1300 @@ -8406,12 +8406,16 @@ "Context: %s\r\n" "Hint: %s\r\n" "Status: %d\r\n" - "StatusText: %s\r\n\r\n", + "StatusText: %s\r\n\r\n" + "PresenceStatus: %d\r\n" + "PresenceStatusText: %s\r\n\r\n", hint->exten->exten, hint->exten->parent->name, hint->exten->app, hint->laststate, - ast_extension_state2str(hint->laststate)); + ast_extension_state2str(hint->laststate), + hint->last_presence_state, + ast_presence_state2str(hint->last_presence_state)); ao2_unlock(hint); } diff '--color=auto' -durN asterisk-22.6.0.orig/main/presencestate.c asterisk-22.6.0/main/presencestate.c --- asterisk-22.6.0.orig/main/presencestate.c 2025-10-17 13:01:59.904454248 +1300 +++ asterisk-22.6.0/main/presencestate.c 2025-10-21 18:12:24.503603685 +1300 @@ -75,13 +75,13 @@ enum ast_presence_state state; } state2string[] = { - { "not_set", AST_PRESENCE_NOT_SET}, - { "unavailable", AST_PRESENCE_UNAVAILABLE }, - { "available", AST_PRESENCE_AVAILABLE}, - { "away", AST_PRESENCE_AWAY}, - { "xa", AST_PRESENCE_XA}, - { "chat", AST_PRESENCE_CHAT}, - { "dnd", AST_PRESENCE_DND}, + { "Not_Set", AST_PRESENCE_NOT_SET}, + { "Unavailable", AST_PRESENCE_UNAVAILABLE }, + { "Available", AST_PRESENCE_AVAILABLE}, + { "Away", AST_PRESENCE_AWAY}, + { "XA", AST_PRESENCE_XA}, + { "Chat", AST_PRESENCE_CHAT}, + { "DND", AST_PRESENCE_DND}, }; static struct ast_manager_event_blob *presence_state_to_ami(struct stasis_message *msg); diff '--color=auto' -durN asterisk-22.6.0.orig/res/parking/parking_applications.c asterisk-22.6.0/res/parking/parking_applications.c --- asterisk-22.6.0.orig/res/parking/parking_applications.c 2025-10-17 13:01:59.917453902 +1300 +++ asterisk-22.6.0/res/parking/parking_applications.c 2025-10-21 18:12:24.504603658 +1300 @@ -80,6 +80,11 @@ Use a timeout of duration seconds instead of the timeout specified by the parking lot. + @@ -251,6 +256,7 @@ OPT_ARG_COMEBACK, OPT_ARG_TIMEOUT, OPT_ARG_MUSICONHOLD, + OPT_ARG_REMINDER, OPT_ARG_ARRAY_SIZE /* Always the last element of the enum */ }; @@ -261,6 +267,7 @@ MUXFLAG_COMEBACK_OVERRIDE = (1 << 3), MUXFLAG_TIMEOUT_OVERRIDE = (1 << 4), MUXFLAG_MUSICONHOLD = (1 << 5), + MUXFLAG_REMINDER_OVERRIDE = (1 << 6), }; AST_APP_OPTIONS(park_opts, { @@ -270,6 +277,7 @@ AST_APP_OPTION_ARG('c', MUXFLAG_COMEBACK_OVERRIDE, OPT_ARG_COMEBACK), AST_APP_OPTION_ARG('t', MUXFLAG_TIMEOUT_OVERRIDE, OPT_ARG_TIMEOUT), AST_APP_OPTION_ARG('m', MUXFLAG_MUSICONHOLD, OPT_ARG_MUSICONHOLD), + AST_APP_OPTION_ARG('T', MUXFLAG_REMINDER_OVERRIDE, OPT_ARG_REMINDER), }); static int apply_option_timeout (int *var, char *timeout_arg) @@ -287,8 +295,23 @@ return 0; } +static int apply_option_reminder (int *var, char *reminder_arg) +{ + if (ast_strlen_zero(reminder_arg)) { + ast_log(LOG_ERROR, "No duration value provided for the reminder ('T') option.\n"); + return -1; + } + + if (sscanf(reminder_arg, "%d", var) != 1 || *var < 0) { + ast_log(LOG_ERROR, "Duration value provided for timeout ('T') option must be 0 or greater.\n"); + return -1; + } + + return 0; +} + static int park_app_parse_data(const char *data, int *disable_announce, int *use_ringing, int *randomize, int *time_limit, - char **comeback_override, char **lot_name, char **musicclass) + int *reminder_delay, char **comeback_override, char **lot_name, char **musicclass) { char *parse; struct ast_flags flags = { 0 }; @@ -311,6 +334,12 @@ } } + if (ast_test_flag(&flags, MUXFLAG_REMINDER_OVERRIDE)) { + if (apply_option_reminder(reminder_delay, opts[OPT_ARG_REMINDER])) { + return -1; + } + } + if (ast_test_flag(&flags, MUXFLAG_COMEBACK_OVERRIDE)) { *comeback_override = ast_strdup(opts[OPT_ARG_COMEBACK]); } @@ -377,7 +406,7 @@ ast_channel_unlock(chan); } -static int setup_park_common_datastore(struct ast_channel *parkee, const char *parker_uuid, const char *comeback_override, int randomize, int time_limit, int silence_announce) +static int setup_park_common_datastore(struct ast_channel *parkee, const char *parker_uuid, const char *comeback_override, int randomize, int time_limit, int reminder_delay, int silence_announce) { struct ast_datastore *datastore = NULL; struct park_common_datastore *park_datastore; @@ -429,6 +458,7 @@ park_datastore->randomize = randomize; park_datastore->time_limit = time_limit; + park_datastore->reminder_delay = reminder_delay; park_datastore->silence_announce = silence_announce; if (comeback_override) { @@ -477,6 +507,7 @@ data_copy->randomize = data->randomize; data_copy->time_limit = data->time_limit; + data_copy->reminder_delay = data->reminder_delay; data_copy->silence_announce = data->silence_announce; if (data->comeback_override) { @@ -500,7 +531,7 @@ static struct ast_bridge *park_common_setup2(struct ast_channel *parkee, struct ast_channel *parker, const char *lot_name, const char *comeback_override, const char *musicclass, - int use_ringing, int randomize, int time_limit, int silence_announcements) + int use_ringing, int randomize, int time_limit, int reminder_delay, int silence_announcements) { struct ast_bridge *parking_bridge; RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup); @@ -540,15 +571,16 @@ ast_channel_set_bridge_role_option(parkee, "holding_participant", "moh_class", musicclass); } setup_park_common_datastore(parkee, ast_channel_uniqueid(parker), comeback_override, randomize, time_limit, - silence_announcements); + reminder_delay, silence_announcements); return parking_bridge; } struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker, const char *lot_name, const char *comeback_override, - int use_ringing, int randomize, int time_limit, int silence_announcements) + int use_ringing, int randomize, int time_limit, int reminder_delay, int silence_announcements) { - return park_common_setup2(parkee, parker, lot_name, comeback_override, NULL, use_ringing, randomize, time_limit, silence_announcements); + return park_common_setup2(parkee, parker, lot_name, comeback_override, NULL, use_ringing, randomize, + time_limit, reminder_delay, silence_announcements); } struct ast_bridge *park_application_setup(struct ast_channel *parkee, struct ast_channel *parker, const char *app_data, @@ -557,17 +589,19 @@ int use_ringing = 0; int randomize = 0; int time_limit = -1; + int reminder_delay = -1; RAII_VAR(char *, comeback_override, NULL, ast_free); RAII_VAR(char *, lot_name_app_arg, NULL, ast_free); RAII_VAR(char *, musicclass, NULL, ast_free); if (app_data) { - park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, &comeback_override, &lot_name_app_arg, &musicclass); + park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, + &reminder_delay, &comeback_override, &lot_name_app_arg, &musicclass); } return park_common_setup2(parkee, parker, lot_name_app_arg, comeback_override, musicclass, use_ringing, - randomize, time_limit, silence_announcements ? *silence_announcements : 0); + randomize, time_limit, reminder_delay, silence_announcements ? *silence_announcements : 0); } diff '--color=auto' -durN asterisk-22.6.0.orig/res/parking/parking_bridge.c asterisk-22.6.0/res/parking/parking_bridge.c --- asterisk-22.6.0.orig/res/parking/parking_bridge.c 2025-10-17 13:01:59.917453902 +1300 +++ asterisk-22.6.0/res/parking/parking_bridge.c 2025-10-21 18:12:24.505603631 +1300 @@ -31,6 +31,7 @@ #include "asterisk/term.h" #include "asterisk/features.h" #include "asterisk/bridge_internal.h" +#include "asterisk/callerid.h" struct ast_bridge_parking { @@ -104,7 +105,7 @@ * * \note ao2_cleanup this reference when you are done using it or you'll cause leaks. */ -static struct parked_user *generate_parked_user(struct parking_lot *lot, struct ast_channel *chan, const char *parker_channel_name, const char *parker_dial_string, int use_random_space, int time_limit) +static struct parked_user *generate_parked_user(struct parking_lot *lot, struct ast_channel *chan, const char *parker_channel_name, const char *parker_dial_string, int use_random_space, int time_limit, int reminder_delay) { struct parked_user *new_parked_user; int preferred_space = -1; /* Initialize to use parking lot defaults */ @@ -161,6 +162,7 @@ new_parked_user->start = ast_tvnow(); new_parked_user->time_limit = (time_limit >= 0) ? time_limit : lot->cfg->parkingtime; + new_parked_user->reminder_delay = (reminder_delay >= 0) ? reminder_delay : lot->cfg->remindertime; if (parker_dial_string) { new_parked_user->parker_dial_string = ast_strdup(parker_dial_string); @@ -208,6 +210,8 @@ struct ast_channel_snapshot *parker = NULL; const char *parker_channel_name = NULL; RAII_VAR(struct park_common_datastore *, park_datastore, NULL, park_common_datastore_free); + char parking_space[16]; + struct ast_party_connected_line connected; ast_bridge_base_v_table.push(&self->base, bridge_channel, swap); @@ -247,6 +251,7 @@ ast_bridge_channel_unlock(swap); parking_set_duration(bridge_channel->features, pu); + parking_set_reminder(bridge_channel->features, pu); if (parking_channel_set_roles(bridge_channel->chan, self->lot, use_ringing)) { ast_log(LOG_WARNING, "Failed to apply holding bridge roles to %s while joining the parking lot.\n", @@ -286,7 +291,7 @@ } pu = generate_parked_user(self->lot, bridge_channel->chan, parker_channel_name, - park_datastore->parker_dial_string, park_datastore->randomize, park_datastore->time_limit); + park_datastore->parker_dial_string, park_datastore->randomize, park_datastore->time_limit, park_datastore->reminder_delay); ao2_cleanup(parker); if (!pu) { publish_parked_call_failure(bridge_channel->chan); @@ -311,6 +316,7 @@ /* Apply parking duration limits */ parking_set_duration(bridge_channel->features, pu); + parking_set_reminder(bridge_channel->features, pu); /* Set this to the bridge pvt so that we don't have to refind the parked user associated with this bridge channel again. */ bridge_channel->bridge_pvt = pu; @@ -320,6 +326,21 @@ COLORIZE(COLOR_BRMAGENTA, 0, self->lot->name), pu->parking_space); + snprintf(parking_space, sizeof(parking_space), "%d", pu->parking_space); + ast_party_connected_line_init(&connected); + + connected.id.name.str = ast_strdup("Park"); + connected.id.name.valid = 1; + + connected.id.number.str = ast_strdup(parking_space); + connected.id.number.valid = 1; + + connected.id.name.presentation = connected.id.number.presentation = AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_PASSED_SCREEN; + connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL; + + ast_channel_update_connected_line(bridge_channel->chan, &connected, NULL); + ast_party_connected_line_free(&connected); + parking_notify_metermaids(pu->parking_space, self->lot->cfg->parking_con, AST_DEVICE_INUSE); return 0; diff '--color=auto' -durN asterisk-22.6.0.orig/res/parking/parking_bridge_features.c asterisk-22.6.0/res/parking/parking_bridge_features.c --- asterisk-22.6.0.orig/res/parking/parking_bridge_features.c 2025-10-17 13:01:59.917453902 +1300 +++ asterisk-22.6.0/res/parking/parking_bridge_features.c 2025-10-21 18:12:24.505603631 +1300 @@ -681,6 +681,14 @@ return -1; } +static int parking_reminder_callback(struct ast_bridge_channel *bridge_channel, void *hook_pvt) +{ + struct parked_user *user = hook_pvt; + + publish_parked_call(user, PARKED_CALL_REMINDER); + return -1; +} + void say_parking_space(struct ast_bridge_channel *bridge_channel, const char *payload) { unsigned int numeric_value; @@ -729,6 +737,33 @@ ao2_ref(user, -1); } } + +void parking_set_reminder(struct ast_bridge_features *features, struct parked_user *user) +{ + unsigned int reminder_delay; + + reminder_delay = user->reminder_delay * 1000; + + if (!reminder_delay) { + /* The is no reminder delay that we need to apply */ + return; + } + + /* If the reminder delay has already been passed skip it */ + reminder_delay = ast_remaining_ms(user->start, reminder_delay); + if (reminder_delay <= 0) { + return; + } + + /* The interval hook is going to need a reference to the parked_user */ + ao2_ref(user, +1); + + if (ast_bridge_interval_hook(features, 0, reminder_delay, + parking_reminder_callback, user, __ao2_cleanup, AST_BRIDGE_HOOK_REMOVE_ON_PULL)) { + ast_log(LOG_ERROR, "Failed to apply reminder delay to the parked call.\n"); + ao2_ref(user, -1); + } +} /*! \brief Dial plan function to get the parking lot channel of an occupied parking lot */ static int func_get_parkingslot_channel(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len) diff '--color=auto' -durN asterisk-22.6.0.orig/res/parking/parking_manager.c asterisk-22.6.0/res/parking/parking_manager.c --- asterisk-22.6.0.orig/res/parking/parking_manager.c 2025-10-17 13:01:59.917453902 +1300 +++ asterisk-22.6.0/res/parking/parking_manager.c 2025-10-21 18:12:24.506603605 +1300 @@ -482,7 +482,7 @@ struct ast_channel *chan, const char *parkinglot, int timeout_override) { struct ast_bridge *parking_bridge = park_common_setup(chan, - chan, parkinglot, NULL, 0, 0, timeout_override, 1); + chan, parkinglot, NULL, 0, 0, timeout_override, -1, 1); if (!parking_bridge) { astman_send_error(s, m, "Park action failed\n"); @@ -691,6 +691,9 @@ case PARKED_CALL_SWAP: event_type = "ParkedCallSwap"; break; + case PARKED_CALL_REMINDER: + /* PARKED_CALL_REMINDER doesn't currently get a message is is used exclusively for subscriptions */ + return; case PARKED_CALL_FAILED: /* PARKED_CALL_FAILED doesn't currently get a message and is used exclusively for bridging */ return; diff '--color=auto' -durN asterisk-22.6.0.orig/res/parking/parking_ui.c asterisk-22.6.0/res/parking/parking_ui.c --- asterisk-22.6.0.orig/res/parking/parking_ui.c 2025-10-17 13:01:59.917453902 +1300 +++ asterisk-22.6.0/res/parking/parking_ui.c 2025-10-21 18:12:24.506603605 +1300 @@ -58,6 +58,7 @@ ast_cli(fd, "Parking Context : %s\n", lot->cfg->parking_con); ast_cli(fd, "Parking Spaces : %d-%d\n", lot->cfg->parking_start, lot->cfg->parking_stop); ast_cli(fd, "Parking Time : %u sec\n", lot->cfg->parkingtime); + ast_cli(fd, "Reminder Time : %u sec\n", lot->cfg->remindertime); ast_cli(fd, "Comeback to Origin : %s\n", lot->cfg->comebacktoorigin ? "yes" : "no"); ast_cli(fd, "Comeback Context : %s%s\n", lot->cfg->comebackcontext, lot->cfg->comebacktoorigin ? " (comebacktoorigin=yes, not used)" : ""); ast_cli(fd, "Comeback Dial Time : %u sec\n", lot->cfg->comebackdialtime); diff '--color=auto' -durN asterisk-22.6.0.orig/res/parking/res_parking.h asterisk-22.6.0/res/parking/res_parking.h --- asterisk-22.6.0.orig/res/parking/res_parking.h 2025-10-17 13:01:59.917453902 +1300 +++ asterisk-22.6.0/res/parking/res_parking.h 2025-10-21 18:12:24.506603605 +1300 @@ -67,6 +67,7 @@ int parking_stop; /*!< Last space in the parking lot */ unsigned int parkingtime; /*!< Analogous to parkingtime config option */ + unsigned int remindertime; /*!< Analogous to remindertime config option */ unsigned int comebackdialtime; /*!< Analogous to comebackdialtime config option */ unsigned int parkfindnext; /*!< Analogous to parkfindnext config option */ unsigned int parkext_exclusive; /*!< Analogous to parkext_exclusive config option */ @@ -110,6 +111,7 @@ char comeback[AST_MAX_CONTEXT]; /*!< Where to go on parking timeout */ char *parker_dial_string; /*!< dialstring to call back with comebacktoorigin. Used timeout extension generation and call control */ unsigned int time_limit; /*!< How long this specific channel may remain in the parking lot before timing out */ + unsigned int reminder_delay; /*!< How long to wait before sending a reminder */ struct parking_lot *lot; /*!< Which parking lot the user is parked to */ enum park_call_resolution resolution; /*!< How did the parking session end? If the call is in a bridge, lock parked_user before checking/setting */ }; @@ -268,6 +270,15 @@ void parking_set_duration(struct ast_bridge_features *features, struct parked_user *user); /*! + * \since 13.7.2 + * \brief Setup a reminder delay feature an ast_bridge_features for parking + * + * \param features The ast_bridge_features we are establishing the interval hook on + * \param user The parked_user receiving the timeout duration limits + */ +void parking_set_reminder(struct ast_bridge_features *features, struct parked_user *user); + +/*! * \since 12.0.0 * \brief Get a pointer to the parking lot container for purposes such as iteration * @@ -422,7 +433,7 @@ */ struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker, const char *lot_name, const char *comeback_override, - int use_ringing, int randomize, int time_limit, int silence_announcements); + int use_ringing, int randomize, int time_limit, int reminder_delay, int silence_announcements); /*! * \since 12.0.0 @@ -451,6 +462,7 @@ char *comeback_override; /*!< Optional goto string for where to send the call after we are done */ int randomize; /*!< Pick a parking space to enter on at random */ int time_limit; /*!< time limit override. -1 values don't override, 0 for unlimited time, >0 for custom time limit in seconds */ + int reminder_delay; /*!< reminder delay override. -1 values don't override, 0 for none, >0 custom reminder delay in seconds */ int silence_announce; /*!< Used when a call parks itself to keep it from hearing the parked call announcement */ }; diff '--color=auto' -durN asterisk-22.6.0.orig/res/res_format_attr_h264.c asterisk-22.6.0/res/res_format_attr_h264.c --- asterisk-22.6.0.orig/res/res_format_attr_h264.c 2025-10-17 13:01:59.920453822 +1300 +++ asterisk-22.6.0/res/res_format_attr_h264.c 2025-10-21 18:12:24.507603578 +1300 @@ -47,6 +47,7 @@ * length. It must ALWAYS be a string literal representation of one less than * H264_MAX_SPS_PPS_SIZE */ #define H264_MAX_SPS_PPS_SIZE_SCAN_LIMIT "15" +#define H264_MAX_IMAGEATTR_SIZE 256 struct h264_attr { unsigned int PROFILE_IDC; @@ -71,6 +72,7 @@ unsigned int LEVEL_ASYMMETRY_ALLOWED; char SPS[H264_MAX_SPS_PPS_SIZE]; char PPS[H264_MAX_SPS_PPS_SIZE]; + char IMAGEATTR[H264_MAX_IMAGEATTR_SIZE]; }; static void h264_destroy(struct ast_format *format) @@ -160,6 +162,12 @@ ast_copy_string(attr->PPS, attr2->PPS, sizeof(attr->PPS)); } + if (attr1 && !ast_strlen_zero(attr1->IMAGEATTR)) { + ast_copy_string(attr->IMAGEATTR, attr1->IMAGEATTR, sizeof(attr->IMAGEATTR)); + } else if (attr2 && !ast_strlen_zero(attr2->IMAGEATTR)) { + ast_copy_string(attr->IMAGEATTR, attr2->IMAGEATTR, sizeof(attr->IMAGEATTR)); + } + return cloned; } @@ -307,6 +315,42 @@ return; } +static struct ast_format *h264_attribute_set(const struct ast_format *format, const char *name, const char *value) +{ + struct ast_format *cloned = ast_format_clone(format); + struct h264_attr *attr; + + if (!cloned) { + return NULL; + } + attr = ast_format_get_attribute_data(cloned); + + if (!strcmp(name, "imageattr")) { + ast_copy_string(attr->IMAGEATTR, value, sizeof(attr->IMAGEATTR)); + } else { + ast_log(LOG_WARNING, "unknown attribute type %s\n", name); + } + + return cloned; +} + +static const void *h264_attribute_get(const struct ast_format *format, const char *name) +{ + struct h264_attr *attr = ast_format_get_attribute_data(format); + + if (!attr) { + return NULL; + } + + if (!strcmp(name, "imageattr")) { + return attr->IMAGEATTR; + } else { + ast_log(LOG_WARNING, "unknown attribute type %s\n", name); + } + + return NULL; +} + static struct ast_format_interface h264_interface = { .format_destroy = h264_destroy, .format_clone = h264_clone, @@ -314,6 +358,8 @@ .format_get_joint = h264_getjoint, .format_parse_sdp_fmtp = h264_parse_sdp_fmtp, .format_generate_sdp_fmtp = h264_generate_sdp_fmtp, + .format_attribute_set = h264_attribute_set, + .format_attribute_get = h264_attribute_get, }; static int unload_module(void) diff '--color=auto' -durN asterisk-22.6.0.orig/res/res_parking.c asterisk-22.6.0/res/res_parking.c --- asterisk-22.6.0.orig/res/res_parking.c 2025-10-17 13:01:59.923453742 +1300 +++ asterisk-22.6.0/res/res_parking.c 2025-10-21 18:12:24.508603551 +1300 @@ -140,6 +140,9 @@ Amount of time a call will remain parked before giving up (in seconds). + + Amount of time before sending a reminder warning (in seconds). + 12.0.0 @@ -1014,6 +1017,7 @@ cfg->parking_start = source->parking_start; cfg->parking_stop = source->parking_stop; cfg->parkingtime = source->parkingtime; + cfg->remindertime = source->remindertime; cfg->comebackdialtime = source->comebackdialtime; cfg->parkfindnext = source->parkfindnext; cfg->parkext_exclusive = source->parkext_exclusive; @@ -1286,6 +1290,7 @@ aco_option_register(&cfg_info, "parkext", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parkext)); aco_option_register(&cfg_info, "context", ACO_EXACT, parking_lot_types, "parkedcalls", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parking_con)); aco_option_register(&cfg_info, "parkingtime", ACO_EXACT, parking_lot_types, "45", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, parkingtime)); + aco_option_register(&cfg_info, "remindertime", ACO_EXACT, parking_lot_types, "0", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, remindertime)); aco_option_register(&cfg_info, "comebacktoorigin", ACO_EXACT, parking_lot_types, "yes", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, comebacktoorigin)); aco_option_register(&cfg_info, "comebackcontext", ACO_EXACT, parking_lot_types, "parkedcallstimeout", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, comebackcontext)); aco_option_register(&cfg_info, "comebackdialtime", ACO_EXACT, parking_lot_types, "30", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, comebackdialtime)); diff '--color=auto' -durN asterisk-22.6.0.orig/res/res_srtp.c asterisk-22.6.0/res/res_srtp.c --- asterisk-22.6.0.orig/res/res_srtp.c 2025-10-17 13:01:59.933453476 +1300 +++ asterisk-22.6.0/res/res_srtp.c 2025-10-21 18:12:24.508603551 +1300 @@ -1166,19 +1166,19 @@ * If you want to enable one of those defines, please, go for * CFLAGS='-DENABLE_SRTP_AES_GCM' ./configure && sudo make install */ - { len, 0, 30 }, +#if defined(HAVE_SRTP_GCM) && defined(ENABLE_SRTP_AES_GCM) && defined(ENABLE_SRTP_AES_256) + { AST_SRTP_CRYPTO_TAG_16, AST_SRTP_CRYPTO_AES_256, AES_256_GCM_KEYSIZE_WSALT }, +#endif #if defined(HAVE_SRTP_GCM) && defined(ENABLE_SRTP_AES_GCM) { AST_SRTP_CRYPTO_TAG_16, 0, AES_128_GCM_KEYSIZE_WSALT }, #endif #if defined(HAVE_SRTP_256) && defined(ENABLE_SRTP_AES_256) { len, AST_SRTP_CRYPTO_AES_256, 46 }, #endif -#if defined(HAVE_SRTP_GCM) && defined(ENABLE_SRTP_AES_GCM) && defined(ENABLE_SRTP_AES_256) - { AST_SRTP_CRYPTO_TAG_16, AST_SRTP_CRYPTO_AES_256, AES_256_GCM_KEYSIZE_WSALT }, -#endif #if defined(HAVE_SRTP_192) && defined(ENABLE_SRTP_AES_192) { len, AST_SRTP_CRYPTO_AES_192, 38 }, #endif + { len, 0, 30 }, }; struct ast_sdp_srtp *tmp = srtp; int i;