diff -durN asterisk-22.3.0.orig/channels/Makefile asterisk-22.3.0/channels/Makefile --- asterisk-22.3.0.orig/channels/Makefile 2025-04-17 11:10:31.735064449 +1200 +++ asterisk-22.3.0/channels/Makefile 2025-04-17 11:11:22.784703995 +1200 @@ -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 -durN asterisk-22.3.0.orig/channels/chan_sip.c asterisk-22.3.0/channels/chan_sip.c --- asterisk-22.3.0.orig/channels/chan_sip.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/chan_sip.c 2025-04-17 11:11:22.785703968 +1200 @@ -0,0 +1,657 @@ +/* + * 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/file.h" +#include "asterisk/lock.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/threadstorage.h" +#include "asterisk/strings.h" +#include "asterisk/utils.h" +#include "asterisk/io.h" +#include "asterisk/sched.h" +#include "asterisk/netsock2.h" +#include "asterisk/tcptls.h" +#include "asterisk/channel.h" +#include "asterisk/indications.h" +#include "asterisk/rtp_engine.h" +#include "asterisk/pbx.h" +#include "asterisk/config.h" +#include "asterisk/acl.h" +#include "asterisk/sip_api.h" +#include "asterisk/message.h" +#include "asterisk/cli.h" + +#include "sip/include/sip.h" +#include "sip/include/netsock.h" +#include "sip/include/route.h" +#include "sip/include/request.h" +#include "sip/include/response.h" +#include "sip/include/proxy.h" +#include "sip/include/stimer.h" +#include "sip/include/auth_realms.h" +#include "sip/include/domain.h" +#include "sip/include/peers.h" +#include "sip/include/realtime.h" +#include "sip/include/registry.h" +#include "sip/include/mwi_subscriptions.h" +#include "sip/include/dialog.h" +#include "sip/include/sdp.h" +#include "sip/include/utils.h" +#include "sip/include/config.h" +#include "sip/include/events.h" +#include "sip/include/chan_tech.h" +#include "sip/include/rtp_glue.h" +#include "sip/include/handlers.h" +#include "sip/include/cli.h" +#include "sip/include/dialplan_funcs.h" +#include "sip/include/dialplan_apps.h" +#include "sip/include/manager.h" +#include "sip/include/security_events.h" +#include "sip/include/pickup.h" +#include "sip/include/conference.h" + +static int unload_module(void); +static void *sip_monitor_thread(void *data); + +int sip_log_level = -1; + +/* Protect the monitoring thread, so only one process can kill or start it, and not when it's doing something critical. */ +AST_MUTEX_DEFINE_STATIC(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 */ + +int sip_debug = SIP_DEBUG_NONE; +struct ast_sockaddr sip_debug_address; + +STASIS_MESSAGE_TYPE_DEFN(sip_session_timeout_type, + .to_ami = sip_session_timeout_to_ami, +); + +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"); +} + +/* 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 */ +static 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_read_id = ast_io_add(sip_io_context, sip_socket_fd, sip_socket_read, AST_IO_IN, NULL); + } + + /* From here on out, we die whenever asked */ + for (;;) { + /* Check for a reload request */ + ast_mutex_lock(&sip_reload_lock); + + reloading = sip_reloading; + sip_reloading = FALSE; + + ast_mutex_unlock(&sip_reload_lock); + + if (reloading) { + time_t qualify_start, qualify_end; + + ast_verb(1, "Reloading SIP\n"); + + sip_config_parse(sip_reload_reason); + ast_sched_dump(sip_sched_context); + + qualify_start = time(NULL); + /* Prune peers who still are supposed to be deleted */ + sip_peer_unlink_all(TRUE); + + ast_debug(4, "Done destroying pruned peers\n"); + + sip_peer_qualify_all(); /* Send qualify (OPTIONS) to all peers */ + sip_peer_register_aliases_all(); /* Register aliases now that all peers have been added */ + sip_peer_keepalive_all(); /* Send keepalive to all peers */ + + /* Register with all services */ + sip_registry_send_all(); + sip_mwi_subscription_send_all(); + + qualify_end = time(0); + + ast_debug(4, "Reload finished. peer qualify/prune reg contact time %d s\n", (int) (qualify_end - qualify_start)); + + /* Change the I/O fd of our UDP socket */ + if (sip_socket_fd > -1) { + if (sip_socket_read_id) { + sip_socket_read_id = ast_io_change(sip_io_context, sip_socket_read_id, sip_socket_fd, NULL, 0, NULL); + } else { + sip_socket_read_id = ast_io_add(sip_io_context, sip_socket_fd, sip_socket_read, AST_IO_IN, NULL); + } + } else if (sip_socket_read_id) { + ast_io_remove(sip_io_context, sip_socket_read_id); + sip_socket_read_id = NULL; + } + } + + /* Check dialogs with rtp and rtptimeout. All dialogs which have rtp are in sip_dialogs_rtp_check.*/ + ao2_t_callback(sip_dialogs_rtp_check, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, sip_dialog_rtp_check, NULL, "unlink dialog"); + + /* Check dialogs marked to be destroyed. All dialogs with needdestroy set are in sip_dialogs_need_destroy. */ + ao2_t_callback(sip_dialogs_need_destroy, OBJ_NODATA | OBJ_MULTIPLE, sip_dialog_need_destroy, NULL, "dialog set need destroy"); + + /* 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; + } + + res = ast_io_wait(sip_io_context, res); + + if (res > 20) { + ast_debug(1, "ast_io_wait ran %d all at once\n", res); + } + + ast_mutex_lock(&sip_monitor_lock); + res = ast_sched_runq(sip_sched_context); + + if (res >= 20) { + ast_debug(1, "ast_sched_runq ran %d all at once\n", res); + } + + 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); + ast_log(LOG_WARNING, "Cannot stop monitor thread\n"); + + 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; +} + +/* Module loading including tests for configuration or dependencies. This function can return AST_MODULE_LOAD_FAILURE, + * AST_MODULE_LOAD_DECLINE, or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails tests return + * AST_MODULE_LOAD_FAILURE. If the module can not load the configuration file or other non-critical problem return + * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS. */ +static int load_module(void) +{ + int i; + + ast_verbose("SIP channel loading...\n"); + sip_log_level = ast_logger_register_level("SIP_HISTORY"); + + if (sip_log_level == -1) { + ast_log(LOG_WARNING, "Unable to register history log level\n"); + } + + /* The fact that ao2_containers can't resize automatically is a major worry! if the number of objects gets + * above MAX_XXX_BUCKETS, things will slow down */ + sip_peers = ao2_t_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 563, + sip_peer_hash, NULL, sip_peer_cmp, "alloc peers"); + sip_peers_by_address = ao2_t_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 563, + sip_peer_hash_by_address, NULL, sip_peer_cmp_by_address, "alloc peer addresses"); + + sip_dialogs = ao2_t_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 1031, + sip_dialog_hash, NULL, sip_dialog_cmp, "alloc dialogs"); + sip_dialogs_need_destroy = ao2_t_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 11, + NULL, NULL, NULL, "alloc dialogs need destroy"); + sip_dialogs_rtp_check = ao2_t_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 563, + sip_dialog_hash, NULL, sip_dialog_cmp, "alloc dialogs rtp check"); + + sip_tcptls_threads = ao2_t_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 1031, + sip_tcptls_thread_hash, NULL, sip_tcptls_thread_cmp, "alloc threads"); + + sip_registry = ao2_t_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 23, + sip_registry_hash, NULL, sip_registry_cmp, "alloc registry"); + sip_mwi_subscriptions = ao2_t_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, AO2_CONTAINER_ALLOC_OPT_INSERT_BEGIN, NULL, NULL, + "alloc mwi"); + + if (!sip_peers || !sip_peers_by_address || !sip_dialogs || !sip_dialogs_need_destroy || !sip_dialogs_rtp_check || + !sip_tcptls_threads || !sip_registry || !sip_mwi_subscriptions) { + ast_log(LOG_ERROR, "Unable to create primary SIP container(s)\n"); + unload_module(); + + return AST_MODULE_LOAD_DECLINE; + } + + ast_mutex_init(&sip_auth_realm_lock); + + if (!(sip_config.format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + unload_module(); + return AST_MODULE_LOAD_DECLINE; + } + + ast_mutex_init(&sip_netsock_lock); + + if (!(sip_sched_context = ast_sched_context_create())) { + ast_log(LOG_ERROR, "Unable to create scheduler context\n"); + unload_module(); + + return AST_MODULE_LOAD_DECLINE; + } + + if (!(sip_io_context = io_context_create())) { + ast_log(LOG_ERROR, "Unable to create I/O context\n"); + unload_module(); + + return AST_MODULE_LOAD_DECLINE; + } + + ast_mutex_init(&sip_reload_lock); + sip_reload_reason = CHANNEL_MODULE_LOAD; + + AST_LIST_HEAD_INIT(&sip_domains); + AST_LIST_HEAD_INIT(&sip_conferences); + + if (sip_config_parse(sip_reload_reason)) { /* Load the configuration from sip.conf */ + unload_module(); + return AST_MODULE_LOAD_DECLINE; + } + + /* Initialize bogus peer. Can be done first after sip_config_parse() */ + if (!(sip_bogus_peer = sip_peer_temp_alloc("(bogus_peer)"))) { + ast_log(LOG_ERROR, "Unable to create bogus_peer for authentication\n"); + unload_module(); + + return AST_MODULE_LOAD_DECLINE; + } + + /* Make sure the auth will always fail. */ + ast_string_field_set(sip_bogus_peer, md5secret, SIP_BOGUS_PEER_MD5SECRET); + + if (!(sip_tech.capabilities = ast_format_cap_alloc(0))) { + unload_module(); + return AST_MODULE_LOAD_DECLINE; + } + + ast_format_cap_append_by_type(sip_tech.capabilities, AST_MEDIA_TYPE_AUDIO); + + /* Make sure we can register our sip channel type */ + if (ast_channel_register(&sip_tech)) { + ast_log(LOG_ERROR, "Unable to register channel type 'SIP'\n"); + unload_module(); + + return AST_MODULE_LOAD_DECLINE; + } + + /* Tell the RTP engine about our RTP glue */ + ast_rtp_glue_register(&sip_rtp_glue); + + if (ast_msg_tech_register(&sip_msg_tech)) { + unload_module(); + + return AST_MODULE_LOAD_DECLINE; + } + + if (STASIS_MESSAGE_TYPE_INIT(sip_session_timeout_type)) { + unload_module(); + return AST_MODULE_LOAD_DECLINE; + } + + if (ast_sip_api_provider_register(&chan_sip_api_tech)) { + unload_module(); + return AST_MODULE_LOAD_DECLINE; + } + + /* Register all CLI functions for SIP */ + for (i = 0; sip_cli_commands[i].handler != NULL; i++) { + ast_cli_register(&sip_cli_commands[i]); + } + + /* Register dialplan applications */ + ast_register_application_xml("SIPDtmfMode", sip_app_dtmfmode); + ast_register_application_xml("SIPAddHeader", sip_app_addheader); + ast_register_application_xml("SIPRemoveHeader", sip_app_removeheader); + ast_register_application_xml("SIPCiscoPage", sip_app_ciscopage); + + /* Register dialplan functions */ + ast_custom_function_register(&sip_func_header); + ast_custom_function_register(&sip_func_headers); + ast_custom_function_register(&sip_func_peer); + ast_custom_function_register(&sip_func_checkdomain); + + /* Register manager commands */ + ast_manager_register_xml("SIPPeers", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, sip_manager_peers); + ast_manager_register_xml("SIPShowPeer", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, sip_manager_show_peer); + ast_manager_register_xml("SIPQualifyPeer", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, sip_manager_qualify_peer); + ast_manager_register_xml("SIPShowRegistry", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, sip_manager_show_registry); + ast_manager_register_xml("SIPNotify", EVENT_FLAG_SYSTEM, sip_manager_notify); + ast_manager_register_xml("SIPPeerStatus", EVENT_FLAG_SYSTEM, sip_manager_peer_status); + + sip_peer_qualify_all(); + sip_peer_keepalive_all(); + + sip_registry_send_all(); + sip_mwi_subscription_send_all(); + + /* And start the monitor for the first time */ + sip_monitor_restart(); + + if (sip_config.realtime_update_peer) { + ast_realtime_require_field(ast_check_realtime("sipregs") ? "sipregs" : "sippeers", + "name", RQ_CHAR, 10, "ipaddr", RQ_CHAR, INET6_ADDRSTRLEN - 1, "port", RQ_UINTEGER2, 5, + "regseconds", RQ_INTEGER4, 11, "defaultuser", RQ_CHAR, 10, "fullcontact", RQ_CHAR, 35, + "regserver", RQ_CHAR, 20, "useragent", RQ_CHAR, 20, "lastms", RQ_INTEGER4, 11, SENTINEL); + } + + 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 AST_MODULE_LOAD_SUCCESS; +} + +/* PBX unload module API */ +static int unload_module(void) +{ + struct sip_dialog *dialog; + struct sip_tcptls_thread *thread; + struct ao2_iterator iter; + struct timeval start; + int i; + + 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_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("SIPDtmfMode"); + ast_unregister_application("SIPAddHeader"); + ast_unregister_application("SIPRemoveHeader"); + ast_unregister_application("SIPCiscoPage"); + + /* Unregister dial plan functions */ + ast_custom_function_unregister(&sip_func_peer); + ast_custom_function_unregister(&sip_func_headers); + ast_custom_function_unregister(&sip_func_header); + ast_custom_function_unregister(&sip_func_checkdomain); + + /* Unregister AMI actions */ + ast_manager_unregister("SIPPeers"); + ast_manager_unregister("SIPShowPeer"); + ast_manager_unregister("SIPQualifyPeer"); + ast_manager_unregister("SIPShowRegistry"); + ast_manager_unregister("SIPNotify"); + ast_manager_unregister("SIPPeerStatus"); + + /* Unregister CLI commands */ + for (i = 0; sip_cli_commands[i].handler != NULL; i++) { + ast_cli_unregister(&sip_cli_commands[i]); + } + + /* 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_t_iterator_next(&iter, "bump thread"))) { + thread->stop = TRUE; + pthread_kill(thread->threadid, SIGURG); + + ao2_t_ref(thread, -1, "drop thread"); + } + + ao2_iterator_destroy(&iter); + + /* Hangup all dialogs if they have an owner */ + iter = ao2_iterator_init(sip_dialogs, 0); + + while ((dialog = ao2_t_iterator_next(&iter, "bump dialog"))) { + if (dialog->channel) { + ast_softhangup(dialog->channel, AST_SOFTHANGUP_APPUNLOAD); + } + + ao2_t_ref(dialog, -1, "drop dialog"); + } + + ao2_iterator_destroy(&iter); + ast_mutex_lock(&sip_monitor_lock); + + if (sip_monitor_threadid && (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 */ + sip_domain_destroy_all(); + sip_auth_realm_destroy_all(); + ast_mutex_destroy(&sip_auth_realm_lock); + + sip_registry_destroy_all(); + sip_mwi_subscription_destroy_all(); + sip_peer_unlink_all(FALSE); + + AST_LIST_HEAD_DESTROY(&sip_domains); + AST_LIST_HEAD_DESTROY(&sip_conferences); + + /* Destroy all the dialogs and free their memory */ + iter = ao2_iterator_init(sip_dialogs, 0); + + while ((dialog = ao2_t_iterator_next(&iter, "bump dialog"))) { + sip_dialog_unlink(dialog); + ao2_t_ref(dialog, -1, "drop dialog"); + } + + 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. + * XXX 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. */ + start = ast_tvnow(); + + while (ao2_container_count(sip_tcptls_threads) && (ast_tvdiff_sec(ast_tvnow(), 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.local_address); + + 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); + + ao2_t_cleanup(sip_registry, "free registry"); + ao2_t_cleanup(sip_mwi_subscriptions, "free mwi"); + + ao2_t_cleanup(sip_bogus_peer, "drop peer"); + + ao2_t_cleanup(sip_peers, "free peers"); + ao2_t_cleanup(sip_peers_by_address, "free peer addresses"); + + ao2_t_cleanup(sip_dialogs, "free dialogs"); + ao2_t_cleanup(sip_dialogs_need_destroy, "free dialogs need destroy"); + ao2_t_cleanup(sip_dialogs_rtp_check, "free dialogs rtp check"); + + ao2_t_cleanup(sip_tcptls_threads, "free threads"); + + sip_config.contact_acl = ast_free_acl_list(sip_config.contact_acl); + + if (sip_socket_read_id) { + ast_io_remove(sip_io_context, sip_socket_read_id); + sip_socket_read_id = NULL; + } + + close(sip_socket_fd); + io_context_destroy(sip_io_context); + + ast_mutex_destroy(&sip_netsock_lock); + + ast_sched_context_destroy(sip_sched_context); + sip_sched_context = NULL; + + ast_unload_realtime("sipregs"); + ast_unload_realtime("sippeers"); + + ast_mutex_destroy(&sip_reload_lock); + + if (sip_notify_types) { + ast_config_destroy(sip_notify_types); + sip_notify_types = NULL; + } + + ao2_t_cleanup(sip_tech.capabilities, "drop format_cap"); + sip_tech.capabilities = NULL; + + ao2_t_cleanup(sip_config.format_cap, "format_cap"); + sip_config.format_cap = NULL; + + STASIS_MESSAGE_TYPE_CLEANUP(sip_session_timeout_type); + + if (sip_log_level != -1) { + ast_logger_unregister_level("SIP_HISTORY"); + } + + return 0; +} + +/* Part of Asterisk module interface */ +static int reload_module(void) +{ + sip_config_reload(); + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Session Initiation Protocol (SIP)", + .support_level = AST_MODULE_SUPPORT_EXTENDED, + .load = load_module, + .unload = unload_module, + .reload = reload_module, + .load_pri = AST_MODPRI_CHANNEL_DRIVER, + .requires = "ccss,dnsmgr,udptl", + .optional_modules = "res_crypto", +); diff -durN asterisk-22.3.0.orig/channels/sip/auth_realms.c asterisk-22.3.0/channels/sip/auth_realms.c --- asterisk-22.3.0.orig/channels/sip/auth_realms.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/auth_realms.c 2025-04-17 11:11:22.787703915 +1200 @@ -0,0 +1,144 @@ +/* + * 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/auth_realms.h" + +static void sip_auth_realm_destroy(void *data); + +/* Authentication container for realm authentication */ +struct sip_auth_realm_head *sip_auth_realms; + +/* Global authentication container protection while adjusting the references. */ +ast_mutex_t sip_auth_realm_lock; + +/* Add realm authentication to credentials. */ +void sip_auth_realm_build(struct sip_auth_realm_head **auth_realms, const char *config, int lineno) +{ + char *username, *realm, *secret, *md5secret; + struct sip_auth_realm *auth_realm; + + if (ast_strlen_zero(config)) { + /* Nothing to add */ + return; + } + + username = ast_strdupa(config); + + /* split user[:secret] and relm */ + if ((realm = strrchr(username, '@'))) { + *realm++ = '\0'; + } + + if (ast_strlen_zero(username) || ast_strlen_zero(realm)) { + ast_log(LOG_WARNING, "Format for authentication entry is user[:secret]@realm at line %d\n", lineno); + return; + } + + /* parse username at ':' for secret, or '#" for md5secret */ + if ((secret = strchr(username, ':'))) { + *secret++ = '\0'; + md5secret = NULL; + } else if ((md5secret = strchr(username, '#'))) { + *md5secret++ = '\0'; + } + + /* Create the continer if needed. */ + if (!*auth_realms) { + *auth_realms = ao2_t_alloc(sizeof(**auth_realms), sip_auth_realm_destroy, "alloc auth_realms"); + + if (!*auth_realms) { + /* Failed to create the credentials container. */ + return; + } + } + + /* Create the authentication credential entry. */ + if (!(auth_realm = ast_calloc_with_stringfields(1, struct sip_auth_realm, 256))) { + return; + } + + ast_string_field_set(auth_realm, username, username); + ast_string_field_set(auth_realm, realm, realm); + + if (secret) { + ast_string_field_set(auth_realm, secret, secret); + } else if (md5secret) { + ast_string_field_set(auth_realm, md5secret, md5secret); + } + + /* Add credential to container list. */ + AST_LIST_INSERT_TAIL(*auth_realms, auth_realm, next); + + ast_debug(1, "Added authentication for realm %s\n", realm); +} + +/* Realm authentication container destructor. */ +static void sip_auth_realm_destroy(void *data) +{ + struct sip_auth_realm_head *auth_realms = data; + struct sip_auth_realm *auth_realm; + + while ((auth_realm = AST_LIST_REMOVE_HEAD(auth_realms, next))) { + ast_string_field_free_memory(auth_realm); + ast_free(auth_realm); + } +} + +void sip_auth_realm_destroy_all(void) +{ + ast_mutex_lock(&sip_auth_realm_lock); + + if (sip_auth_realms) { + ao2_t_ref(sip_auth_realms, -1, "free auth_realms"); + sip_auth_realms = NULL; + } + + ast_mutex_unlock(&sip_auth_realm_lock); +} + +/* Find authentication for a specific realm. */ +struct sip_auth_realm *sip_auth_realm_find(struct sip_auth_realm_head *auth_realms, const char *realm) +{ + struct sip_auth_realm *auth_realm; + + if (auth_realms) { + AST_LIST_TRAVERSE(auth_realms, auth_realm, next) { + if (!strcasecmp(auth_realm->realm, realm)) { + break; + } + } + } + + return NULL; +} diff -durN asterisk-22.3.0.orig/channels/sip/callback.c asterisk-22.3.0/channels/sip/callback.c --- asterisk-22.3.0.orig/channels/sip/callback.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/callback.c 2025-04-17 11:11:22.787703915 +1200 @@ -0,0 +1,465 @@ +/* + * 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/request.h" +#include "include/response.h" +#include "include/stimer.h" +#include "include/proxy.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/handlers.h" +#include "include/callback.h" + +static int sip_callback_event(const char *context, const char *exten, struct ast_state_cb_info *info, void *data); + +void sip_callback_destroy(struct sip_peer *peer) +{ + ast_extension_state_del(peer->callback->state_id, NULL); + ao2_t_cleanup(peer, "drop peer"); + + ast_free(peer->callback->exten); + ast_free(peer->callback); +} + +/* Handle remotecc callback requests */ +int sip_remotecc_callback(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data) +{ + struct sip_dialog *target_dialog, *refer_dialog; + struct ast_channel *channel = NULL; + char *exten = NULL; + int exten_state, presence_state; + struct ast_str *content; + int is_79xx; + + content = ast_str_alloca(4096); + is_79xx = strstr(sip_request_get_header(request, "User-Agent"), "CP79") != NULL; + + sip_send_response(dialog, "202 Accepted", request); + + if (!ast_strlen_zero(remotecc_data->dialog.call_id)) { + RAII_VAR(char *, subtype, NULL, ast_free_ptr); + RAII_VAR(char *, message, NULL, ast_free_ptr); + int res = -1; + + /* 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); + goto cleanup; + } + + ao2_lock(target_dialog); + + if (!(channel = target_dialog->channel)) { + ast_debug(1, "No owner channel\n"); + + ao2_unlock(target_dialog); + ao2_t_cleanup(target_dialog, "drop dialog"); + + goto cleanup; + } + + ast_channel_ref(channel); + ao2_unlock(target_dialog); + ao2_t_cleanup(target_dialog, "drop dialog"); + + 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->exten)); + + ast_channel_hangupcause_set(channel, AST_CAUSE_FAILURE); + ast_channel_unlock(channel); + + ast_softhangup(channel, AST_SOFTHANGUP_EXPLICIT); + ast_channel_unref(channel); + + if (peer->callback) { + sip_callback_destroy(peer); + peer->callback = NULL; + } + + if (ast_strlen_zero(exten)) { + goto cleanup; + } + + if (!(peer->callback = ast_calloc(1, sizeof(*peer->callback)))) { + goto cleanup; + } + + if (!(peer->callback->exten = ast_strdup(exten))) { + ast_free(peer->callback); + peer->callback = NULL; + + goto cleanup; + } + + ao2_t_bump(peer, "bump peer"); + + if (!(peer->callback->state_id = ast_extension_state_add(peer->context, peer->callback->exten, sip_callback_event, peer))) { + ao2_t_cleanup(peer, "drop peer"); + ast_free(peer->callback->exten); + ast_free(peer->callback); + + goto cleanup; + } + + exten_state = ast_extension_state(NULL, peer->context, peer->callback->exten); + presence_state = ast_hint_presence_state(NULL, peer->context, peer->callback->exten, &subtype, &message); + + if (exten_state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY) || presence_state == AST_PRESENCE_DND) { + peer->callback->busy = TRUE; + } + + res = 0; + + cleanup: + if (res) { + if (!(refer_dialog = sip_dialog_alloc(NULL, &dialog->socket, FALSE, SIP_METHOD_REFER, NULL, 0))) { + return 0; + } + + sip_dialog_copy(refer_dialog, dialog); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "CallBack\n"); + ast_str_append(&content, 0, "Unable to activate callback on %s\n", S_OR(exten, "(unknown number)")); + ast_str_append(&content, 0, "Please select\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Exit\n"); + ast_str_append(&content, 0, "%d\n", is_79xx ? 3 : 1); + ast_str_append(&content, 0, "SoftKey:Exit\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + sip_send_refer_with_content(refer_dialog, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(refer_dialog, "drop dialog"); + + return 0; + } + } else if (!ast_strlen_zero(remotecc_data->user_call_data) && peer->callback) { + if (!(refer_dialog = sip_dialog_alloc(NULL, &dialog->socket, FALSE, SIP_METHOD_REFER, NULL, 0))) { + return 0; + } + + sip_dialog_copy(refer_dialog, dialog); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + sip_send_refer_with_content(refer_dialog, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(refer_dialog, "drop dialog"); + + if (!strcmp(remotecc_data->user_call_data, "Dial")) { + ast_str_reset(content); + + if (!(refer_dialog = sip_dialog_alloc(NULL, &dialog->socket, FALSE, SIP_METHOD_REFER, NULL, 0))) { + return 0; + } + + sip_dialog_copy(refer_dialog, dialog); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", peer->callback->exten); + ast_str_append(&content, 0, "%d\n", peer->cisco_line_index); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + sip_send_refer_with_content(refer_dialog, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_t_cleanup(refer_dialog, "drop dialog"); + + sip_callback_destroy(peer); + peer->callback = NULL; + } else if (!strcmp(remotecc_data->user_call_data, "Cancel")) { + sip_callback_destroy(peer); + peer->callback = NULL; + } + + return 0; + } + + if (!(refer_dialog = sip_dialog_alloc(NULL, &dialog->socket, FALSE, SIP_METHOD_REFER, NULL, 0))) { + return 0; + } + + sip_dialog_copy(refer_dialog, dialog); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%d\n", SIP_REMOTECC_CALLBACK); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "CallBack\n"); + + if (peer->callback) { + ast_str_append(&content, 0, + "CallBack is activated on %s.\n\nPress Cancel to deactivate.\nPress Exit to quit this screen.", + peer->callback->exten); + } else { + ast_str_append(&content, 0, "CallBack is not activated."); + } + + ast_str_append(&content, 0, "Please select\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Exit\n"); + ast_str_append(&content, 0, "%d\n", is_79xx ? 3 : 1); + ast_str_append(&content, 0, "SoftKey:Exit\n"); + ast_str_append(&content, 0, "\n"); + + if (peer->callback) { + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "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"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + sip_send_refer_with_content(refer_dialog, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(refer_dialog, "drop dialog"); + + return 0; +} + +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 tv; + struct ast_tm tm; + int is_79xx; + char date[32]; + + peer = (struct sip_peer *) data; + + 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; + } + + is_79xx = strstr(peer->useragent, "CP79") != NULL; + content = ast_str_alloca(4096); + + tv = ast_tvnow(); + ast_strftime(date, sizeof(date), "%X %x", ast_localtime(&tv, &tm, NULL)); + + if (!((dialog = sip_dialog_alloc(NULL, NULL, FALSE, SIP_METHOD_REFER, NULL, 0)))) { + return 0; + } + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + return 0; + } + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + sip_send_refer_with_content(dialog, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(dialog, "drop dialog"); + + ast_str_reset(content); + + if (!((dialog = sip_dialog_alloc(NULL, NULL, FALSE, SIP_METHOD_REFER, NULL, 0)))) { + return 0; + } + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + return 0; + } + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%d\n", SIP_REMOTECC_CALLBACK); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "CallBack\n"); + ast_str_append(&content, 0, + "%s is now available at %s.\n\nPress Dial to call.\nPress Cancel to deactivate.\nPress Exit to quit this screen.\n", + peer->callback->exten, date); + ast_str_append(&content, 0, "Please select\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Exit\n"); + ast_str_append(&content, 0, "%d\n", is_79xx ? 3 : 1); + ast_str_append(&content, 0, "SoftKey:Exit\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "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"); + ast_str_append(&content, 0, "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"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + sip_send_refer_with_content(dialog, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(dialog, "drop dialog"); + + return 0; +} diff -durN asterisk-22.3.0.orig/channels/sip/chan_tech.c asterisk-22.3.0/channels/sip/chan_tech.c --- asterisk-22.3.0.orig/channels/sip/chan_tech.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/chan_tech.c 2025-04-17 11:11:22.788703889 +1200 @@ -0,0 +1,1999 @@ +/* + * 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/aoc.h" +#include "asterisk/udptl.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/chan_tech.h" +#include "include/rtp_glue.h" +#include "include/handlers.h" +#include "include/dialplan_funcs.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_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_tech = { + .type = "SIP", + .description = "Session Initiation Protocol (SIP)", + .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER, + .requester = sip_requester, /* called with chan unlocked */ + .call = sip_call, /* called with chan locked */ + .hangup = sip_hangup, /* called with chan locked */ + .answer = sip_answer, /* called with chan locked */ + .read = sip_read, /* called with chan locked */ + .write = sip_write, /* called with chan locked */ + .write_video = sip_write, /* called with chan locked */ + .write_text = sip_write, + .indicate = sip_indicate, /* called with chan locked */ + .transfer = sip_transfer, /* called with chan locked */ + .fixup = sip_fixup, /* called with chan locked */ + .send_digit_begin = sip_send_digit_begin, /* called with chan unlocked */ + .send_digit_end = sip_send_digit_end, + .setoption = sip_setoption, + .queryoption = sip_queryoption, + .devicestate = sip_devicestate, /* called with chan unlocked (not chan-specific) */ + .presencestate = sip_presencestate, /* called with chan unlocked (not chan-specific) */ + .get_pvt_uniqueid = sip_get_pvt_uniqueid, + .early_bridge = ast_rtp_instance_early_bridge, + .send_text = sip_send_text, /* called with chan locked */ + .send_html = sip_send_html, + .func_channel_read = sip_func_channel_read, +}; + +struct ast_sip_api_tech chan_sip_api_tech = { + .version = AST_SIP_API_VERSION, + .name = "chan_sip", + .sipinfo_send = sip_sipinfo_send, +}; + +const struct ast_msg_tech sip_msg_tech = { + .name = "sip", + .msg_send = sip_msg_send, +}; + +/* PBX interface function -build SIP dialog structure. SIP calls initiated by the PBX arrive here. + * SIP dial string syntax: + * SIP/devicename + * or SIP/username@domain (SIP uri) + * or SIP/username[:password[:md5secret[:authname[:transport]]]]@host[:port] + * or SIP/devicename/extension + * or SIP/devicename/extension/proxy + * or SIP/username@domain//proxy */ +struct ast_channel *sip_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; + struct ast_channel *channel; + char *peer_name, *exten, *username, *hostname, *secret, *md5secret, *auth_name, *protocol, *proxy_address; + struct ast_str *format_names; + enum ast_transport transport; + ast_callid callid; + + 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; + } + + callid = ast_read_threadstorage_callid(); + + if (!(dialog = sip_dialog_alloc(NULL, NULL, FALSE, SIP_METHOD_INVITE, NULL, callid))) { + ast_log(LOG_ERROR, "Unable to build sip dialog data for '%s' (out of memory or socket error)\n", destination); + *cause = AST_CAUSE_SWITCH_CONGESTION; + + return NULL; + } + + dialog->outgoing_call = TRUE; + ast_string_field_build(dialog, dial_string, "%s/%s", type, destination); + + peer_name = NULL; + secret = NULL; + md5secret = NULL; + auth_name = NULL; + exten = NULL; + proxy_address = NULL; + protocol = NULL; + transport = AST_TRANSPORT_UDP; + + /* Save the destination, the SIP dial string */ + username = ast_strdupa(destination); + + if ((hostname = strchr(username, '@'))) { + *hostname++ = '\0'; + secret = strchr(username, ':'); + + if (secret) { + *secret++ = '\0'; + md5secret = strchr(secret, ':'); + } + + if (md5secret) { + *md5secret++ = '\0'; + auth_name = strchr(md5secret, ':'); + } + + if (auth_name) { + *auth_name++ = '\0'; + protocol = strchr(auth_name, ':'); + } + + if (protocol) { + *protocol++ = '\0'; + + if (!(transport = sip_str2transport(protocol))) { + ast_log(LOG_WARNING, + "'%s' is not a valid transport option to Dial() for SIP calls, using udp by default\n", protocol); + transport = AST_TRANSPORT_UDP; + } + } + } else { + peer_name = username; + exten = strchr(peer_name, '/'); + + if (exten) { + *exten++ = '\0'; + proxy_address = strchr(exten, '/'); + } + + if (proxy_address) { + *proxy_address++ = '\0'; + } + } + + if (!ast_strlen_zero(proxy_address)) { + ao2_cleanup(dialog->proxy); + dialog->proxy = sip_proxy_build(proxy_address, 0, NULL); + + if (!dialog->proxy) { + ast_log(LOG_WARNING, "Unable to parse outbound proxy %s. We will not use this remote IP address\n", proxy_address); + } + } + + if (!ast_strlen_zero(hostname)) { + sip_socket_set_transport(&dialog->socket, transport); + } + + if (sip_dialog_build(dialog, S_OR(peer_name, hostname), NULL, TRUE)) { + ast_debug(3, "Cant create SIP call, target device not registered\n"); + + sip_dialog_unlink(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + *cause = AST_CAUSE_UNREGISTERED; + + return NULL; + } + + if (ast_strlen_zero(dialog->peer_name) && exten) { + ast_string_field_set(dialog, peer_name, exten); + } + + /* Recalculate our side, and recalculate Call ID */ + sip_get_our_address(dialog->socket.transport, &dialog->address, &dialog->our_address); + /* 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 (dialog->peer) { + if (!ast_strlen_zero(dialog->peer->full_contact) && !dialog->nat_detected && + ((ast_test_flag(&dialog->flags[2], SIP_NAT_AUTO_RPORT) && !ast_test_flag(&dialog->flags[0], SIP_NAT_FORCE_RPORT)) || + (ast_test_flag(&dialog->flags[2], SIP_NAT_AUTO_COMEDIA) && !ast_test_flag(&dialog->flags[1], SIP_SYMMETRIC_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_address(dialog->peer->full_contact, &address); + sip_dialog_check_nat(dialog, &address); + } + + sip_dialog_set_nat(dialog, dialog->peer); + } + + sip_dialog_set_rtp_nat(dialog); + + /* We have an extension to call, don't use the full contact here. This to enable dialing registered peers with extension dialling, + * like SIP/peername/extension SIP/peername will still use the full contact */ + if (!ast_strlen_zero(exten)) { + ast_string_field_set(dialog, username, exten); + ast_string_field_set(dialog, full_contact, NULL); + } + + if (!ast_strlen_zero(secret)) { + ast_string_field_set(dialog, peer_secret, secret); + } + + if (!ast_strlen_zero(md5secret)) { + ast_string_field_set(dialog, peer_md5secret, md5secret); + } + + if (!ast_strlen_zero(auth_name)) { + ast_string_field_set(dialog, auth_name, auth_name); + } + + 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); + channel = sip_dialog_alloc_channel(dialog, AST_STATE_DOWN, dialog->peer_name, assigned_ids, requestor_channel, callid); + ao2_unlock(dialog); + + if (!channel) { + sip_dialog_unlink(dialog); + } else { + ast_channel_unlock(channel); + } + + ao2_t_cleanup(dialog, "drop dialog"); + + ast_update_use_count(); + sip_monitor_restart(); + + return channel; +} + +/* Initiate SIP call from PBX */ +int sip_call(struct ast_channel *channel, const char *destination, int timeout) +{ + int res; + struct sip_dialog *dialog; + struct ast_var_t *var; + + /* channel is locked, so the reference cannot go away */ + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked call channel %s without tech dialog; ignoring\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 %s, neither down nor reserved\n", ast_channel_name(channel)); + return -1; + } + + if (dialog->do_not_disturb && ast_test_flag(&dialog->flags[2], SIP_DND_BUSY)) { + ast_queue_control(dialog->channel, AST_CONTROL_BUSY); + return 0; + } else if (!ast_strlen_zero(dialog->call_forward)) { + ast_channel_call_forward_set(channel, dialog->call_forward); + ast_queue_control(dialog->channel, AST_CONTROL_BUSY); + + return 0; + } + + AST_LIST_TRAVERSE(ast_channel_varshead(channel), var, entries) { + if (!strcmp(ast_var_name(var), "SIP_URI_OPTIONS")) { + ast_string_field_set(dialog, uri_options, ast_var_value(var)); + } else if (!dialog->add_headers && !strncmp(ast_var_name(var), "SIPADDHEADER", 12)) { + /* Check whether there is a variable with a name starting with SIPADDHEADER */ + dialog->add_headers = TRUE; + } else if (!strcmp(ast_var_name(var), "SIPFROMDOMAIN")) { + ast_string_field_set(dialog, from_domain, ast_var_value(var)); + } else if (!strcmp(ast_var_name(var), "SIPTRANSFER_REPLACES")) { + /* We're replacing a call. */ + ast_string_field_set(dialog, replaces, ast_var_value(var)); + } else if (!strcmp(ast_var_name(var), "SIP_MAX_FORWARDS")) { + if (sscanf(ast_var_value(var), "%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\n"); + ast_channel_hangupcause_set(channel, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); + + return -1; + } + + if (ast_test_flag(&dialog->flags[0], SIP_USE_SRTP)) { + if (ast_test_flag(&dialog->flags[0], SIP_REINVITE)) { + ast_debug(1, "Direct media not possible when using SRTP, ignoring canreinvite setting\n"); + ast_clear_flag(&dialog->flags[0], SIP_REINVITE); + } + + 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; + } + } + + res = 0; + ast_set_flag(&dialog->flags[0], SIP_OUTGOING); + + /* T.38 re-INVITE FAX detection should never be done for outgoing calls, so ensure it is disabled. */ + ast_clear_flag(&dialog->flags[1], SIP_FAX_DETECT_T38); + + ast_debug(1, "Outgoing call for %s\n", dialog->username); + res = sip_dialog_update_call_counter(dialog, SIP_INC_CALL_RINGING); + + if (res == -1) { + ast_channel_hangupcause_set(channel, AST_CAUSE_USER_BUSY); + return res; + } + + dialog->caller_presentation = ast_party_id_presentation(&ast_channel_caller(channel)->id); + + ast_rtp_instance_available_formats(dialog->audio_rtp, dialog->format_cap, dialog->outgoing_format_cap, dialog->joint_format_cap); + dialog->joint_non_format_cap = dialog->non_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->username); + res = -1; + + /* 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. */ + } else 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->username); + res = -1; + } else { + if (!ast_strlen_zero(dialog->caller_number) || (dialog->caller_presentation & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED || + !ast_strlen_zero(dialog->caller_name) || (dialog->caller_presentation & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + sip_dialog_queue_connected_line_update(dialog, AST_CONNECTED_LINE_UPDATE_SOURCE_UNKNOWN); + } + + if (sip_send_invite(dialog, TRUE, SIP_INIT_REQUEST, NULL)) { + ao2_unlock(dialog); + return -1; + } + + dialog->invite_state = SIP_INVITE_CALLING; + + /* Initialize auto-congest time */ + AST_SCHED_REPLACE_UNREF(dialog->invite_sched_id, sip_sched_context, dialog->timer_b, sip_dialog_auto_congest, dialog, + ao2_t_cleanup(_data, "drop dialog"), + ao2_t_cleanup(dialog, "drop dialog"), + ao2_t_bump(dialog, "bump dialog")); + + ao2_unlock(dialog); + } + + return res; +} + +/* Hangup SIP call */ +int sip_hangup(struct ast_channel *channel) +{ + struct sip_dialog *dialog; + int need_cancel, need_destroy; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked hangup channel %s without tech dialog; ignoring\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"); + sip_history_append(dialog, "Cancel", "Call answered elsewhere"); + + dialog->answered_elsewhere = TRUE; + } + + /* Store hangupcause locally in PVT so we still have it before disconnect */ + if (dialog->channel) { + dialog->hangupcause = ast_channel_hangupcause(dialog->channel); + } + + if (ast_test_flag(&dialog->flags[0], SIP_DEFER_BYE_ON_TRANSFER)) { + if (ast_test_flag(&dialog->flags[0], SIP_CALL_COUNTER_INUSE) || ast_test_flag(&dialog->flags[1], SIP_ONHOLD)) { + ast_debug(1, "Decrement %s call limit counter on hangup\n", dialog->username); + + sip_dialog_update_call_counter(dialog, SIP_DEC_CALL_LIMIT); + } + + ast_debug(4, "Not hanging up '%s' right now due to transfer, rescheduling hangup\n", dialog->call_id); + + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + ast_clear_flag(&dialog->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Really hang up next time */ + + 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_t_cleanup(ast_channel_tech_pvt(channel), "drop dialog"); + ast_channel_tech_pvt_set(channel, NULL); + } + + sip_module_unref(); + return 0; + } + + ast_debug(1, "Hanging up '%s' (channel '%s')\n", dialog->call_id, ast_channel_name(channel)); + + ao2_lock(dialog); + + if (ast_test_flag(&dialog->flags[0], SIP_CALL_COUNTER_INUSE) || ast_test_flag(&dialog->flags[1], SIP_ONHOLD)) { + ast_debug(1, "Decrement %s call limit counter on hangup\n", dialog->username); + + sip_dialog_update_call_counter(dialog, SIP_DEC_CALL_LIMIT); + + if (!ast_test_flag(&dialog->flags[0], SIP_CALL_COUNTER_INUSE) && dialog->peer) { + ao2_lock(dialog->peer); + sip_selected_destroy_all(dialog->peer); + ao2_unlock(dialog->peer); + } + } + + /* Determine how to disconnect */ + if (dialog->channel != channel) { + ast_log(LOG_WARNING, "We aren't the owner, can't hangup call\n"); + ao2_unlock(dialog); + + return 0; + } + + /* 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 (dialog->invite_state < SIP_INVITE_COMPLETED && ast_channel_state(dialog->channel) != AST_STATE_UP) { + need_cancel = TRUE; + ast_debug(4, "Hanging up channel in state %s (not up)\n", ast_state2str(ast_channel_state(channel))); + } else { + need_cancel = FALSE; + } + + sip_dialog_stop_rtp(dialog); /* Immediately stop RTP, VRTP and UDPTL as applicable */ + sip_history_append(dialog, need_cancel ? "Cancel" : "Hangup", "Cause %s", ast_cause2str(dialog->hangupcause)); + + /* Disconnect */ + 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...) */ + need_destroy = FALSE; + + if (dialog->already_gone) { + need_destroy = TRUE; /* Set destroy flag at end of this function */ + } else if (dialog->invite_state != SIP_INVITE_CALLING) { + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + } + + /* Start the process if it's not already started */ + if (!dialog->already_gone && dialog->initial_request.uri) { + if (need_cancel) { /* Outgoing call, not up */ + if (ast_test_flag(&dialog->flags[0], SIP_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 */ + ast_set_flag(&dialog->flags[0], SIP_PENDING_BYE); + + /* Do we need a timer here if we don't hear from them at all? Yes we do or else we will get hung + * dialogs and those are no fun. */ + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + sip_history_append(dialog, "DELAY", "Not sending cancel, waiting for timeout"); + } else { + struct sip_packet *packet; + + AST_LIST_TRAVERSE(&dialog->packet_queue, packet, next) { + sip_packet_semi_ack(dialog, packet->method, packet->cseq, packet->response); + } + + /* Send a new request: CANCEL */ + sip_send_cancel(dialog); + + /* Actually don't destroy us yet, wait for the 487 on our original INVITE, but do set an autodestruct + * just in case we never get it. */ + need_destroy = FALSE; + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + } + } else { /* Incoming call, not up */ + const char *status_line; + + sip_dialog_cancel_provisional_keepalive(dialog); + + if (ast_test_flag(&dialog->flags[1], SIP_TRANSFER_RESPONSE)) { + sip_send_response_reliable(dialog, "500 Internal Server Error", &dialog->initial_request); + } else if (dialog->hangupcause && (status_line = sip_cause2hangup(dialog->hangupcause))) { + sip_send_response_reliable(dialog, status_line, &dialog->initial_request); + } else { + sip_send_response_reliable(dialog, "603 Decline", &dialog->initial_request); + } + + dialog->invite_state = SIP_INVITE_TERMINATED; + } + } else { /* Call is in UP state, send BYE */ + if (dialog->stimer) { + sip_stimer_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))) { + if (dialog->record_history) { + sip_history_append(dialog, "RTCPaudio", "Quality:%s", 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))) { + if (dialog->record_history) { + sip_history_append(dialog, "RTCPvideo", "Quality:%s", 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))) { + if (dialog->record_history) { + sip_history_append(dialog, "RTCPtext", "Quality:%s", 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_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. */ + ast_set_flag(&dialog->flags[0], SIP_PENDING_BYE); + ast_clear_flag(&dialog->flags[0], SIP_NEED_REINVITE); + + 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) { + if ((dialog->reinvite_sched_id = ast_sched_add(sip_sched_context, 32 * dialog->timer_t1, + sip_dialog_reinvite_timeout, ao2_t_bump(dialog, "bump dialog"))) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(dialog, "drop dialog"); + } + } + } + } + } + + if (need_destroy) { + sip_dialog_set_need_destroy(dialog, "hangup"); + } + + ao2_unlock(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + return 0; +} + +/* Answer SIP call, send 200 OK on Invite */ +int sip_answer(struct ast_channel *channel) +{ + struct sip_dialog *dialog; + int res, old_sdp; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to answer channel %s without tech dialog; ignoring\n", ast_channel_name(channel)); + return 0; + } + + ao2_lock(dialog); + + res = 0; + + if (ast_channel_state(channel) != AST_STATE_UP) { + sip_try_suggested_codec(dialog); + + if (ast_test_flag(&dialog->flags[0], SIP_PROGRESS_SENT)) { + old_sdp = TRUE; + } else { + old_sdp = FALSE; + } + + ast_debug(1, "Answering channel: %s\n", ast_channel_name(channel)); + + ast_setstate(channel, AST_STATE_UP); + ast_rtp_instance_update_source(dialog->audio_rtp); + + res = sip_send_response_with_sdp(dialog, "200 OK", &dialog->initial_request, SIP_SEND_CRITICAL, old_sdp, TRUE); + ast_set_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED); + + /* RFC says the session timer starts counting on 200, not on INVITE. */ + if (dialog->stimer) { + sip_stimer_restart(dialog); + } + } + + ao2_unlock(dialog); + + return res; +} + +/* Read SIP RTP from channel */ +struct ast_frame *sip_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 without tech dialog; ignoring\n", ast_channel_name(channel)); + return &ast_null_frame; + } + + ao2_lock(dialog); + + fax_detect = FALSE; + frame = sip_read_rtp(channel, dialog, &fax_detect); + dialog->last_rtp_received = time(NULL); + + /* If we detect a CNG tone and fax detection is enabled then send us off to the fax extension */ + if (fax_detect && ast_test_flag(&dialog->flags[1], 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_NOTICE, + "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_read_rtp(struct ast_channel *channel, struct sip_dialog *dialog, int *fax_detect) +{ + /* Retrieve audio/etc from channel. Assumes p->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 0: + frame = ast_rtp_instance_read(dialog->audio_rtp, 0); /* RTP Audio */ + break; + case 1: + frame = ast_rtp_instance_read(dialog->audio_rtp, 1); /* RTCP Control Channel */ + break; + case 2: + frame = ast_rtp_instance_read(dialog->video_rtp, 0); /* RTP Video */ + break; + case 3: + frame = ast_rtp_instance_read(dialog->video_rtp, 1); /* RTCP Control Channel for video */ + break; + case 4: + frame = ast_rtp_instance_read(dialog->text_rtp, 0); /* RTP Text */ + break; + case 5: + 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) && + ast_test_flag(&dialog->flags[0], SIP_DTMF) != SIP_DTMF_RFC2833) { + ast_debug(1, "Ignoring DTMF (%c) RTP frame because dtmfmode 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, "Bogus 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)); + *fax_detect = TRUE; + + /* If we only needed this DSP for fax detection purposes we can just drop it now */ + if (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_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_write(struct ast_channel *channel, struct ast_frame *frame) +{ + struct sip_dialog *dialog; + int res; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to write to channel %s without tech dialog; ignoring\n", ast_channel_name(channel)); + return 0; + } + + res = 0; + + 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 frame type %s, while native formats is %s read/write = %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))); + + return 0; + } + + if (dialog) { + ao2_lock(dialog); + + if (dialog->t38_state == SIP_T38_ENABLED) { + /* drop frame, can't sent VOICE frames while in T.38 mode */ + ao2_unlock(dialog); + break; + } else if (dialog->audio_rtp) { + /* If channel is not up, activate early media session */ + if ((ast_channel_state(channel) != AST_STATE_UP) && + !ast_test_flag(&dialog->flags[0], SIP_PROGRESS_SENT) && + !ast_test_flag(&dialog->flags[0], SIP_OUTGOING)) { + ast_rtp_instance_update_source(dialog->audio_rtp); + + if (!sip_config.premature_media_filter) { + dialog->invite_state = SIP_INVITE_EARLY_MEDIA; + + sip_send_response_provisional(dialog, + "183 Session Progress", &dialog->initial_request, TRUE); + ast_set_flag(&dialog->flags[0], SIP_PROGRESS_SENT); + } + } + + if (dialog->invite_state > SIP_INVITE_EARLY_MEDIA || (dialog->invite_state == SIP_INVITE_EARLY_MEDIA && + ast_test_flag(&dialog->flags[0], SIP_PROGRESS_SENT))) { + dialog->last_rtp_sent = time(NULL); + res = ast_rtp_instance_write(dialog->audio_rtp, frame); + } + } + + ao2_unlock(dialog); + } + + break; + case AST_FRAME_VIDEO: + if (dialog) { + ao2_lock(dialog); + + if (dialog->video_rtp) { + /* Activate video early media */ + if ((ast_channel_state(channel) != AST_STATE_UP) && + !ast_test_flag(&dialog->flags[0], SIP_PROGRESS_SENT) && + !ast_test_flag(&dialog->flags[0], SIP_OUTGOING)) { + dialog->invite_state = SIP_INVITE_EARLY_MEDIA; + + sip_send_response_provisional(dialog, "183 Session Progress", &dialog->initial_request, TRUE); + ast_set_flag(&dialog->flags[0], SIP_PROGRESS_SENT); + } + + if (dialog->invite_state > SIP_INVITE_EARLY_MEDIA || (dialog->invite_state == SIP_INVITE_EARLY_MEDIA && + ast_test_flag(&dialog->flags[0], SIP_PROGRESS_SENT))) { + dialog->last_rtp_sent = time(NULL); + res = ast_rtp_instance_write(dialog->video_rtp, frame); + } + } + + ao2_unlock(dialog); + } + + break; + case AST_FRAME_TEXT: + if (dialog) { + ao2_lock(dialog); + + if (dialog->red) { + ast_rtp_red_buffer(dialog->text_rtp, frame); + } else { + if (dialog->text_rtp) { + /* Activate text early media */ + if ((ast_channel_state(channel) != AST_STATE_UP) && + !ast_test_flag(&dialog->flags[0], SIP_PROGRESS_SENT) && + !ast_test_flag(&dialog->flags[0], SIP_OUTGOING)) { + dialog->invite_state = SIP_INVITE_EARLY_MEDIA; + + sip_send_response_provisional(dialog, "183 Session Progress", + &dialog->initial_request, TRUE); + ast_set_flag(&dialog->flags[0], SIP_PROGRESS_SENT); + } + + if (dialog->invite_state > SIP_INVITE_EARLY_MEDIA || + (dialog->invite_state == SIP_INVITE_EARLY_MEDIA && + ast_test_flag(&dialog->flags[0], SIP_PROGRESS_SENT))) { + dialog->last_rtp_sent = time(NULL); + res = ast_rtp_instance_write(dialog->text_rtp, frame); + } + } + } + + ao2_unlock(dialog); + } + + break; + case AST_FRAME_IMAGE: + return 0; + break; + case AST_FRAME_MODEM: + if (dialog) { + ao2_lock(dialog); + + /* 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->t38_state == SIP_T38_ENABLED) { + res = ast_udptl_write(dialog->udptl, frame); + } + + ao2_unlock(dialog); + } + break; + default: + ast_log(LOG_WARNING, "Unable to send %u type frames with SIP write\n", frame->frametype); + return 0; + } + + return res; +} + +/* Fix up a channel: If a channel is consumed, this is called. Basically update any ->owner links */ +int sip_fixup(struct ast_channel *old_channel, struct ast_channel *new_channel) +{ + struct sip_dialog *dialog; + int res; + + 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 || !(dialog = ast_channel_tech_pvt(new_channel))) { + if (!new_channel) { + ast_log(LOG_WARNING, "No new channel! Fixup of %s failed\n", ast_channel_name(old_channel)); + } else { + ast_log(LOG_WARNING, "No SIP tech_pvt! Fixup of %s failed\n", ast_channel_name(old_channel)); + } + + return -1; + } + + ao2_lock(dialog); + + sip_history_append(dialog, "Masq", "Old channel: %s\n", ast_channel_name(old_channel)); + sip_history_append(dialog, "Masq (cont)", "New channel: %s\n", ast_channel_name(new_channel)); + + res = -1; + + if (dialog->channel != old_channel) { + ast_log(LOG_WARNING, "Old channel wasn't %p but was %p\n", old_channel, dialog->channel); + } else { + 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* a 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); + res = 0; + } + + ast_debug(3, "New channel for '%s' is '%s' (old channel '%s')\n", + dialog->call_id, ast_channel_name(dialog->channel), ast_channel_name(old_channel)); + ao2_unlock(dialog); + + return res; +} + +int sip_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 dialog; ignoring\n", ast_channel_name(channel)); + return -1; + } + + ao2_lock(dialog); + + res = 0; + + switch (ast_test_flag(&dialog->flags[0], SIP_DTMF)) { + case SIP_DTMF_INBAND: + res = -1; /* Tell Asterisk to generate inband indications */ + break; + case SIP_DTMF_RFC2833: + if (dialog->audio_rtp) { + ast_rtp_instance_dtmf_begin(dialog->audio_rtp, digit); + } + + break; + default: + 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_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 dialog; ignoring\n", ast_channel_name(channel)); + return -1; + } + + ao2_lock(dialog); + + res = 0; + + switch (ast_test_flag(&dialog->flags[0], SIP_DTMF)) { + case SIP_DTMF_INBAND: + res = -1; /* Tell Asterisk to stop inband indications */ + break; + case SIP_DTMF_RFC2833: + if (dialog->audio_rtp) { + ast_rtp_instance_dtmf_end_with_duration(dialog->audio_rtp, digit, duration); + } + + break; + } + + ao2_unlock(dialog); + + return res; +} + +/* Set an option on a SIP dialog */ +int sip_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 dialog; ignoring\n", ast_channel_name(channel)); + return -1; + } + + ao2_lock(dialog); + + res = -1; + + 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); + } + + break; + case AST_OPTION_FORMAT_WRITE: + if (dialog->audio_rtp) { + res = ast_rtp_instance_set_write_format(dialog->audio_rtp, *(struct ast_format **) data); + } + break; + case AST_OPTION_DIGIT_DETECT: + if (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_INBAND || + ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + char *detect; + + detect = (char *) data; + + ast_debug(1, "%sabling digit detection on %s\n", *detect ? "En" : "Dis", ast_channel_name(channel)); + sip_dialog_set_dsp_detect(dialog, *detect ? TRUE : FALSE); + + res = 0; + } + break; + case AST_OPTION_SECURE_SIGNALING: + dialog->secure_signaling = *(unsigned int *) data; + res = 0; + + break; + case AST_OPTION_SECURE_MEDIA: + ast_set2_flag(&dialog->flags[0], *(unsigned int *) data, SIP_USE_SRTP); + res = 0; + + break; + default: + break; + } + + ao2_unlock(dialog); + + return res; +} + +/* Query an option on a SIP dialog */ +int sip_queryoption(struct ast_channel *channel, int option, void *data, int *data_len) +{ + struct sip_dialog *dialog; + enum ast_t38_state state; + int res; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to query option on channel %s with no dialog; ignoring\n", ast_channel_name(channel)); + return -1; + } + + ao2_lock(dialog); + + res = -1; + + switch (option) { + case AST_OPTION_DEVICE_NAME: + if (dialog && dialog->outgoing_call) { + ast_copy_string((char *) data, dialog->dial_string, *data_len); + res = 0; + } + + /* We purposely break with a return of -1 in the implied else case here */ + break; + case AST_OPTION_DIGIT_DETECT: + *((char *) data) = dialog->dsp ? TRUE : FALSE; + + ast_debug(1, "Reporting digit detection %sabled on %s\n", dialog->dsp ? "en" : "dis", ast_channel_name(channel)); + + break; + case AST_OPTION_SECURE_SIGNALING: + *((unsigned int *) data) = dialog->secure_signaling; + res = 0; + + break; + case AST_OPTION_SECURE_MEDIA: + *((unsigned int *) data) = !!ast_test_flag(&dialog->flags[0], SIP_USE_SRTP); + res = 0; + + break; + case AST_OPTION_T38_STATE: + /* Make sure we got an ast_t38_state enum passed in */ + if (*data_len != sizeof(enum ast_t38_state)) { + ast_log(LOG_ERROR, "Invalid data_len for AST_OPTION_T38_STATE option. Expected %d, got %d\n", + (int) sizeof(enum ast_t38_state), *data_len); + break; + } + + /* 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 (ast_test_flag(&dialog->flags[1], SIP_T38_SUPPORT)) { + switch (dialog->t38_state) { + case SIP_T38_LOCAL_REINVITE: + case SIP_T38_PEER_REINVITE: + state = T38_STATE_NEGOTIATING; + break; + case SIP_T38_ENABLED: + state = T38_STATE_NEGOTIATED; + break; + case SIP_T38_REJECTED: + state = T38_STATE_REJECTED; + break; + default: + state = T38_STATE_UNKNOWN; + break; + } + } else { + state = T38_STATE_UNAVAILABLE; + } + + *((enum ast_t38_state *) data) = state; + res = 0; + + break; + default: + break; + } + + ao2_unlock(dialog); + + return res; +} + +/* Transfer SIP call */ +int sip_transfer(struct ast_channel *channel, const char *destination) +{ + struct sip_dialog *dialog; + int res; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to transfer channel %s with no dialog; ignoring\n", ast_channel_name(channel)); + return -1; + } + + if (!destination) { /* functions below do not take a NULL */ + destination = ""; + } + + ao2_lock(dialog); + + res = -1; + + /* Transfer call before connect with a 302 redirect. Called by the transfer() dialplan application through the sip_transfer() + * pbx interface function if the call is in ringing state */ + if (ast_channel_state(channel) == AST_STATE_RING) { + char *user, *domain; + + user = ast_strdupa(destination); + user = strsep(&domain, "@"); + + if (ast_strlen_zero(user)) { + ast_log(LOG_ERROR, "Missing mandatory argument: extension\n"); + ao2_unlock(dialog); + + return 0; + } + + /* we'll issue the redirect message here */ + if (ast_strlen_zero(domain)) { + char *to = ast_strdupa(sip_request_get_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 0; + } + + if (!(domain = strchr(to, '@'))) { + ast_log(LOG_ERROR, "Unable to find the domain\n"); + ao2_unlock(dialog); + + return 0; + } + + *domain++ = '\0'; + domain = strsep(&domain, ":;"); + } + + ast_string_field_build(dialog, our_contact, "", user, domain); + sip_send_response_reliable(dialog, "302 Moved Temporarily", &dialog->initial_request); + + sip_dialog_sched_destroy(dialog, SIP_TIMEOUT); /* 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 ? */ + if (ast_test_flag(&dialog->flags[0], SIP_OUTGOING)) { + uri = ast_strdupa(sip_request_get_header(&dialog->initial_request, "To")); + } else { + uri = ast_strdupa(sip_request_get_header(&dialog->initial_request, "From")); + } + + uri = sip_get_in_brackets(uri); + ast_string_field_set(dialog, from, uri); + + if (!strncasecmp(uri, "sips:", 4)) { + use_tls = TRUE; + } else { + if (strncasecmp(uri, "sip:", 5)) { + ast_log(LOG_NOTICE, "From address missing 'sip(s):', assuming sip:\n"); + } + + use_tls = FALSE; + } + + ast_string_field_build(dialog, refer_to, "", use_tls ? "s" : "", destination); + ast_string_field_set(dialog, referred_by, dialog->our_contact); + + res = sip_send_refer(dialog); + } + + ao2_unlock(dialog); + + return res; +} + +/* 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_indicate(struct ast_channel *channel, int condition, const void *data, size_t data_len) +{ + struct sip_dialog *dialog; + int res; + struct ast_aoc_decoded *decoded_aoc; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_log(LOG_WARNING, "Asked to indicate condition on channel %s with no dialog; ignoring\n", ast_channel_name(channel)); + return -1; + } + + ao2_lock(dialog); + + res = 0; + + switch (condition) { + case AST_CONTROL_RINGING: + if (ast_channel_state(channel) == AST_STATE_RING) { + dialog->invite_state = SIP_INVITE_EARLY_MEDIA; + + if (!ast_test_flag(&dialog->flags[0], SIP_PROGRESS_SENT) || + ast_test_flag(&dialog->flags[0], SIP_PROGRESS_INBAND) == SIP_PROGRESS_INBAND_NEVER) { + /* Send 180 ringing if out-of-band seems reasonable */ + sip_send_response_provisional(dialog, "180 Ringing", &dialog->initial_request, FALSE); + ast_set_flag(&dialog->flags[0], SIP_RINGING); + + if (ast_test_flag(&dialog->flags[0], SIP_PROGRESS_INBAND) != SIP_PROGRESS_INBAND_YES) { + break; + } + } + } + + res = -1; + break; + case AST_CONTROL_BUSY: + if (ast_channel_state(channel) != AST_STATE_UP) { + sip_send_response_reliable(dialog, "486 Busy Here", &dialog->initial_request); + dialog->invite_state = SIP_INVITE_COMPLETED; + + sip_dialog_set_already_gone(dialog); + ast_softhangup_nolock(channel, AST_SOFTHANGUP_DEV); + + break; + } + + res = -1; + break; + case AST_CONTROL_CONGESTION: + if (ast_channel_state(channel) != AST_STATE_UP) { + sip_send_response_reliable(dialog, "503 Service Unavailable", &dialog->initial_request); + dialog->invite_state = SIP_INVITE_COMPLETED; + + sip_dialog_set_already_gone(dialog); + ast_softhangup_nolock(channel, AST_SOFTHANGUP_DEV); + + break; + } + + res = -1; + break; + case AST_CONTROL_INCOMPLETE: + if (ast_channel_state(channel) != AST_STATE_UP) { + switch (ast_test_flag(&dialog->flags[1], SIP_ALLOW_OVERLAP)) { + case SIP_ALLOW_OVERLAP_YES: + sip_send_response_reliable(dialog, "484 Address Incomplete", &dialog->initial_request); + dialog->invite_state = SIP_INVITE_COMPLETED; + + 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: + /* it actually means no support for overlap */ + sip_send_response_reliable(dialog, "404 Not Found", &dialog->initial_request); + dialog->invite_state = SIP_INVITE_COMPLETED; + + sip_dialog_set_already_gone(dialog); + ast_softhangup_nolock(channel, AST_SOFTHANGUP_DEV); + break; + } + } + + break; + case AST_CONTROL_PROCEEDING: + if (ast_channel_state(channel) != AST_STATE_UP && + !ast_test_flag(&dialog->flags[0], SIP_PROGRESS_SENT) && !ast_test_flag(&dialog->flags[0], SIP_OUTGOING)) { + sip_send_response(dialog, "100 Trying", &dialog->initial_request); + dialog->invite_state = SIP_INVITE_PROCEEDING; + + break; + } + + res = -1; + break; + case AST_CONTROL_PROGRESS: + if (ast_channel_state(channel) != AST_STATE_UP && + !ast_test_flag(&dialog->flags[0], SIP_PROGRESS_SENT) && !ast_test_flag(&dialog->flags[0], SIP_OUTGOING)) { + dialog->invite_state = SIP_INVITE_EARLY_MEDIA; + + /* SIP_PROGRESS_INBAND_NEVER means sending 180 ringing in place of a 183 */ + if (ast_test_flag(&dialog->flags[0], SIP_PROGRESS_INBAND) != SIP_PROGRESS_INBAND_NEVER) { + sip_send_response_provisional(dialog, "183 Session Progress", &dialog->initial_request, TRUE); + ast_set_flag(&dialog->flags[0], SIP_PROGRESS_SENT); + } else if (ast_channel_state(channel) == AST_STATE_RING && !ast_test_flag(&dialog->flags[0], SIP_RINGING)) { + sip_send_response_provisional(dialog, "180 Ringing", &dialog->initial_request, FALSE); + ast_set_flag(&dialog->flags[0], SIP_RINGING); + } + + break; + } + + res = -1; + break; + case AST_CONTROL_HOLD: + ast_rtp_instance_update_source(dialog->audio_rtp); + ast_moh_start(channel, data, dialog->moh_interpret); + + break; + case AST_CONTROL_UNHOLD: + ast_rtp_instance_update_source(dialog->audio_rtp); + ast_moh_stop(channel); + + break; + case AST_CONTROL_VIDUPDATE: /* Request a video frame update */ + if (dialog->video_rtp && !dialog->no_video_support) { + /* FIXME: 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) { + /* FIXME 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; + + frame.frametype = AST_FRAME_CONTROL; + frame.subclass.integer = AST_CONTROL_VIDUPDATE; + + res = ast_rtp_instance_write(dialog->video_rtp, &frame); + } else { + sip_send_info_with_media_control(dialog); + } + } else { + res = -1; + } + + break; + case AST_CONTROL_T38_PARAMETERS: + res = -1; + + if (data_len != sizeof(struct ast_control_t38_parameters)) { + ast_log(LOG_ERROR, "Invalid length for AST_CONTROL_T38_PARAMETERS. Expected %d, got %d\n", + (int) sizeof(struct ast_control_t38_parameters), (int) data_len); + } else { + if (!sip_fax_alloc(dialog)) { + res = sip_fax_update(dialog, (struct ast_control_t38_parameters *) data); + } + } + + break; + case AST_CONTROL_SRCUPDATE: + ast_rtp_instance_update_source(dialog->audio_rtp); + break; + case AST_CONTROL_SRCCHANGE: + ast_rtp_instance_change_source(dialog->audio_rtp); + break; + case AST_CONTROL_CONNECTED_LINE: + sip_dialog_update_connected_line(dialog); + break; + case AST_CONTROL_REDIRECTING: + sip_dialog_update_redirecting(dialog); + break; + case AST_CONTROL_AOC: + if (!(decoded_aoc = ast_aoc_decode((struct ast_aoc_encoded *) data, data_len, channel))) { + ast_log(LOG_ERROR, "Error decoding indicated AOC data\n"); + res = -1; + + break; + } + + switch (ast_aoc_get_msg_type(decoded_aoc)) { + case AST_AOC_REQUEST: + if (ast_aoc_get_termination_request(decoded_aoc)) { + /* TODO, once there is a way to get AOC-E on hangup, attempt that here before hanging up the channel.*/ + /* The other side has already initiated the hangup. This frame just says they are waiting to get AOC-E + * before completely tearing the call down. Since SIP does not support this at the moment go ahead and + * terminate the call here to avoid an unnecessary timeout. */ + ast_debug(1, "AOC-E termination request received on %s. This is not yet supported on SIP. Continue with hangup\n", + ast_channel_name(dialog->channel)); + ast_softhangup_nolock(dialog->channel, AST_SOFTHANGUP_DEV); + } + + break; + case AST_AOC_D: + case AST_AOC_E: + case AST_AOC_S: /* S not supported yet */ + default: + break; + } + + ast_aoc_destroy_decoded(decoded_aoc); + break; + case AST_CONTROL_UPDATE_RTP_PEER: /* Absorb this since it is handled by the bridge */ + 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_MCID: + res = -1; + break; + case AST_CONTROL_PVT_CAUSE_CODE: /* these should be handled by the code in channel.c */ + case AST_CONTROL_MASQUERADE_NOTIFY: + case -1: + res = -1; + break; + default: + ast_log(LOG_WARNING, "Don't know how to indicate condition %d\n", condition); + 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 call limit: + * - not registered AST_DEVICE_UNAVAILABLE + * - registered, no call AST_DEVICE_NOT_INUSE + * - registered, active calls AST_DEVICE_INUSE + * - registered, call limit reached AST_DEVICE_BUSY + * - registered, onhold AST_DEVICE_ONHOLD + * - registered, ringing AST_DEVICE_RINGING + * + * For peers without call limit: + * - not registered AST_DEVICE_UNAVAILABLE + * - registered AST_DEVICE_NOT_INUSE + * - fixed IP (!dynamic) AST_DEVICE_NOT_INUSE + * + * Peers that does not have a known 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 (\ref app_queue.c) treats a member as "active" if devicestate is != AST_DEVICE_UNAVAILBALE && != AST_DEVICE_INVALID + * When placing a call to the queue member, queue system sets a member to busy if != AST_DEVICE_NOT_INUSE and != AST_DEVICE_UNKNOWN */ +int sip_devicestate(const char *peer_name) +{ + struct sip_peer *peer; + int res; + + ast_debug(3, "Checking device state for peer %s\n", peer_name); + + res = AST_DEVICE_INVALID; + + /* 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(peer_name, FALSE, TRUE))) { + /* We have an address for the peer */ + if (!ast_sockaddr_isnull(&peer->address) || !ast_sockaddr_isnull(&peer->default_address)) { + /* 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->call_limit && (peer->inuse == peer->call_limit)) { + /* check call limit */ + res = AST_DEVICE_BUSY; + } else if (peer->call_limit && 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->call_limit && (peer->inuse || peer->offhook)) { + /* Not busy, but we do have a call */ + res = AST_DEVICE_INUSE; + } else if (peer->qualify_max && (peer->last_qualify > peer->qualify_max || peer->last_qualify < 0)) { + /* 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; + } + } else { + /* there is no address, it's unavailable */ + res = AST_DEVICE_UNAVAILABLE; + } + + ao2_t_cleanup(peer, "drop peer"); + } + + return res; +} + +int sip_presencestate(const char *peer_name, char **subtype, char **message) +{ + struct sip_peer *peer; + int res; + + ast_debug(3, "Checking presence state for peer %s\n", peer_name); + + res = AST_PRESENCE_INVALID; + + if ((peer = sip_peer_find(peer_name, FALSE, TRUE))) { + if (!ast_sockaddr_isnull(&peer->address) || !ast_sockaddr_isnull(&peer->default_address)) { + if (peer->do_not_disturb) { + res = AST_PRESENCE_DND; + } else { + res = AST_PRESENCE_AVAILABLE; + } + } + + ao2_t_cleanup(peer, "drop peer"); + } + + return res; +} + +/* Deliver SIP call ID for the call */ +const char *sip_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_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 dialog; ignoring\n", ast_channel_name(channel)); + return -1; + } + + /* NOT ast_strlen_zero, because a zero-length message is specifically allowed by RFC 3428 (See section 10, Examples) */ + if (!text) { + return -1; + } + + if (!sip_allowed_method(&dialog->allowed_methods, SIP_METHOD_MESSAGE)) { + ast_debug(2, "Trying to send MESSAGE to device that does not support it\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_send_message(dialog, FALSE, FALSE); + ao2_unlock(dialog); + + return 0; +} + +/* Send message with Access-URL header, if this is an HTML URL only! */ +int sip_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 dialog; ignoring\n", ast_channel_name(channel)); + return -1; + } + + if (subclass != AST_HTML_URL) { + return -1; + } + + ast_string_field_build(dialog, html_url, "<%s>;mode=active", data); + + switch (ast_channel_state(channel)) { + case AST_STATE_RING: + sip_send_response(dialog, "100 Trying", &dialog->initial_request); + break; + case AST_STATE_RINGING: + sip_send_response(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_send_reinvite_with_sdp(dialog, FALSE, FALSE); + } else if (!ast_test_flag(&dialog->flags[0], SIP_PENDING_BYE)) { + ast_set_flag(&dialog->flags[0], SIP_NEED_REINVITE); + } + + break; + default: + ast_log(LOG_WARNING, "Don't know how to send URI when state is %u\n", ast_channel_state(channel)); + break; + } + + return 0; +} + +int sip_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_request request; + int res; + + ast_channel_lock(channel); + + if (ast_channel_tech(channel) != &sip_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; + } + + dialog = ast_channel_tech_pvt(channel); + ao2_lock(dialog); + + res = -1; + + if (!ast_strlen_zero(useragent_filter) && strstr(dialog->useragent, useragent_filter)) { + goto cleanup; + } + + sip_request_prepare(&request, dialog, SIP_METHOD_INFO, 0, TRUE); + + for (header = headers; header; header = header->next) { + sip_request_add_header(&request, header->name, header->value); + } + + if (!ast_strlen_zero(content) && !ast_strlen_zero(content_type)) { + sip_request_add_header(&request, "Content-Type", content_type); + sip_request_add_content(&request, content); + } + + res = sip_request_send(dialog, &request, SIP_SEND_RELIABLE, dialog->outgoing_cseq); + +cleanup: + ao2_unlock(dialog); + ast_channel_unlock(channel); + + return res; +} + +int sip_msg_send(const struct ast_msg *msg, const char *to, const char *from) +{ + struct sip_dialog *dialog; + char *uri, *domain, *user; + const char *name, *value; + struct ast_msg_var_iterator *iter; + struct sip_peer *peer; + int res; + + if (!(dialog = sip_dialog_alloc(NULL, NULL, FALSE, SIP_METHOD_MESSAGE, NULL, 0))) { + return -1; + } + + uri = ast_strdupa(to); + uri = sip_get_in_brackets(uri); + + sip_parse_uri(uri, "sip:,sips:", &user, &domain, NULL); + + if (ast_strlen_zero(domain)) { + ast_log(LOG_WARNING, "MESSAGE(to) is invalid for SIP '%s'\n", to); + + sip_dialog_unlink(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + 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_t_cleanup(peer, "drop peer"); + } else if (strchr(from, '<')) { /* from is callerid-style */ + char *callerid, *caller_name, *caller_number; + + 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 (strchr(caller_number, ':')) { /* Must be a URI */ + char *user, *domain, *port; + + sip_parse_uri(caller_number, "sip:,sips:", &user, &domain, NULL); + + sip_pedantic_decode(user); + sip_pedantic_decode(domain); + + ast_sockaddr_split_hostport(domain, &domain, &port, PARSE_PORT_IGNORE); + + 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); + } + } + + ao2_lock(dialog); + + /* Look up the host to contact */ + if (sip_dialog_build(dialog, domain, NULL, TRUE)) { + ao2_unlock(dialog); + + sip_dialog_unlink(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + return -1; + } + + if (!ast_strlen_zero(user)) { + ast_string_field_set(dialog, username, user); + } + + sip_get_our_address(dialog->socket.transport, &dialog->address, &dialog->our_address); + ast_set_flag(&dialog->flags[0], SIP_OUTGOING); + + /* Save additional MESSAGE headers in case of authentication request. */ + for (iter = ast_msg_var_iterator_init(msg); ast_msg_var_iterator_next(msg, iter, &name, &value); ast_msg_var_unref_current(iter)) { + if (!strcasecmp(name, "Request-URI")) { + ast_string_field_set(dialog, full_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_t_cleanup(dialog, "drop dialog"); + + ast_log(LOG_NOTICE, "MESSAGE(Max-Forwards) reached zero. MESSAGE not sent\n"); + + return -1; + } + + dialog->max_forwards--; + continue; + } 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")) { + continue; + } + + ast_variable_list_append(&dialog->message_headers, ast_variable_new(name, value, "")); + } + + ast_msg_var_iterator_destroy(iter); + + ast_string_field_set(dialog, message_content, ast_msg_get_body(msg)); + res = sip_send_message(dialog, TRUE, FALSE); + + ao2_unlock(dialog); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + ao2_t_cleanup(dialog, "drop dialog"); + + return res; +} diff -durN asterisk-22.3.0.orig/channels/sip/cli.c asterisk-22.3.0/channels/sip/cli.c --- asterisk-22.3.0.orig/channels/sip/cli.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/cli.c 2025-04-17 11:11:22.790703835 +1200 @@ -0,0 +1,2237 @@ +/* + * 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/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 "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/stimer.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/auth_realms.h" +#include "include/domain.h" +#include "include/peers.h" +#include "include/registry.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/cli.h" +#include "include/manager.h" +#include "include/mwi_subscriptions.h" + +/* SIP Cli commands definition */ +struct ast_cli_entry sip_cli_commands[] = { + AST_CLI_DEFINE(sip_cli_show_channels, "List active SIP channels or subscriptions"), + AST_CLI_DEFINE(sip_cli_show_channel, "Show detailed SIP channel info"), + AST_CLI_DEFINE(sip_cli_show_channel_stats, "List statistics for active SIP channels"), + AST_CLI_DEFINE(sip_cli_show_domains, "List our local SIP domains"), + AST_CLI_DEFINE(sip_cli_show_inuse, "List all inuse/limits"), + AST_CLI_DEFINE(sip_cli_show_peers, "List defined SIP peers"), + AST_CLI_DEFINE(sip_cli_show_registry, "List SIP registration status"), + AST_CLI_DEFINE(sip_cli_show_settings, "Show SIP global settings"), + AST_CLI_DEFINE(sip_cli_show_history, "Show SIP dialog history"), + 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_mwi, "Show MWI subscriptions"), + 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 all SIP object allocations"), + AST_CLI_DEFINE(sip_cli_unregister, "Unregister (sip_force expiration) a SIP peer from the registry"), + AST_CLI_DEFINE(sip_cli_notify, "Send a notify packet 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 packet to a peer"), + AST_CLI_DEFINE(sip_cli_prune_realtime, "Prune cached Realtime users/peers"), + AST_CLI_DEFINE(sip_cli_set_debug, "Enable/Disable SIP debugging"), + AST_CLI_DEFINE(sip_cli_set_history, "Enable/Disable SIP history"), + AST_CLI_DEFINE(sip_cli_reload, "Reload SIP configuration"), + AST_CLI_DEFINE(NULL, "") +}; + +static char *sip_cli_complete_peer(const char *word, int state, int flags); +static char *sip_cli_complete_call_id(const char *line, const char *word, int pos, int state); +static char *sip_cli_complete_notify(const char *line, const char *word, int pos, int state); +static int sip_cli_peer_cmp(const void *arg1, const void *arg2); + +/* Do completion on peer name */ +static char *sip_cli_complete_peer(const char *word, int state, int flags) +{ + int word_len, which; + struct ao2_iterator iter; + struct sip_peer *peer; + char *name; + + word_len = strlen(word); + which = 0; + name = NULL; + + iter = ao2_iterator_init(sip_peers, 0); + + while ((peer = ao2_t_iterator_next(&iter, "bump peer"))) { + /* locking of the object is not required because only the name and flags are being compared */ + if (!strncasecmp(word, peer->name, word_len) && (!flags || ast_test_flag(&peer->flags[1], flags)) && ++which > state) { + name = ast_strdup(peer->name); + } + + ao2_t_cleanup(peer, "drop peer"); + + if (name) { + break; + } + } + + ao2_iterator_destroy(&iter); + + return name; +} + +/* Support routine for 'sip show channel' and 'sip show history' CLI + * This is in charge of generating all strings that match a prefix in the + * given position. As many functions of this kind, each invokation has + * O(state) time complexity so be careful in using it. */ +static char *sip_cli_complete_call_id(const char *line, const char *word, int pos, int state) +{ + int which, word_len; + struct sip_dialog *dialog; + char *call_id; + struct ao2_iterator iter; + + if (pos != 3) { + return NULL; + } + + word_len = strlen(word); + which = 0; + call_id = NULL; + + iter = ao2_iterator_init(sip_dialogs, 0); + + while ((dialog = ao2_t_iterator_next(&iter, "bump dialog"))) { + ao2_lock(dialog); + + if (!strncasecmp(word, dialog->call_id, word_len) && ++which > state) { + call_id = ast_strdup(dialog->call_id); + ao2_unlock(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + break; + } + + ao2_unlock(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + } + + ao2_iterator_destroy(&iter); + + return call_id; +} + +/* Support routine for 'sip notify' CLI */ +static char *sip_cli_complete_notify(const char *line, const char *word, int pos, int state) +{ + int which, word_len; + char *category; + + word_len = strlen(word); + which = 0; + category = NULL; + + if (pos == 2) { + /* do completion for notify type */ + if (!sip_notify_types) { + return NULL; + } + + while ((category = ast_category_browse(sip_notify_types, category))) { + if (!strncasecmp(word, category, word_len) && ++which > state) { + return ast_strdup(category); + } + } + } + + if (pos > 2) { + return sip_cli_complete_peer(word, state, 0); + } + + return NULL; +} + +/* Force reload of module from cli */ +char *sip_cli_reload(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + if (cmd == CLI_INIT) { + entry->command = "sip reload"; + entry->usage = "Usage: sip reload\n" + " Reloads SIP configuration from sip.conf\n"; + + return NULL; + } else if (cmd == CLI_GENERATE) { + return NULL; + } + + sip_config_reload(); + + return CLI_SUCCESS; +} + +/* Show details of one active dialog */ +char *sip_cli_show_channel(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_dialog *dialog; + int arg_len, found; + struct ao2_iterator iter; + + if (cmd == CLI_INIT) { + entry->command = "sip show channel"; + entry->usage = "Usage: sip show channel \n" + " Provides detailed status on a given SIP dialog (identified by SIP call-id).\n"; + + return NULL; + } else if (cmd == CLI_GENERATE) { + return sip_cli_complete_call_id(args->line, args->word, args->pos, args->n); + } + + 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_t_iterator_next(&iter, "bump dialog"))) { + ao2_lock(dialog); + + if (!strncasecmp(dialog->call_id, args->argv[3], arg_len)) { + struct ast_str *path, *format_names; + const char *content_type; + + format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + + ast_cli(args->fd, "\n"); + + if (dialog->subscribe_events != SIP_SUBSCRIBE_NONE) { + switch (dialog->subscribe_events) { + case SIP_SUBSCRIBE_DIALOG_INFO_XML: + content_type = "dialog-info"; + break; + case SIP_SUBSCRIBE_PIDF_XML: + content_type = "pidf-xml"; + break; + case SIP_SUBSCRIBE_MESSAGE_SUMMARY: + content_type = "message-summary"; + break; + case SIP_SUBSCRIBE_FEATURE_EVENTS: + content_type = "feature-events"; + break; + default: + content_type = ""; + break; + } + + ast_cli(args->fd, " Type: Subscription (%s)\n", content_type); + } else { + ast_cli(args->fd, " Type: Call\n"); + } + + ast_cli(args->fd, " Curr. Trans. Direction: %s\n", + ast_test_flag(&dialog->flags[0], SIP_OUTGOING) ? "Outgoing" : "Incoming"); + ast_cli(args->fd, " Call-ID: %s\n", dialog->call_id); + ast_cli(args->fd, " Owner channel ID: %s\n", + dialog->channel ? ast_channel_name(dialog->channel) : ""); + ast_cli(args->fd, " Our Codec Capability: %s\n", + ast_format_cap_get_names(dialog->format_cap, &format_names)); + ast_cli(args->fd, " Non-Codec Capability (DTMF): %d\n", dialog->non_format_cap); + ast_cli(args->fd, " Their Codec Capability: %s\n", + ast_format_cap_get_names(dialog->remote_format_cap, &format_names)); + ast_cli(args->fd, " Joint Codec Capability: %s\n", + ast_format_cap_get_names(dialog->joint_format_cap, &format_names)); + ast_cli(args->fd, " Format: %s\n", + dialog->channel ? ast_format_cap_get_names(ast_channel_nativeformats(dialog->channel), &format_names) : ""); + ast_cli(args->fd, " T.38 support %s\n", AST_CLI_YESNO(dialog->udptl != NULL)); + ast_cli(args->fd, " Video support %s\n", AST_CLI_YESNO(dialog->video_rtp != NULL)); + ast_cli(args->fd, " MaxCallBR: %dkbps\n", dialog->max_call_bitrate); + ast_cli(args->fd, " Theoretical Address: %s\n", ast_sockaddr_stringify(&dialog->address)); + ast_cli(args->fd, " Received Address: %s\n", ast_sockaddr_stringify(&dialog->received_address)); + ast_cli(args->fd, " Allow Transfer: %s\n", + AST_CLI_YESNO(ast_test_flag(&dialog->flags[1], SIP_ALLOW_TRANSFER))); + ast_cli(args->fd, " Force Rport: %s\n", sip_force_rport2str(dialog->flags)); + + if (ast_sockaddr_isnull(&dialog->audio_redirect_address)) { + ast_cli(args->fd, " Audio IP: %s, local\n", + ast_sockaddr_stringify_addr(&dialog->our_address)); + } else { + ast_cli(args->fd, " Audio IP: %s, outside bridge\n", + ast_sockaddr_stringify_addr(&dialog->audio_redirect_address)); + } + + ast_cli(args->fd, " Our Tag: %s\n", dialog->local_tag); + ast_cli(args->fd, " Their Tag: %s\n", dialog->remote_tag); + ast_cli(args->fd, " SIP User agent: %s\n", dialog->useragent); + + if (!ast_strlen_zero(dialog->username)) { + ast_cli(args->fd, " Username: %s\n", dialog->username); + } + + if (!ast_strlen_zero(dialog->peer_name)) { + ast_cli(args->fd, " Peername: %s\n", dialog->peer_name); + } + + if (!ast_strlen_zero(dialog->uri)) { + ast_cli(args->fd, " Original URI: %s\n", dialog->uri); + } + + if (!ast_strlen_zero(dialog->caller_number)) { + ast_cli(args->fd, " Caller-ID: %s\n", dialog->caller_number); + } + + ast_cli(args->fd, " Need Destroy: %s\n", AST_CLI_YESNO(dialog->need_destroy)); + ast_cli(args->fd, " Last Message: %s\n", dialog->last_message); + ast_cli(args->fd, " Promiscuous Redir: %s\n", + AST_CLI_YESNO(ast_test_flag(&dialog->flags[0], SIP_PROMISCUOUS_REDIRECT))); + + if ((path = sip_route_list(&dialog->route, TRUE, 0))) { + ast_cli(args->fd, " Route: %s\n", ast_str_buffer(path)); + ast_free(path); + } + + ast_cli(args->fd, " DTMF Mode: %s\n", + sip_dtmf_mode2str(ast_test_flag(&dialog->flags[0], SIP_DTMF))); + ast_cli(args->fd, " SIP Options: %s\n", sip_options2str(dialog->options)); + + ast_cli(args->fd, " Transport: %s\n", ast_transport2str(dialog->socket.transport)); + ast_cli(args->fd, " Media: %s\n", + dialog->secure_audio_rtp ? "SRTP" : dialog->audio_rtp ? "RTP" : "None"); + + if (!dialog->stimer) { + ast_cli(args->fd, " Session-Timer: Uninitiallized\n"); + } else { + ast_cli(args->fd, " Session-Timer: %s\n", dialog->stimer->active ? "Active" : "Inactive"); + + if (dialog->stimer->active) { + ast_cli(args->fd, " Session Interval: %d\n", dialog->stimer->interval); + ast_cli(args->fd, " Session Refresher: %s\n", + sip_stimer_refresher2str(dialog->stimer->refresher)); + ast_cli(args->fd, " Session Peer Timer: %s\n", + dialog->stimer->remote_active ? "Active" : "Inactive"); + ast_cli(args->fd, " Session Cached Min-SE: %d\n", dialog->stimer->cached_min_se); + ast_cli(args->fd, " Session Cached SE: %d\n", dialog->stimer->cached_max_se); + ast_cli(args->fd, " Session Cached Ref: %s\n", + sip_stimer_refresher2str(dialog->stimer->cached_refresher)); + ast_cli(args->fd, " Session Cached Mode: %s\n", + sip_stimer_mode2str(dialog->stimer->cached_mode)); + } + } + + ast_cli(args->fd, "\n"); + found = TRUE; + } + + ao2_unlock(dialog); + ao2_t_ref(dialog, -1, "drop dialog"); + } + + ao2_iterator_destroy(&iter); + + if (!found) { + ast_cli(args->fd, "No such SIP Call-ID starting with '%s'\n", args->argv[3]); + } + + return CLI_SUCCESS; +} + +/* Cli command to send SIP notify to peer */ +char *sip_cli_notify(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct ast_variable *variables; + RAII_VAR(struct ast_channel *, dummy_channel, NULL, ast_channel_cleanup); + struct ast_str *content; + int i; + + if (cmd == 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 (cmd == CLI_GENERATE) { + return sip_cli_complete_notify(args->line, args->word, args->pos, args->n); + } + + if (args->argc < 4) { + return CLI_SHOWUSAGE; + } + + if (!sip_notify_types) { + ast_cli(args->fd, "No notify types found, or no types listed there\n"); + return CLI_FAILURE; + } + + if (!(variables = ast_variable_browse(sip_notify_types, args->argv[2]))) { + ast_cli(args->fd, "Unable to find notify type '%s'\n", args->argv[2]); + return CLI_FAILURE; + } + + if (!(dummy_channel = ast_dummy_channel_alloc())) { + ast_cli(args->fd, "Cannot allocate dummy channel for variables substitution\n"); + return CLI_FAILURE; + } + + content = ast_str_alloca(4096); + + for (i = 3; i < args->argc; i++) { + struct sip_dialog *dialog; + char value[2048], value_sub[2048]; + struct ast_variable *variable; + + if (!(dialog = sip_dialog_alloc(NULL, NULL, FALSE, SIP_METHOD_NOTIFY, NULL, 0))) { + ast_log(LOG_WARNING, "Unable to build sip dialog data for notify (memory/socket error)\n"); + return CLI_FAILURE; + } + + if (sip_dialog_build(dialog, args->argv[i], NULL, TRUE)) { + /* Maybe they're not registered, etc. */ + sip_dialog_unlink(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + ast_cli(args->fd, "Could not create address for '%s'\n", args->argv[i]); + continue; + } + + sip_get_our_address(dialog->socket.transport, &dialog->address, &dialog->our_address); + ast_set_flag(&dialog->flags[0], SIP_OUTGOING); + + /* Set the name of the peer being sent the notification so it can be used in ${} functions */ + pbx_builtin_setvar_helper(dummy_channel, "PEERNAME", dialog->peer_name); + + ast_str_reset(content); + + for (variable = variables; variable; variable = variable->next) { + ast_copy_string(value, variable->value, sizeof(value)); + ast_unescape_semicolon(value); + pbx_substitute_variables_helper(dummy_channel, value, value_sub, sizeof(value_sub)); + + if (!strcasecmp(variable->name, "Content")) { + if (ast_str_strlen(content)) { + ast_str_append(&content, 0, "\r\n"); + } + + ast_str_append(&content, 0, "%s", value_sub); + } else if (!strcasecmp(variable->name, "Content-Length")) { + ast_log(LOG_WARNING, "it is not necessary to specify Content-Length in sip_notify.conf, ignoring\n"); + } else { + ast_variable_list_append(&dialog->notify_headers, ast_variable_new(variable->name, value_sub, "")); + } + } + + ast_string_field_set(dialog, notify_content, ast_str_buffer(content)); + + ast_cli(args->fd, "Sending NOTIFY of type '%s' to '%s'\n", args->argv[2], args->argv[i]); + + sip_dialog_sched_destroy(dialog, SIP_TIMEOUT); + sip_send_notify(dialog, SIP_INIT_REQUEST); + + ao2_t_cleanup(dialog, "drop dialog"); + } + + return CLI_SUCCESS; +} + +/* Enable/Disable DoNotDisturb on a peer */ +char *sip_cli_do_not_disturb(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_peer *peer; + struct sip_alias *alias; + int do_not_disturb; + + if (cmd == 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 (cmd == CLI_GENERATE) { + if (args->pos == 3) { + return sip_cli_complete_peer(args->word, args->n, 0); + } + + 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, "No such peer '%s'\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_t_cleanup(peer, "drop peer"); + + return CLI_SUCCESS; +} + +/* Login to/Logout from huntgroup for a peer */ +char *sip_cli_hunt_group(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_peer *peer; + struct sip_alias *alias; + int hunt_group; + + if (cmd == 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 (cmd == CLI_GENERATE) { + if (args->pos == 3) { + return sip_cli_complete_peer(args->word, args->n, 0); + } + + 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, "No such peer '%s'\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_t_cleanup(peer, "drop peer"); + + return CLI_SUCCESS; +} + +/* Sets/Removes the call fowarding extension for a peer */ +char *sip_cli_call_forward(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_peer *peer; + const char *call_forward; + + if (cmd == 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 (cmd == CLI_GENERATE) { + if (args->pos == 3) { + return sip_cli_complete_peer(args->word, args->n, 0); + } + + 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, "No such peer '%s'\n", args->argv[3]); + return CLI_FAILURE; + } + + if (ast_strlen_zero(call_forward)) { + ast_cli(args->fd, "Call forwarding on '%s' cleared\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_t_cleanup(peer, "drop peer"); + + return CLI_SUCCESS; +} + +/* Show history details of one dialog */ +char *sip_cli_show_history(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_dialog *dialog; + int arg_len, found; + struct ao2_iterator iter; + + if (cmd == CLI_INIT) { + entry->command = "sip show history"; + entry->usage = "Usage: sip show history \n" + " Provides detailed dialog history on a given SIP call (specified by call-id).\n"; + + return NULL; + } else if (cmd == CLI_GENERATE) { + return sip_cli_complete_call_id(args->line, args->word, args->pos, args->n); + } + + if (args->argc != 4) { + return CLI_SHOWUSAGE; + } + + if (!sip_config.record_history) { + ast_cli(args->fd, "\n***Note: History recording is currently disablefd. Use 'sip set history on' to enable\n"); + } + + arg_len = strlen(args->argv[3]); + found = FALSE; + + iter = ao2_iterator_init(sip_dialogs, 0); + + while ((dialog = ao2_t_iterator_next(&iter, "bump dialog"))) { + ao2_lock(dialog); + + if (!strncasecmp(dialog->call_id, args->argv[3], arg_len)) { + struct sip_history *history; + int count; + + ast_cli(args->fd, "\n"); + + if (dialog->subscribe_events != SIP_SUBSCRIBE_NONE) { + ast_cli(args->fd, " Subscription\n"); + } else { + ast_cli(args->fd, " Call\n"); + } + + count = 0; + + AST_LIST_TRAVERSE(&dialog->history, history, next) { + ast_cli(args->fd, "%d. %s\n", ++count, history->event); + } + + if (!count) { + ast_cli(args->fd, "Call '%s' has no history\n", dialog->call_id); + + } + + found = TRUE; + } + + ao2_unlock(dialog); + ao2_t_ref(dialog, -1, "drop dialog"); + } + + ao2_iterator_destroy(&iter); + + if (!found) { + ast_cli(args->fd, "No such SIP Call ID starting with '%s'\n", args->argv[3]); + } + + return CLI_SUCCESS; +} + +/* Turn on SIP debugging (CLI command) */ +char *sip_cli_set_debug(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + int old_sip_debug; + + if (cmd == 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 (cmd == CLI_GENERATE) { + if (args->pos == 4 && !strcasecmp(args->argv[3], "peer")) { + return sip_cli_complete_peer(args->word, args->n, 0); + } + + return NULL; + } + + old_sip_debug = sip_debug & SIP_DEBUG_CONSOLE; + + if (args->argc == entry->args) { /* 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 == SIP_DEBUG_NONE) { + 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/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; + } + + ast_cli(args->fd, "SIP debugging enabled for IP: %s\n", ast_sockaddr_stringify_addr(&sip_debug_address)); + sip_debug |= SIP_DEBUG_CONSOLE; + } 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, "No such peer '%s'\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); + + ast_cli(args->fd, "SIP debugging enabled for IP: %s\n", ast_sockaddr_stringify_addr(&sip_debug_address)); + sip_debug |= SIP_DEBUG_CONSOLE; + } + } + + return CLI_SUCCESS; + } + + return CLI_SHOWUSAGE; +} + +/* Enable/Disable SIP History logging (CLI) */ +char *sip_cli_set_history(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + if (cmd == CLI_INIT) { + entry->command = "sip set history {on|off}"; + entry->usage = "Usage: sip set history {on|off}\n" + " Enables/Disables recording of SIP dialog history for debugging purposes.\n" + " Use 'sip show history' to view the history of a call number.\n"; + + return NULL; + } else if (cmd == CLI_GENERATE) { + return NULL; + } + + if (args->argc != entry->args) { + return CLI_SHOWUSAGE; + } + + if (!strcasecmp(args->argv[3], "on")) { + sip_config.record_history = TRUE; + ast_cli(args->fd, "SIP history recording enabled (use 'sip show history')\n"); + } else if (!strcasecmp(args->argv[3], "off")) { + sip_config.record_history = FALSE; + ast_cli(args->fd, "SIP history recording disabled\n"); + } else { + return CLI_SHOWUSAGE; + } + + return CLI_SUCCESS; +} + +/* Remove temporary realtime objects from memory (CLI) */ +char *sip_cli_prune_realtime(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_peer *peer; + int check_all; + const char *peer_name; + regex_t regex; + + if (cmd == 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 (cmd == CLI_GENERATE) { + if (args->pos == 3) { + const char *choices[] = {"peer", "like", "all", NULL}; + char *arg; + + if (!(arg = ast_cli_complete(args->word, choices, args->n))) { + arg = sip_cli_complete_peer(args->word, args->n - ARRAY_LEN(choices), SIP_REALTIME_CACHE_PEERS); + } + + return arg; + } + + if (args->pos == 4 && !strcasecmp(args->argv[3], "peer")) { + return sip_cli_complete_peer(args->word, args->n, SIP_REALTIME_CACHE_PEERS); + } + + 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) { + /* sip prune realtime {peer|like} name */ + 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(®ex, 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_t_iterator_next(&iter, "bump peer"))) { + ao2_lock(peer); + + if (peer_name && regexec(®ex, peer->name, 0, NULL, 0)) { + ao2_unlock(peer); + ao2_t_cleanup(peer, "drop peer"); + + continue; + }; + + if (ast_test_flag(&peer->flags[1], SIP_REALTIME_CACHE_PEERS)) { + peer->removed = TRUE; + count++; + } + + ao2_unlock(peer); + ao2_t_cleanup(peer, "drop peer"); + } + + ao2_iterator_destroy(&iter); + + if (count) { + sip_peer_unlink_all(TRUE); + ast_cli(args->fd, "%d peers pruned\n", count); + } else { + ast_cli(args->fd, "No peers found to prune\n"); + } + } else if ((peer = ao2_t_find(sip_peers, peer_name, OBJ_SEARCH_KEY | OBJ_UNLINK, "unlink+bump peer"))) { + if (!ast_sockaddr_isnull(&peer->address)) { + ao2_t_unlink(sip_peers_by_address, peer, "unlink peer"); + } + + if (!ast_test_flag(&peer->flags[1], SIP_REALTIME_CACHE_PEERS)) { + ast_cli(args->fd, "Peer '%s' is not a realtime peer, cannot be pruned\n", peer_name); + /* Put it back! */ + ao2_t_link(sip_peers, peer, "link peer"); + + if (!ast_sockaddr_isnull(&peer->address)) { + ao2_t_link(sip_peers_by_address, peer, "link peer"); + } + } else { + ast_cli(args->fd, "Peer '%s' pruned\n", peer_name); + } + + ao2_t_cleanup(peer, "drop peer"); + } else { + ast_cli(args->fd, "Peer '%s' not found\n", peer_name); + } + + if (check_all && peer_name) { + regfree(®ex); + } + + return CLI_SUCCESS; +} + +char *sip_cli_show_sched(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct ast_cb_names cbnames = { + 10, + {"sip_packet_resend", "sip_auto_destruct", "sip_peer_expire_register", "sip_dialog_auto_congest", + "sip_registy_timeout", "sip_peer_qualify", "sip_peer_qualify_now", "sip_peer_qualify_timeout", + "sip_registry_send", "sip_dialog_start_need_reinvite"}, + {sip_packet_resend, sip_dialog_auto_destruct, sip_peer_expire_register, sip_dialog_auto_congest, + sip_registry_timeout, sip_peer_qualify, sip_peer_qualify_now, sip_peer_qualify_timeout, + sip_registry_send, sip_dialog_start_need_reinvite} + }; + struct ast_str *sched; + + if (cmd == CLI_INIT) { + entry->command = "sip show sched"; + entry->usage = "Usage: sip show sched\n" + " Shows stats on what's in the sched queue at the moment\n"; + + return NULL; + } else if (cmd == CLI_GENERATE) { + return NULL; + } + + sched = ast_str_alloca(4096); + + ast_cli(args->fd, "\n"); + ast_sched_report(sip_sched_context, &sched, &cbnames); + ast_cli(args->fd, "%s", ast_str_buffer(sched)); + + return CLI_SUCCESS; +} + +/* Show active TCP connections */ +char *sip_cli_show_tcp(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_tcptls_thread *thread; + struct ao2_iterator iter; + + if (cmd == 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 (cmd == CLI_GENERATE) { + return NULL; + } + + if (args->argc != 3) { + return CLI_SHOWUSAGE; + } + + ast_cli(args->fd, "%-47.47s %9.9s %6.6s\n", "Address", "Transport", "Type"); + iter = ao2_iterator_init(sip_tcptls_threads, 0); + + while ((thread = ao2_t_iterator_next(&iter, "bump thread"))) { + ast_cli(args->fd, "%-47.47s %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_t_ref(thread, -1, "drop thread"); + } + + ao2_iterator_destroy(&iter); + + return CLI_SUCCESS; +} + +/* Send an OPTIONS packet to a SIP peer */ +char *sip_cli_qualify_peer(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + int load_realtime; + struct sip_peer *peer; + + if (cmd == CLI_INIT) { + entry->command = "sip qualify peer"; + 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 (cmd == 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->word, args->n, 0); + } 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]); + + ao2_t_cleanup(peer, "drop peer"); + } else { + ast_cli(args->fd, "Peer '%s' not found\n", args->argv[3]); + } + + return CLI_SUCCESS; +} + +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); +} + +/* CLI Show Peers command */ +char *sip_cli_show_peers(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_peer *peer; + struct ao2_iterator iter; + int i, count, monitored_notok, monitored_ok, unmonitored_notok, unmonitored_ok, realtime, has_pattern; + struct sip_peer **peers; + struct ast_str *status; + char name[256]; + regex_t regex; + + if (cmd == 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 (cmd == CLI_GENERATE) { + return NULL; + } + + has_pattern = FALSE; + + if (args->argc == 5) { + if (!strcasecmp(args->argv[3], "like")) { + if (regcomp(®ex, 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"); + + ast_cli(args->fd, "%-25.25s %-34.34s %-3.3s %-10.10s %-10.10s %-3.3s %-9s %-16s %-32.32s %s\n", + "Name/Username", "Host", "Dyn", "ForceRport", "Comedia", "ACL", "Port", "Status", "Description", + (realtime ? "Realtime" : "")); + + ao2_lock(sip_peers); + + if (!(peers = ast_calloc(sizeof(peer), ao2_container_count(sip_peers)))) { + ast_log(AST_LOG_ERROR, "Unable to allocate peer array for sip show peers\n"); + ao2_unlock(sip_peers); + + return CLI_FAILURE; + } + + ao2_unlock(sip_peers); + iter = ao2_iterator_init(sip_peers, 0); + + for (count = 0; (peer = ao2_t_iterator_next(&iter, "bump peer")); count++) { + ao2_lock(peer); + + if (has_pattern && regexec(®ex, peer->name, 0, NULL, 0)) { + ao2_unlock(peer); + ao2_t_cleanup(peer, "drop peer"); + + continue; + } + + peers[count] = peer; + ao2_unlock(peer); + } + + ao2_iterator_destroy(&iter); + + qsort(peers, count, sizeof(peer), sip_cli_peer_cmp); + + status = ast_str_alloca(16); + monitored_notok = 0; monitored_ok = 0; unmonitored_notok = 0; unmonitored_ok = 0; + + for (i = 0; i < count; i++) { + peer = peers[i]; + ao2_lock(peer); + + if (!ast_strlen_zero(peer->username)) { + snprintf(name, sizeof(name), "%s/%s", peer->name, peer->username); + } else { + ast_copy_string(name, peer->name, sizeof(name)); + } + + switch (sip_peer_get_status(peer, &status)) { + case SIP_PEER_REACHABLE: + monitored_ok++; + break; + case SIP_PEER_UNREACHABLE: + monitored_notok++; + break; + case SIP_PEER_UNMONITORED: + if (ast_sockaddr_isnull(&peer->address) || !ast_sockaddr_port(&peer->address)) { + unmonitored_notok++; + } else { + unmonitored_ok++; + } + + break; + } + + ast_cli(args->fd, "%-25.25s %-34.34s %-3.3s %-10.10s %-10.10s %-3.3s %-9s %-16s %-32.32s %s\n", + name, + !ast_sockaddr_isnull(&peer->address) ? ast_sockaddr_stringify_addr(&peer->address) : "(Unspecified)", + peer->host_dynamic ? " D " : " ", /* Dynamic or not? */ + sip_force_rport2str(peer->flags), + sip_comedia2str(peer->flags), + !ast_acl_list_is_empty(peer->acl) ? " A " : " ", /* permit/deny */ + !ast_sockaddr_isnull(&peer->address) ? ast_strdupa(ast_sockaddr_stringify_port(&peer->address)) : "0", + ast_str_buffer(status), + peer->description ? peer->description : "", + realtime ? (peer->realtime ? "Cached RT" : "") : ""); + + ao2_unlock(peer); + ao2_t_cleanup(peer, "drop peer"); + } + + ast_cli(args->fd, "%d sip peer%s. Monitored: %d online, %d offline. Unmonitored: %d online, %d offline\n", + count, ESS(count), monitored_ok, monitored_notok, unmonitored_ok, unmonitored_notok); + + if (has_pattern) { + regfree(®ex); + } + + ast_free(peers); + + return CLI_SUCCESS; +} + +/* Show one peer in detail */ +char *sip_cli_show_peer(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + char callerid[256], group[256]; + struct sip_peer *peer; + int load_realtime, realtime; + struct sip_alias *alias; + struct sip_subscription *subscription; + struct ast_str *status, *path, *format_names, *mailboxes, *namedgroups; + struct sip_auth_realm_head *auth_realms; + + if (cmd == 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 (cmd == 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->word, args->n, 0); + } 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, " Name: %s\n", peer->name); + ast_cli(args->fd, " Description: %s\n", peer->description); + + if (realtime) { /* Realtime is enabled */ + ast_cli(args->fd, " Realtime Peer: %s\n", peer->realtime ? "Yes, cached" : "No"); + } + + ast_cli(args->fd, " Secret: %s\n", ast_strlen_zero(peer->secret) ? "" : ""); + ast_cli(args->fd, " MD5Secret: %s\n", ast_strlen_zero(peer->md5secret) ? "" : ""); + ast_cli(args->fd, " Remote Secret: %s\n", ast_strlen_zero(peer->remote_secret) ? "" : ""); + + if ((auth_realms = peer->auth_realms)) { + struct sip_auth_realm *auth_realm; + const char *secret; + + ao2_t_ref(auth_realms, +1, "bump auth_realms"); + + AST_LIST_TRAVERSE(auth_realms, auth_realm, next) { + if (!ast_strlen_zero(auth_realm->secret)) { + secret = ""; + } else if (!ast_strlen_zero(auth_realm->md5secret)) { + secret = ""; + } else { + secret = ""; + } + + ast_cli(args->fd, " Realm-Auth: Realm %-15.15s User %-10.20s %s\n", + auth_realm->realm, auth_realm->username, secret); + } + + ao2_t_ref(auth_realms, -1, "drop auth_realms"); + } + + ast_cli(args->fd, " Context: %s\n", peer->context); + ast_cli(args->fd, " Subscr.Cont.: %s\n", S_OR(peer->subscribe_context, "")); + ast_cli(args->fd, " Language: %s\n", peer->language); + ast_cli(args->fd, " Tonezone: %s\n", !ast_strlen_zero(peer->zone) ? peer->zone : ""); + + if (!ast_strlen_zero(peer->accountcode)) { + ast_cli(args->fd, " Accountcode: %s\n", peer->accountcode); + } + + ast_cli(args->fd, " AMA Flags: %s\n", ast_channel_amaflags2string(peer->amaflags)); + ast_cli(args->fd, " Allow Transfer: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_ALLOW_TRANSFER))); + ast_cli(args->fd, " CallingPres: %s\n", ast_describe_caller_presentation(peer->caller_presentation)); + + if (!ast_strlen_zero(peer->from_user)) { + ast_cli(args->fd, " FromUser: %s\n", peer->from_user); + } + + if (!ast_strlen_zero(peer->from_domain)) { + ast_cli(args->fd, " FromDomain: %s Port %d\n", + peer->from_domain, (peer->from_domain_port) ? peer->from_domain_port : SIP_STANDARD_PORT); + } + + ast_cli(args->fd, " Callgroup: %s\n", ast_print_group(group, sizeof(group), peer->callgroup)); + ast_cli(args->fd, " Pickupgroup: %s\n", ast_print_group(group, sizeof(group), peer->pickupgroup)); + + namedgroups = ast_str_alloca(512); + + ast_cli(args->fd, " Named Callgr: %s\n", ast_print_namedgroups(&namedgroups, peer->named_callgroups)); + + ast_str_reset(namedgroups); + + ast_cli(args->fd, " Nam. Pickupgr: %s\n", ast_print_namedgroups(&namedgroups, peer->named_pickupgroups)); + ast_cli(args->fd, " MOH Suggest: %s\n", peer->moh_suggest); + + mailboxes = ast_str_alloca(512); + + sip_peer_get_mailboxes(peer, &mailboxes); + + ast_cli(args->fd, " Mailbox: %s\n", ast_str_buffer(mailboxes)); + ast_cli(args->fd, " VM Extension: %s\n", peer->mwi_exten); + ast_cli(args->fd, " LastMsgsSent: %d/%d\n", peer->new_messages, peer->old_messages); + ast_cli(args->fd, " Call Limit: %d\n", peer->call_limit); + ast_cli(args->fd, " Max Forwards: %d\n", peer->max_forwards); + + if (peer->busy_level) { + ast_cli(args->fd, " Busy Level: %d\n", peer->busy_level); + } + + ast_cli(args->fd, " Dynamic: %s\n", AST_CLI_YESNO(peer->host_dynamic)); + ast_cli(args->fd, " CallerID: %s\n", + ast_callerid_merge(callerid, sizeof(callerid), peer->caller_name, peer->caller_number, "")); + ast_cli(args->fd, " MaxCallBR: %dkbps\n", peer->max_call_bitrate); + ast_cli(args->fd, " Expires: %ldms\n", ast_sched_when(sip_sched_context, peer->register_expire_sched_id)); + ast_cli(args->fd, " Force Rport: %s\n", sip_force_rport2str(peer->flags)); + ast_cli(args->fd, " Symmetric RTP: %s\n", sip_comedia2str(peer->flags)); + ast_cli(args->fd, " ACL: %s\n", AST_CLI_YESNO(ast_acl_list_is_empty(peer->acl) == 0)); + ast_cli(args->fd, " Contact ACL: %s\n", AST_CLI_YESNO(ast_acl_list_is_empty(peer->contact_acl) == 0)); + ast_cli(args->fd, " DirectMedia ACL: %s\n", AST_CLI_YESNO(ast_acl_list_is_empty(peer->direct_media_acl) == 0)); + ast_cli(args->fd, " T.38 support: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_T38_SUPPORT))); + ast_cli(args->fd, " T.38 EC mode: %s\n", sip_t38_ecmode2str(ast_test_flag(&peer->flags[1], SIP_T38_SUPPORT))); + ast_cli(args->fd, " T.38 MaxDtgrm: %u\n", peer->t38_max_datagram); + ast_cli(args->fd, " DirectMedia: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_DIRECT_MEDIA))); + ast_cli(args->fd, " PromiscRedir: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_PROMISCUOUS_REDIRECT))); + ast_cli(args->fd, " User=Phone: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_USER_EQ_PHONE))); + ast_cli(args->fd, " Video Support: %s\n", + AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_VIDEO_SUPPORT) || ast_test_flag(&peer->flags[1], SIP_VIDEO_SUPPORT_ALWAYS))); + ast_cli(args->fd, " Text Support: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_TEXT_SUPPORT))); + ast_cli(args->fd, " Ign SDP Ver: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_IGNORE_SDP_VERSION))); + ast_cli(args->fd, " Trust RPID: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_TRUST_REMOTE_PARTY_ID))); + ast_cli(args->fd, " Send RPID: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_SEND_REMOTE_PARTY_ID))); + ast_cli(args->fd, " Path Support: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_USE_PATH))); + + if ((path = sip_route_list(&peer->path, TRUE, 0))) { + ast_cli(args->fd, " Path: %s\n", ast_str_buffer(path)); + ast_free(path); + } + + ast_cli(args->fd, " TrustIDOutbnd: %s\n", sip_trust_id_outbound2str(ast_test_flag(&peer->flags[1], SIP_TRUST_ID_OUTBOUND))); + ast_cli(args->fd, " Subscriptions: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_ALLOW_SUBSCRIBE))); + ast_cli(args->fd, " Overlap Dial: %s\n", sip_allow_overlap2str(ast_test_flag(&peer->flags[1], SIP_ALLOW_OVERLAP))); + + if (peer->proxy) { + ast_cli(args->fd, " Outbound Proxy: %s %s\n", + ast_strlen_zero(peer->proxy->host) ? "" : peer->proxy->host, + peer->proxy->force ? "(forced)" : ""); + } + + ast_cli(args->fd, " DTMF Mode: %s\n", sip_dtmf_mode2str(ast_test_flag(&peer->flags[0], SIP_DTMF))); + ast_cli(args->fd, " Timer T1: %d\n", peer->timer_t1); + ast_cli(args->fd, " Timer B: %d\n", peer->timer_b); + ast_cli(args->fd, " ToHost: %s\n", peer->host); + ast_cli(args->fd, " IP Address: %s\n", ast_sockaddr_stringify(&peer->address)); + ast_cli(args->fd, " Def.IP Address: %s\n", ast_sockaddr_stringify(&peer->default_address)); + ast_cli(args->fd, " Prim.Transp.: %s\n", ast_transport2str(peer->socket.transport)); + ast_cli(args->fd, " Allowed.Trsp: %s\n", sip_transports2str(peer->transports)); + ast_cli(args->fd, " Def. Username: %s\n", peer->username); + ast_cli(args->fd, " SIP Options: %s\n", sip_options2str(peer->options)); + + 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, " Auto-Framing: %s\n", AST_CLI_YESNO(peer->auto_framing)); + + status = ast_str_alloca(16); + + sip_peer_get_status(peer, &status); + + ast_cli(args->fd, " Status: %s\n", ast_str_buffer(status)); + ast_cli(args->fd, " Useragent: %s\n", peer->useragent); + ast_cli(args->fd, " Reg. Contact: %s\n", peer->full_contact); + ast_cli(args->fd, " Qualify Freq: %dms\n", peer->qualify_freq); + ast_cli(args->fd, " Keepalive: %dms\n", peer->keepalive * 1000); + + 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, " Session Timers: %s\n", sip_stimer_refresher2str(peer->stimer_mode)); + ast_cli(args->fd, " Session Refresh: %s\n", sip_stimer_refresher2str(peer->stimer_refresher)); + ast_cli(args->fd, " Session Expires: %ds\n", peer->stimer_max_se); + ast_cli(args->fd, " Min-Sess: %ds\n", peer->stimer_min_se); + ast_cli(args->fd, " RTP Engine: %s\n", peer->rtp_engine); + ast_cli(args->fd, " Parkinglot: %s\n", peer->parkinglot); + ast_cli(args->fd, " Use Reason: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_Q850_REASON))); + ast_cli(args->fd, " Encryption: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_USE_SRTP))); + ast_cli(args->fd, " RTCP Mux: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[2], SIP_RTCP_MUX))); + ast_cli(args->fd, " DND: %s\n", AST_CLI_YESNO(peer->do_not_disturb)); + ast_cli(args->fd, " CallFwd Ext.: %s\n", peer->call_forward); + ast_cli(args->fd, " Hunt Group: %s\n", AST_CLI_YESNO(peer->hunt_group)); + + if (ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER)) { + ast_cli(args->fd, " Off Hook: %s\n", AST_CLI_YESNO(peer->offhook)); + ast_cli(args->fd, " Device Name: %s\n", peer->cisco_device_name); + ast_cli(args->fd, " Active Load: %s\n", peer->cisco_active_load); + ast_cli(args->fd, " Inactive Load: %s\n", peer->cisco_inactive_load); + + if (!AST_LIST_EMPTY(&peer->aliases)) { + ast_cli(args->fd, " BulkReg.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); + } + } + } + + ast_cli(args->fd, "\n"); + ao2_t_cleanup(peer, "drop peer"); + + return CLI_SUCCESS; +} + +char *sip_cli_show_mwi(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + char host[80]; + struct ao2_iterator iter; + struct sip_mwi_subscription *mwi; + + if (cmd == 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 (cmd == CLI_GENERATE) { + return NULL; + } + + ast_cli(args->fd, "%-30.30s %-20.20s %-20.20s %-10.10s\n", "Host", "Username", "Mailbox", "Subscribed"); + iter = ao2_iterator_init(sip_mwi_subscriptions, 0); + + while ((mwi = ao2_t_iterator_next(&iter, "bump mwi"))) { + ao2_lock(mwi); + + snprintf(host, sizeof(host), "%s:%d", mwi->hostname, mwi->port ? mwi->port : SIP_STANDARD_PORT); + ast_cli(args->fd, "%-30.30s %-20.20s %-20.20s %-10.10s\n", + host, mwi->username, mwi->mailbox, AST_CLI_YESNO(mwi->subscribed)); + + ao2_unlock(mwi); + ao2_t_ref(mwi, -1, "drop mwi"); + } + + ao2_iterator_destroy(&iter); + + return CLI_SUCCESS; +} + +/* Show SIP Registry (registrations with other SIP proxies */ +char *sip_cli_show_registry(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + char host[80], user[80], reg_time[80]; + struct ast_tm tm; + int user_len, count; + struct ao2_iterator iter; + struct sip_registry *registry; + + if (cmd == CLI_INIT) { + entry->command = "sip show registry"; + entry->usage = "Usage: sip show registry\n" + " Lists all registration requests and status.\n"; + + return NULL; + } else if (cmd == 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", "Reg.Time"); + iter = ao2_iterator_init(sip_registry, 0); + + for (count = 0; (registry = ao2_t_iterator_next(&iter, "bump registry")); count++) { + ao2_lock(registry); + + snprintf(host, sizeof(host), "%s:%d", registry->hostname, registry->port ? registry->port : SIP_STANDARD_PORT); + snprintf(user, sizeof(user), "%s", registry->username); + + if (!ast_strlen_zero(registry->domain)) { + user_len = strlen(user); + snprintf(user + user_len, sizeof(user) - user_len, "@%s", registry->domain); + } + + if (registry->domain_port) { + user_len = strlen(user); + snprintf(user + user_len, sizeof(user) - user_len, ":%d", registry->domain_port); + } + + if (registry->time.tv_sec) { + ast_localtime(®istry->time, &tm, NULL); + ast_strftime(reg_time, sizeof(reg_time), "%a, %d %b %Y %T", &tm); + } else { + reg_time[0] = '\0'; + } + + ast_cli(args->fd, "%-39.39s %-6.6s %-12.12s %8d %-20.20s %-25.25s\n", + host, registry->dnsmgr ? "Y" : "N", user, registry->outgoing_expiry, sip_registry_state2str(registry->state), reg_time); + + ao2_unlock(registry); + ao2_t_ref(registry, -1, "drop registry"); + } + + ao2_iterator_destroy(&iter); + ast_cli(args->fd, "%d SIP registration%s\n", count, ESS(count)); + + return CLI_SUCCESS; +} + +/* Unregister (force expiration) a SIP peer in the registry via CLI + * \note This function does not tell the SIP device what's going on, + * so use it with great care. */ +char *sip_cli_unregister(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_peer *peer; + + if (cmd == CLI_INIT) { + entry->command = "sip unregister"; + entry->usage = "Usage: sip unregister \n" + " Unregister (force expiration) a SIP peer from the registry\n"; + + return NULL; + } else if (cmd == CLI_GENERATE) { + return sip_cli_complete_peer(args->word, args->n, 0); + } + + if (args->argc != 3) { + return CLI_SHOWUSAGE; + } + + if ((peer = sip_peer_find(args->argv[2], FALSE, TRUE))) { + if (peer->register_expire_sched_id != -1) { + AST_SCHED_DEL_UNREF(sip_sched_context, peer->register_expire_sched_id, ao2_t_cleanup(peer, "drop peer")); + + sip_peer_expire_register(ao2_t_bump(peer, "bump peer")); + ast_cli(args->fd, "Unregistered peer \'%s\'\n\n", args->argv[2]); + } else { + ast_cli(args->fd, "Peer %s not registered\n", args->argv[2]); + } + + ao2_t_cleanup(peer, "drop peer"); + } else { + ast_cli(args->fd, "Peer unknown: \'%s\'. Not unregistered\n", args->argv[2]); + } + + return CLI_SUCCESS; +} + +/* CLI for show channels or subscriptions */ +char *sip_cli_show_channels(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_dialog *dialog; + struct ao2_iterator iter; + int subscriptions; + int count = 0; + + if (cmd == CLI_INIT) { + entry->command = "sip show {channels|subscriptions}"; + entry->usage = "Usage: sip show channels\n" + " Lists all currently active SIP calls (dialogs).\n" + "Usage: sip show subscriptions\n" + " Lists active SIP subscriptions.\n"; + + return NULL; + } else if (cmd == CLI_GENERATE) { + return NULL; + } + + if (args->argc != entry->args) { + return CLI_SHOWUSAGE; + } + + subscriptions = !strcasecmp(args->argv[entry->args - 1], "subscriptions"); + + if (!subscriptions) { + ast_cli(args->fd, "%-15.15s %-15.15s %-15.15s %-15.15s %-7.7s %-15.15s %-10.10s %-10.10s\n", + "Peer", "User/ANR", "Call ID", "Format", "Hold", "Last Message", "Expiry", "Peer"); + } else { + ast_cli(args->fd, "%-15.15s %-15.15s %-15.15s %-15.15s %-13.13s %-15.15s %-10.10s %-6.6s\n", + "Peer", "User", "Call ID", "Extension", "Last state", "Type", "Mailbox", "Expiry"); + } + + iter = ao2_iterator_init(sip_dialogs, 0); + + while ((dialog = ao2_iterator_next(&iter))) { + struct ast_sockaddr address; + + ao2_lock(dialog); + ast_sockaddr_copy(&address, sip_dialog_get_address(dialog)); + + if (dialog->subscribe_events == SIP_SUBSCRIBE_NONE && !subscriptions) { + /* set if SIP transfer in progress */ + struct ast_str *format_names; + + format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + + ast_cli(args->fd, "%-15.15s %-15.15s %-15.15s %-15.15s %-3.3s %-3.3s %-15.15s %-10.10s %-10.10s\n", + ast_sockaddr_stringify_addr(&address), + S_OR(dialog->username, S_OR(dialog->caller_number, "")), + dialog->call_id, + dialog->channel ? ast_format_cap_get_names(ast_channel_nativeformats(dialog->channel), &format_names) : "", + AST_CLI_YESNO(ast_test_flag(&dialog->flags[1], SIP_ONHOLD)), + dialog->need_destroy ? "*" : "", + dialog->last_message , + dialog->refer_status ? sip_refer_status2str(dialog->refer_status) : "", + dialog->peer ? dialog->peer->name : ""); + + count++; + } else if (dialog->subscribe_events != SIP_SUBSCRIBE_NONE && subscriptions) { + struct ast_str *mailboxes; + char *type; + + mailboxes = ast_str_alloca(512); + + if (dialog->subscribe_events == SIP_SUBSCRIBE_MESSAGE_SUMMARY && dialog->peer) { + sip_peer_get_mailboxes(dialog->peer, &mailboxes); + } + + switch (dialog->subscribe_events) { + case SIP_SUBSCRIBE_DIALOG_INFO_XML: + type = "dialog-info"; + break; + case SIP_SUBSCRIBE_PIDF_XML: + type = "pidf-xml"; + break; + case SIP_SUBSCRIBE_MESSAGE_SUMMARY: + type = "mwi"; + break; + case SIP_SUBSCRIBE_FEATURE_EVENTS: + type = "feature-events"; + break; + default: + type = ""; + break; + } + + ast_cli(args->fd, "%-15.15s %-15.15s %-15.15s %-15.15s %-13.13s %-15.15s %-10.10s %-6.6d\n", + ast_sockaddr_stringify_addr(&address), + S_OR(dialog->username, S_OR(dialog->caller_number, "")), + dialog->call_id, + dialog->subscribe_events == SIP_SUBSCRIBE_DIALOG_INFO_XML || + dialog->subscribe_events == SIP_SUBSCRIBE_PIDF_XML ? dialog->subscribe_uri : "--", + dialog->subscribe_events == SIP_SUBSCRIBE_DIALOG_INFO_XML || + dialog->subscribe_events == SIP_SUBSCRIBE_PIDF_XML ? ast_extension_state2str(dialog->last_exten_state) : "", + type, + dialog->subscribe_events == SIP_SUBSCRIBE_MESSAGE_SUMMARY ? S_OR(ast_str_buffer(mailboxes), "") : "", + dialog->expiry); + + count++; + } + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + } + + ao2_iterator_destroy(&iter); + ast_cli(args->fd, "%d active SIP %s%s\n", count, (subscriptions ? "subscription" : "dialog"), ESS(count)); + + return CLI_SUCCESS; +} + +/* SIP show channelstats CLI (main function) */ +char *sip_cli_show_channel_stats(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_dialog *dialog; + struct ao2_iterator iter; + int count = 0; + + if (cmd == CLI_INIT) { + entry->command = "sip show channelstats"; + entry->usage = "Usage: sip show channelstats\n" + " Lists all currently active SIP channel'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 (cmd == 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: Pack", "Lost", "Jitter", "Send: Pack", "Lost", "Jitter"); + + iter = ao2_iterator_init(sip_dialogs, 0); + + while ((dialog = ao2_iterator_next(&iter))) { + struct ast_rtp_instance_stats stats; + char duration[10]; + + ao2_lock(dialog); + + if (dialog->subscribe_events != SIP_SUBSCRIBE_NONE) { + /* Subscriptions */ + ao2_unlock(dialog); + ao2_ref(dialog, -1); + + continue; + } + + if (!dialog->audio_rtp) { + if (sip_debug) { + ast_cli(args->fd, "%-15.15s %-11.11s (inv state: %s) -- %s\n", + ast_sockaddr_stringify_addr(&dialog->address), dialog->call_id, + sip_invite_state2str(dialog->invite_state), "-- No RTP active"); + } + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + + continue; + } + + if (ast_rtp_instance_get_stats(dialog->audio_rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) { + ast_log(LOG_WARNING, "Could not get RTP stats\n"); + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + + 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 %-10.10u%-1.1s %-10.10u (%5.2f%%) %-6.4lf %-10.10u%-1.1s %-10.10u (%5.2f%%) %-6.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); + + count++; + + ao2_unlock(dialog); + ao2_ref(dialog, -1); + } + + ao2_iterator_destroy(&iter); + ast_cli(args->fd, "%d active SIP channel%s\n", count, ESS(count)); + + return CLI_SUCCESS; +} + +/* CLI Command to show calls within limits set by call_limit */ +char *sip_cli_show_inuse(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + char limit[64], inuse[64]; + int show_all; + struct ao2_iterator iter; + struct sip_peer *peer; + + if (cmd == 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 limit.\n"; + + return NULL; + } else if (cmd == CLI_GENERATE) { + return NULL; + } + + if (args->argc < 3) { + return CLI_SHOWUSAGE; + } + + show_all = FALSE; + + if (args->argc == 4 && !strcmp(args->argv[3], "all")) { + show_all = TRUE; + } + + ast_cli(args->fd, "%-25.25s %-15.15s %-15.15s\n", "* Peer name", "In use", "Limit"); + iter = ao2_iterator_init(sip_peers, 0); + + while ((peer = ao2_t_iterator_next(&iter, "bump peer"))) { + ao2_lock(peer); + + if (peer->call_limit) { + snprintf(limit, sizeof(limit), "%d", peer->call_limit); + } else { + ast_copy_string(limit, "N/A", sizeof(limit)); + } + + snprintf(inuse, sizeof(inuse), "%d/%d/%d", peer->inuse, peer->ringing, peer->onhold); + + if (show_all || peer->call_limit) { + ast_cli(args->fd, "%-25.25s %-15.15s %-15.15s\n", peer->name, inuse, limit); + } + + ao2_unlock(peer); + ao2_t_cleanup(peer, "drop peer"); + } + + ao2_iterator_destroy(&iter); + + return CLI_SUCCESS; +} + +/* CLI command to list local domains */ +char *sip_cli_show_domains(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_domain *domain; + + if (cmd == 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 (cmd == CLI_GENERATE) { + return NULL; + } + + if (AST_LIST_EMPTY(&sip_domains)) { + ast_cli(args->fd, "SIP Domain support not enabled\n\n"); + return CLI_SUCCESS; + } else { + ast_cli(args->fd, "%-40.40s %-20.20s %-16.16s\n", "Our local SIP domains:", "Context", "Set by"); + AST_LIST_LOCK(&sip_domains); + + AST_LIST_TRAVERSE(&sip_domains, domain, next) { + ast_cli(args->fd, "%-40.40s %-20.20s %-16.16s\n", domain->name, S_OR(domain->context, "(default)"), + sip_domain_mode2str(domain->mode)); + } + + AST_LIST_UNLOCK(&sip_domains); + ast_cli(args->fd, "\n"); + + return CLI_SUCCESS; + } +} + +/* List all allocated SIP Objects (realtime or static) */ +char *sip_cli_show_objects(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct ao2_iterator iter; + struct sip_peer *peer; + struct sip_registry *registry; + struct sip_dialog *dialog; + + if (cmd == CLI_INIT) { + entry->command = "sip show objects"; + entry->usage = "Usage: sip show objects\n" + " Lists status of known SIP objects\n"; + + return NULL; + } else if (cmd == CLI_GENERATE) { + return NULL; + } + + if (args->argc != 3) { + return CLI_SHOWUSAGE; + } + + ast_cli(args->fd, "Peer objects: %d static, %d realtime\n\n", sip_static_count, sip_realtime_count); + + iter = ao2_iterator_init(sip_peers, 0); + + while ((peer = ao2_t_iterator_next(&iter, "bump peer"))) { + ao2_lock(peer); + ast_cli(args->fd, "Name: %s\nRefcount: %d\n\n", peer->name, ao2_ref(peer, 0)); + + ao2_unlock(peer); + ao2_t_ref(peer, -1, "drop peer"); + } + + ao2_iterator_destroy(&iter); + + ast_cli(args->fd, "Peer objects by IP:\n\n"); + + iter = ao2_iterator_init(sip_peers_by_address, 0); + + while ((peer = ao2_t_iterator_next(&iter, "bump peer"))) { + ao2_lock(peer); + ast_cli(args->fd, "Name: %s\nRefcount: %d\n\n", peer->name, ao2_ref(peer, 0)); + + ao2_unlock(peer); + ao2_t_ref(peer, -1, "drop peer"); + } + + ao2_iterator_destroy(&iter); + + ast_cli(args->fd, "Registry objects: %d\n\n", ao2_container_count(sip_registry)); + + iter = ao2_iterator_init(sip_registry, 0); + + while ((registry = ao2_t_iterator_next(&iter, "bump registry"))) { + ao2_lock(registry); + ast_cli(args->fd, "Name: %s\n", registry->config); + + ao2_unlock(registry); + ao2_t_ref(registry, -1, "drop registry"); + } + + ao2_iterator_destroy(&iter); + + ast_cli(args->fd, "Dialog objects:\n\n"); + + iter = ao2_iterator_init(sip_dialogs, 0); + + while ((dialog = ao2_t_iterator_next(&iter, "bump dialog"))) { + ao2_lock(dialog); + ast_cli(args->fd, "Call-ID: %s\nRefcount: %d\n\n", dialog->call_id, ao2_ref(dialog, 0)); + + ao2_unlock(dialog); + ao2_t_ref(dialog, -1, "drop dialog"); + } + + ao2_iterator_destroy(&iter); + + return CLI_SUCCESS; +} + +/* List global settings for the SIP channel */ +char *sip_cli_show_settings(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + int realtime; + struct ast_str *format_names; + const char *remapping; + struct sip_auth_realm_head *auth_realms; + struct ast_ha *ha; + + if (cmd == 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 (cmd == CLI_GENERATE) { + return NULL; + } + + if (args->argc != 3) { + return CLI_SHOWUSAGE; + } + + realtime = ast_check_realtime("sippeers"); + + ast_cli(args->fd, "\n\nGlobal Settings:\n"); + ast_cli(args->fd, "----------------\n"); + ast_cli(args->fd, " UDP Bind Address: %s\n", ast_sockaddr_stringify(&sip_config.bind_address)); + + if (ast_sockaddr_is_ipv6(&sip_config.bind_address) && ast_sockaddr_is_any(&sip_config.bind_address)) { + ast_cli(args->fd, " ** Additional Info:\n"); + ast_cli(args->fd, " [::] may include IPv4 in addition to IPv6, if such a feature is enabled in the OS\n"); + } + + ast_cli(args->fd, " TCP SIP Bind Address: %s\n", + sip_config.tcp_enabled ? ast_sockaddr_stringify(&sip_tcp_session.local_address) : "Disabled"); + ast_cli(args->fd, " TLS SIP 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, " Video Support: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[1], SIP_VIDEO_SUPPORT))); + ast_cli(args->fd, " Text Support: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[1], SIP_TEXT_SUPPORT))); + ast_cli(args->fd, " Ignore SDP Sess. Ver.: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[1], SIP_IGNORE_SDP_VERSION))); + ast_cli(args->fd, " Match Auth Username: %s\n", AST_CLI_YESNO(sip_config.match_auth_username)); + ast_cli(args->fd, " Allow Subscriptions: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[1], SIP_ALLOW_SUBSCRIBE))); + ast_cli(args->fd, " Allow Overlap Dialing: %s\n", sip_allow_overlap2str(ast_test_flag(&sip_config.flags[1], SIP_ALLOW_OVERLAP))); + ast_cli(args->fd, " Allow Promisc. Redir: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[0], SIP_PROMISCUOUS_REDIRECT))); + ast_cli(args->fd, " Enable Call Counters: %s\n", AST_CLI_YESNO(sip_config.call_counter)); + ast_cli(args->fd, " SIP Domain Support: %s\n", AST_CLI_YESNO(!AST_LIST_EMPTY(&sip_domains))); + ast_cli(args->fd, " Path Support : %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[0], SIP_USE_PATH))); + + ast_mutex_lock(&sip_auth_realm_lock); + + if ((auth_realms = sip_auth_realms)) { + ao2_t_ref(auth_realms, +1, "bump auth_realms"); + } + + ast_mutex_unlock(&sip_auth_realm_lock); + + ast_cli(args->fd, " Realm. Auth: %s\n", AST_CLI_YESNO(auth_realms != NULL)); + + if (auth_realms) { + struct sip_auth_realm *auth_realm; + const char *secret; + + AST_LIST_TRAVERSE(auth_realms, auth_realm, next) { + if (!ast_strlen_zero(auth_realm->secret)) { + secret = ""; + } else if (!ast_strlen_zero(auth_realm->md5secret)) { + secret = ""; + } else { + secret = ""; + } + + ast_cli(args->fd, " Realm. Auth: Realm %-15.15s User %-10.20s %s\n", + auth_realm->realm, auth_realm->username, secret); + } + + ao2_t_ref(auth_realms, -1, "drop auth_realms"); + } + + ast_cli(args->fd, " Our Auth Realm %s\n", sip_config.realm); + ast_cli(args->fd, " Use Domains as Realms: %s\n", AST_CLI_YESNO(sip_config.domains_as_realm)); + ast_cli(args->fd, " Call to Non-local Dom.: %s\n", AST_CLI_YESNO(sip_config.allow_external_domains)); + ast_cli(args->fd, " URI User is Phone no: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[0], SIP_USER_EQ_PHONE))); + ast_cli(args->fd, " Always Auth Rejects: %s\n", AST_CLI_YESNO(sip_config.always_auth_reject)); + ast_cli(args->fd, " Direct RTP Setup: %s\n", AST_CLI_YESNO(sip_config.direct_rtp_setup)); + ast_cli(args->fd, " User Agent: %s\n", sip_config.useragent); + ast_cli(args->fd, " SDP Session Name: %s\n", ast_strlen_zero(sip_config.sdp_session) ? "-" : sip_config.sdp_session); + ast_cli(args->fd, " SDP Owner Name: %s\n", ast_strlen_zero(sip_config.sdp_username) ? "-" : sip_config.sdp_username); + ast_cli(args->fd, " Trust RPID: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[0], SIP_TRUST_REMOTE_PARTY_ID))); + ast_cli(args->fd, " Send RPID: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[0], SIP_SEND_REMOTE_PARTY_ID))); + ast_cli(args->fd, " Send Diversion: %s\n", AST_CLI_YESNO(sip_config.send_diversion)); + ast_cli(args->fd, " Caller ID: %s\n", sip_config.callerid); + + 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, " Record SIP History: %s\n", AST_CLI_ONOFF(sip_config.record_history)); + ast_cli(args->fd, " Auth. Failure Events: %s\n", AST_CLI_ONOFF(sip_config.auth_failure_events)); + + ast_cli(args->fd, " SIP Realtime: %s\n", AST_CLI_YESNO(realtime)); + ast_cli(args->fd, " Qualify Freq: %dms\n", sip_config.qualify_freq); + ast_cli(args->fd, " Q.850 Reason Header: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[1], SIP_Q850_REASON))); + + ast_cli(args->fd, "\nNetwork QoS Settings:\n"); + ast_cli(args->fd, "---------------------------\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, " Jitterbuffer 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, " Jitterbuffer Forced: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_jb_config, AST_JB_FORCED))); + ast_cli(args->fd, " Jitterbuffer Max Size: %ld\n", sip_jb_config.max_size); + ast_cli(args->fd, " Jitterbuffer Resync: %ld\n", sip_jb_config.resync_threshold); + ast_cli(args->fd, " Jitterbuffer Impl.: %s\n", sip_jb_config.impl); + + if (!strcasecmp(sip_jb_config.impl, "adaptive")) { + ast_cli(args->fd, " Jitterbuffer Tgt Extra: %ld\n", sip_jb_config.target_extra); + } + + ast_cli(args->fd, " Jitterbuffer Log: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_jb_config, AST_JB_LOG))); + } + + ast_cli(args->fd, "\nNetwork Settings:\n"); + ast_cli(args->fd, "---------------------------\n"); + + /* determine if/how SIP address can be remapped */ + if (!sip_config.local_address) { + remapping = "Disabled, no localnet list"; + } else if (ast_sockaddr_isnull(&sip_config.external_address)) { + remapping = "Disabled"; + } else if (!ast_strlen_zero(sip_config.external_host)) { + remapping = "Enabled using external host"; + } else { + remapping = "Enabled using external address"; + } + + ast_cli(args->fd, " SIP Address Remapping: %s\n", remapping); + ast_cli(args->fd, " External Host: %s\n", S_OR(sip_config.external_host, "")); + ast_cli(args->fd, " External Address: %s\n", ast_sockaddr_stringify(&sip_config.external_address)); + ast_cli(args->fd, " External Refresh: %d\n", sip_config.external_refresh); + + for (ha = sip_config.local_address; ha != NULL; ha = ha->next) { + const char *address, *mask; + + address = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr)); + mask = ast_strdupa(ast_sockaddr_stringify_addr(&ha->netmask)); + + ast_cli(args->fd, " %-24s%s/%s\n", (ha == sip_config.local_address) ? "Localnet:" : "", address, mask); + } + + ast_cli(args->fd, "\nGlobal Signalling Settings:\n"); + ast_cli(args->fd, "---------------------------\n"); + + 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, " Relax DTMF: %s\n", AST_CLI_YESNO(sip_config.relax_dtmf)); + ast_cli(args->fd, " RFC2833 Compensation: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[0], SIP_RFC2833_COMPENSATE))); + ast_cli(args->fd, " Symmetric RTP: %s\n", sip_comedia2str(sip_config.flags)); + ast_cli(args->fd, " RTP Keepalive: %d %s\n", sip_config.rtp_keepalive, sip_config.rtp_keepalive ? "" : "(disabled)"); + ast_cli(args->fd, " RTP Timeout: %d %s\n", sip_config.rtp_timeout, sip_config.rtp_timeout ? "" : "(disabled)" ); + ast_cli(args->fd, " RTP Hold Timeout: %d %s\n", sip_config.rtp_hold_timeout, sip_config.rtp_hold_timeout ? "" : "(disabled)"); + ast_cli(args->fd, " DNS SRV Lookup: %s\n", AST_CLI_YESNO(sip_config.srv_lookup)); + ast_cli(args->fd, " Pedantic SIP Support: %s\n", AST_CLI_YESNO(sip_config.pedantic_checking)); + ast_cli(args->fd, " Reg. Min Duration %ds\n", sip_config.min_expiry); + ast_cli(args->fd, " Reg. Max Duration: %ds\n", sip_config.max_expiry); + ast_cli(args->fd, " Reg. Default Duration: %ds\n", sip_config.default_expiry); + ast_cli(args->fd, " Sub. Min Duration %ds\n", sip_config.min_subscribe_expiry); + ast_cli(args->fd, " Sub. Max Duration: %ds\n", sip_config.max_subscribe_expiry); + ast_cli(args->fd, " Outbound Reg. Timeout: %ds\n", sip_config.register_timeout); + ast_cli(args->fd, " Outbound Reg. Attempts: %d\n", sip_config.register_max_attempts); + ast_cli(args->fd, " Outbound Reg. Retry 403: %s\n", AST_CLI_YESNO(sip_config.register_retry_on_403)); + ast_cli(args->fd, " Notify Ringing State: %s\n", AST_CLI_YESNO(sip_config.notify_ringing)); + + if (sip_config.notify_ringing) { + ast_cli(args->fd, " Include CID: %s%s\n", + AST_CLI_YESNO(sip_config.notify_callerid), + sip_config.notify_callerid == SIP_NOTIFY_CALLERID_IGNORE_CONTEXT ? " (Ignoring context)" : ""); + } + + ast_cli(args->fd, " Notify Hold State: %s\n", AST_CLI_YESNO(sip_config.notify_hold)); + ast_cli(args->fd, " Allow Transfer: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[1], SIP_ALLOW_TRANSFER))); + ast_cli(args->fd, " Max Call Bitrate: %dkbps\n", sip_config.max_call_bitrate); + ast_cli(args->fd, " Auto-Framing: %s\n", AST_CLI_YESNO(sip_config.auto_framing)); + ast_cli(args->fd, " Outbound Proxy: %s %s\n", + ast_strlen_zero(sip_config.proxy.host) ? "" : sip_config.proxy.host, sip_config.proxy.force ? "(forced)" : ""); + ast_cli(args->fd, " Session Timers: %s\n", sip_stimer_mode2str(sip_config.stimer_mode)); + ast_cli(args->fd, " Session Refresher: %s\n", sip_stimer_refresher2str(sip_config.stimer_refresher)); + ast_cli(args->fd, " Session Expires: %ds\n", sip_config.stimer_max_se); + ast_cli(args->fd, " Session Min-SE: %ds\n", sip_config.stimer_min_se); + ast_cli(args->fd, " Timer T1: %d\n", sip_config.timer_t1); + ast_cli(args->fd, " Timer T1 Minimum: %d\n", sip_config.t1_min); + ast_cli(args->fd, " Timer B: %d\n", sip_config.timer_b); + ast_cli(args->fd, " No Premature Media: %s\n", AST_CLI_YESNO(sip_config.premature_media_filter)); + ast_cli(args->fd, " Max Forwards: %d\n", sip_config.max_forwards); + + ast_cli(args->fd, "\nDefault Settings:\n"); + ast_cli(args->fd, "-----------------\n"); + ast_cli(args->fd, " Allowed Transports: %s\n", sip_transports2str(sip_config.transports)); + ast_cli(args->fd, " Outbound Transport: %s\n", ast_transport2str(sip_config.primary_transport)); + ast_cli(args->fd, " Context: %s\n", sip_config.context); + ast_cli(args->fd, " Force Rport: %s\n", sip_force_rport2str(sip_config.flags)); + ast_cli(args->fd, " DTMF: %s\n", sip_dtmf_mode2str(ast_test_flag(&sip_config.flags[0], SIP_DTMF))); + ast_cli(args->fd, " Qualify: %d\n", sip_config.qualify_max); + ast_cli(args->fd, " Keepalive: %d\n", sip_config.keepalive); + ast_cli(args->fd, " Progress Inband: "); + + if (ast_test_flag(&sip_config.flags[0], SIP_PROGRESS_INBAND) == SIP_PROGRESS_INBAND_NEVER) { + ast_cli(args->fd, "Never\n"); + } else { + ast_cli(args->fd, "%s\n", + AST_CLI_YESNO(ast_test_flag(&sip_config.flags[0], SIP_PROGRESS_INBAND) != SIP_PROGRESS_INBAND_NO)); + } + + ast_cli(args->fd, " Language: %s\n", sip_config.language); + ast_cli(args->fd, " Tone Zone: %s\n", !ast_strlen_zero(sip_config.zone) ? sip_config.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, " Voice Mail Extension: %s\n", sip_config.mwi_exten); + ast_cli(args->fd, " RTCP Multiplexing: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[2], SIP_RTCP_MUX))); + + if (realtime) { + ast_cli(args->fd, "\nRealtime SIP Settings:\n"); + ast_cli(args->fd, "----------------------\n"); + ast_cli(args->fd, " Cache Friends: %s\n", + AST_CLI_YESNO(ast_test_flag(&sip_config.flags[1], SIP_REALTIME_CACHE_PEERS))); + ast_cli(args->fd, " Update: %s\n", AST_CLI_YESNO(sip_config.realtime_update_peer)); + ast_cli(args->fd, " Ignore Reg. Expiry: %s\n", AST_CLI_YESNO(sip_config.realtime_ignore_expiry)); + 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, " Auto Clear: %d (%s)\n", sip_config.realtime_auto_clear, + ast_test_flag(&sip_config.flags[1], SIP_REALTIME_AUTO_CLEAR) ? "Enabled" : "Disabled"); + } + + ast_cli(args->fd, "\n"); + + return CLI_SUCCESS; +} diff -durN asterisk-22.3.0.orig/channels/sip/conference.c asterisk-22.3.0/channels/sip/conference.c --- asterisk-22.3.0.orig/channels/sip/conference.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/conference.c 2025-04-17 11:11:22.790703835 +1200 @@ -0,0 +1,1021 @@ +/* + * 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/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/chan_tech.h" +#include "include/handlers.h" +#include "include/conference.h" + +/* Information required to start or join an ad-hoc conference */ +struct sip_conference_data { + struct sip_dialog *dialog; + AST_LIST_HEAD_NOLOCK(, sip_selected) selected; + unsigned int joining:1; + 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); + ); +}; + +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 *hook_dialog, int talking); +static int sip_conference_leave(struct ast_bridge_channel *channel, void *hook_dialog); +static int sip_conference_join(struct sip_conference *conference, struct ast_channel *channel, int administrator); + +static void sip_selected_destroy(struct sip_selected *selected); + +/* The ad-hoc conference list */ +struct sip_conferences_head sip_conferences; + +/* Handle conference request */ +int sip_remotecc_conference(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data) +{ + 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; + } + + ao2_t_bump(dialog, "bump dialog"); + + conference_data->dialog = dialog; + conference_data->joining = !strcmp(remotecc_data->soft_key_event, "Join"); + + ast_string_field_set(conference_data, call_id, remotecc_data->dialog.call_id); + ast_string_field_set(conference_data, local_tag, remotecc_data->dialog.remote_tag); + ast_string_field_set(conference_data, remote_tag, remotecc_data->dialog.local_tag); + + if (!conference_data->joining) { + ast_string_field_set(conference_data, join_call_id, remotecc_data->consult_dialog.call_id); + ast_string_field_set(conference_data, join_local_tag, remotecc_data->consult_dialog.remote_tag); + ast_string_field_set(conference_data, join_remote_tag, remotecc_data->consult_dialog.local_tag); + } else { + ao2_lock(peer); + + while ((selected = AST_LIST_REMOVE_HEAD(&peer->selected, next))) { + AST_LIST_INSERT_TAIL(&conference_data->selected, selected, next); + } + + ao2_unlock(peer); + } + + if (ast_pthread_create_detached_background(&threadid, NULL, sip_conference_thread, conference_data)) { + ao2_t_cleanup(conference_data->dialog, "drop dialog"); + 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_send_response(dialog, "202 Accepted", request); + + if (!conference_data->joining) { + struct sip_request notify_request; + + ast_set_flag(&dialog->flags[0], SIP_OUTGOING); + ast_set_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED); + + dialog->subscribe_events = SIP_SUBSCRIBE_REMOTECC_XML; + dialog->expiry = sip_config.min_expiry; + + sip_request_copy(&dialog->initial_request, request); + sip_request_init(¬ify_request, dialog, SIP_METHOD_NOTIFY, NULL); + + sip_request_add_header(¬ify_request, "Event", "refer"); + sip_request_build_header(¬ify_request, "Subscription-State", "active;expires=%d", dialog->expiry); + + sip_request_send(dialog, ¬ify_request, SIP_SEND_RELIABLE, dialog->outgoing_cseq); + } + + return 0; +} + +/* Handle conflist and confdetails requests */ +int sip_remotecc_conflist(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data) +{ + struct sip_dialog *refer_dialog; + struct sip_conference *conference; + struct sip_participant *participant; + struct ast_str *content; + int is_79xx; + + conference = NULL; + + if (!ast_strlen_zero(remotecc_data->dialog.call_id)) { + struct sip_dialog *target_dialog; + + 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 ((conference = target_dialog->conference)) { + ao2_ref(conference, +1); + } + + ao2_unlock(target_dialog); + ao2_t_cleanup(target_dialog, "drop dialog"); + } else if (remotecc_data->conference_id) { + AST_LIST_LOCK(&sip_conferences); + + AST_LIST_TRAVERSE(&sip_conferences, conference, next) { + if (conference->id == remotecc_data->conference_id) { + ao2_ref(conference, +1); + break; + } + } + + AST_LIST_UNLOCK(&sip_conferences); + } + + if (!conference) { + ast_debug(1, "Unable to find conference\n"); + return -1; + } + + sip_send_response(dialog, "202 Accepted", request); + + if (!ast_strlen_zero(remotecc_data->user_call_data) && strcmp(remotecc_data->user_call_data, "Update")) { + int participant_id; + + if (!strcmp(remotecc_data->user_call_data, "Remove") || !strcmp(remotecc_data->user_call_data, "Mute")) { + ast_string_field_set(peer, cisco_soft_key, remotecc_data->user_call_data); + ao2_ref(conference, -1); + + return 0; + } + + /* Default action is to mute/unmute */ + if (ast_strlen_zero(peer->cisco_soft_key)) { + ast_string_field_set(peer, cisco_soft_key, "Mute"); + } + + participant_id = atoi(remotecc_data->user_call_data); + ao2_lock(conference); + + AST_LIST_TRAVERSE(&conference->participants, participant, next) { + if (participant->id == participant_id) { + if (!strcmp(peer->cisco_soft_key, "Remove")) { + ast_verb(3, "%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(peer->cisco_soft_key, "Mute")) { + ast_verb(3, "%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(peer, cisco_soft_key, ""); + } + + if (!(content = ast_str_create(4096))) { + ao2_ref(conference, -1); + return 0; + } + + if (!(refer_dialog = sip_dialog_alloc(NULL, &dialog->socket, FALSE, SIP_METHOD_REFER, NULL, 0))) { + ao2_ref(conference, -1); + ast_free(content); + return 0; + } + + sip_dialog_copy(refer_dialog, dialog); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%d\n", SIP_REMOTECC_CONFLIST); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "%d\n", conference->id); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Conference\n"); + + ao2_lock(conference); + AST_LIST_TRAVERSE(&conference->participants, participant, next) { + char *status, *name; + + if (participant->removed) { + continue; + } + + if (participant->muted) { + status = "- "; + } else if (participant->talking) { + status = "+ "; + } else { + status = ""; + } + + ast_channel_lock(participant->channel); + + name = NULL; + + if (ast_strlen_zero(name) && ast_channel_caller(participant->channel)->id.name.valid) { + name = ast_strdupa(ast_channel_caller(participant->channel)->id.name.str); + } + + if (ast_strlen_zero(name) && ast_channel_caller(participant->channel)->id.number.valid) { + name = ast_strdupa(ast_channel_caller(participant->channel)->id.number.str); + } + + ast_channel_unlock(participant->channel); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s%s\n", status, S_OR(name, "Anonymous")); + 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_request_get_header(request, "User-Agent"), "CP79") != NULL; + + ast_str_append(&content, 0, "Please select\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Exit\n"); + ast_str_append(&content, 0, "%d\n", is_79xx ? 3 : 1); + ast_str_append(&content, 0, "SoftKey:Exit\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Remove\n"); + ast_str_append(&content, 0, "%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"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Mute\n"); + ast_str_append(&content, 0, "%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"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Update\n"); + ast_str_append(&content, 0, "%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"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + sip_send_refer_with_content(refer_dialog, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(refer_dialog, "drop dialog"); + + ao2_ref(conference, -1); + ast_free(content); + + return 0; +} + +/* Handle remove last conference participant requests */ +int sip_remotecc_rmlastconf(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data) +{ + struct sip_dialog *target_dialog; + struct sip_conference *conference; + struct sip_participant *participant; + + 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 (target_dialog->conference) { + conference = target_dialog->conference; + ao2_ref(conference, +1); + } else { + conference = NULL; + } + + ao2_unlock(target_dialog); + ao2_t_cleanup(target_dialog, "drop dialog"); + + if (!conference) { + ast_debug(1, "Not in a conference\n"); + return -1; + } + + sip_send_response(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; +} + +/* Handle remotecc join requests */ +int sip_remotecc_join(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data) +{ + return sip_remotecc_conference(dialog, request, peer, remotecc_data); +} + +/* Handle remotecc select requests */ +int sip_remotecc_select(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data) +{ + struct sip_selected *selected; + int found; + + ao2_lock(peer); + + found = FALSE; + + AST_LIST_TRAVERSE(&peer->selected, selected, next) { + if (!strcmp(remotecc_data->dialog.call_id, selected->call_id) && + !strcmp(remotecc_data->dialog.remote_tag, selected->local_tag) && + !strcmp(remotecc_data->dialog.local_tag, selected->remote_tag)) { + found = TRUE; + break; + } + } + + ao2_unlock(peer); + + if (!found) { + if (!(selected = ast_calloc_with_stringfields(1, struct sip_selected, 256))) { + return -1; + } + + ast_string_field_set(selected, call_id, remotecc_data->dialog.call_id); + ast_string_field_set(selected, local_tag, remotecc_data->dialog.remote_tag); + ast_string_field_set(selected, remote_tag, remotecc_data->dialog.local_tag); + + ao2_lock(peer); + AST_LIST_INSERT_TAIL(&peer->selected, selected, next); + ao2_unlock(peer); + } + + sip_send_response(dialog, "202 Accepted", request); + + return 0; +} + +/* Handle remotecc unselect requests */ +int sip_remotecc_unselect(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data) +{ + struct sip_selected *selected; + + ao2_lock(peer); + + AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->selected, selected, next) { + if (!strcmp(remotecc_data->dialog.call_id, selected->call_id) && + !strcmp(remotecc_data->dialog.remote_tag, selected->local_tag) && + !strcmp(remotecc_data->dialog.local_tag, selected->remote_tag)) { + AST_LIST_REMOVE_CURRENT(next); + sip_selected_destroy(selected); + + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + ao2_unlock(peer); + sip_send_response(dialog, "202 Accepted", request); + + return 0; +} + +/* destroy conference callback for ao2_alloc */ +static void sip_conference_destroy(void *data) +{ + struct sip_conference *conference = (struct sip_conference *) data; + + ast_verb(3, "Destroying ad-hoc conference %d\n", conference->id); + + if (conference->bridge) { + ast_bridge_destroy(conference->bridge, 0); + conference->bridge = NULL; + } + + AST_LIST_LOCK(&sip_conferences); + AST_LIST_REMOVE(&sip_conferences, conference, next); + AST_LIST_UNLOCK(&sip_conferences); +} + +/* 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 = !!ast_test_flag(&dialog->flags[2], SIP_CISCO_KEEP_CONFERENCE); + conference->multi_admin = !!ast_test_flag(&dialog->flags[2], SIP_CISCO_MULTI_ADMIN_CONFERENCE); + + AST_LIST_HEAD_INIT_NOLOCK(&conference->participants); + dialog->conference = conference; + + AST_LIST_LOCK(&sip_conferences); + AST_LIST_INSERT_TAIL(&sip_conferences, conference, next); + AST_LIST_UNLOCK(&sip_conferences); + + ast_verb(3, "Creating ad-hoc conference %d\n", conference->id); + + return 0; +} + +static int sip_conference_talk_detector(struct ast_bridge_channel *channel, void *hook_dialog, int talking) +{ + struct sip_participant *participant; + struct sip_conference *conference; + + participant = (struct sip_participant *) hook_dialog; + conference = participant->conference; + + ast_debug(1, "%s %s talking in ad-hoc conference %d\n", + ast_channel_name(participant->channel), talking ? "started" : "stopped", conference->id); + participant->talking = talking; + + return 0; +} + +/* cleanup participant structure after leaving bridge */ +static int sip_conference_leave(struct ast_bridge_channel *channel, void *hook_dialog) +{ + struct sip_participant *participant; + struct sip_conference *conference; + + participant = (struct sip_participant *) hook_dialog; + conference = participant->conference; + + ast_verb(3, "%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->administrators--; + } else { + conference->users--; + } + + ao2_unlock(conference); + + if (conference->administrators + conference->users > 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->administrators + conference->users == 1) { + struct sip_participant *participant = AST_LIST_FIRST(&conference->participants); + + ast_verb(3, "Only one participant left in ad-hoc conference %d, removing\n", conference->id); + + ast_bridge_remove(conference->bridge, participant->channel); + } else if (conference->users && !conference->administrators && !conference->keep) { + struct sip_participant *participant; + + ast_verb(3, "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); + } + } + + ast_channel_unref(participant->channel); + ao2_ref(participant->conference, -1); + ast_free(participant); + + return -1; +} + +/* 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_tech) { + struct sip_dialog *dialog; + + dialog = ast_channel_tech_pvt(channel); + + ao2_lock(dialog); + + if (ast_test_flag(&dialog->flags[1], SIP_CISCO_USECALLMANAGER) && + ast_test_flag(&dialog->flags[2], SIP_CISCO_MULTI_ADMIN_CONFERENCE)) { + ao2_ref(conference, +1); + dialog->conference = 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; + + ao2_ref(conference, +1); + participant->conference = conference; + + participant->id = ++conference->next_participant_id; + participant->administrator = administrator; + + bridge_channel = ast_channel_internal_bridge_channel(channel); + ao2_ref(bridge_channel, +1); + + 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->administrators++; + } else { + conference->users++; + } + + ao2_unlock(conference); + + if (conference->administrators + conference->users > 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_verb(3, "%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; +} + +/* 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_t_cleanup(dialog, "drop dialog"); + + goto cleanup; + } + + /* Increase ref on conference so we don't need to keep a ref on it's parent dialog */ + conference = dialog->conference; + ao2_ref(conference, +1); + + if (!(channel = dialog->channel)) { + ast_debug(1, "No owner channel\n"); + + ao2_unlock(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + goto cleanup; + } + + ast_channel_ref(channel); + ao2_unlock(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + 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 = dialog->conference; + ao2_ref(conference, +1); + + ao2_unlock(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + } + + content = ast_str_alloca(4096); + + if (!conference_data->joining) { + 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_t_cleanup(dialog, "drop dialog"); + + goto cleanup; + } + + ast_channel_ref(channel); + ao2_unlock(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + 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 (!(dialog = sip_dialog_alloc(NULL, &conference_data->dialog->socket, FALSE, SIP_METHOD_REFER, NULL, 0))) { + goto cleanup; + } + + sip_dialog_copy(dialog, conference_data->dialog); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\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"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + ao2_lock(dialog); + sip_send_refer_with_content(dialog, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_unlock(dialog); + + ao2_t_cleanup(dialog, "drop dialog"); + } 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_t_cleanup(dialog, "drop dialog"); + + goto cleanup; + } + + ast_channel_ref(channel); + ao2_unlock(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + 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: + if (conference) { + ao2_ref(conference, -1); + } + + ast_str_reset(content); + + if (!conference_data->joining) { + struct sip_request request; + + ao2_lock(conference_data->dialog); + + sip_request_prepare(&request, conference_data->dialog, SIP_METHOD_NOTIFY, 0, TRUE); + sip_request_add_header(&request, "Event", "refer"); + sip_request_add_header(&request, "Subscription-State", "terminated;reason=noresource"); + sip_request_add_header(&request, "Content-Type", "application/x-cisco-remotecc-response+xml"); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%d\n", res ? 500 : 200); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + sip_request_add_content(&request, ast_str_buffer(content)); + sip_request_send(conference_data->dialog, &request, SIP_SEND_RELIABLE, conference_data->dialog->outgoing_cseq); + + ao2_unlock(conference_data->dialog); + } else { + if ((dialog = sip_dialog_alloc(NULL, &conference_data->dialog->socket, FALSE, SIP_METHOD_REFER, NULL, 0))) { + sip_dialog_copy(dialog, conference_data->dialog); + + if (res) { + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "notify_display\n"); + ast_str_append(&content, 0, "\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"); + ast_str_append(&content, 0, "\200S\n"); + ast_str_append(&content, 0, "10\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "1\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + } else { + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\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"); + ast_str_append(&content, 0, "Join\n"); + ast_str_append(&content, 0, "Complete\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + } + + ao2_lock(dialog); + sip_send_refer_with_content(dialog, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_unlock(dialog); + + ao2_t_cleanup(dialog, "drop dialog"); + } + } + + if (conference_data->joining) { + struct sip_selected *selected; + + while ((selected = AST_LIST_REMOVE_HEAD(&conference_data->selected, next))) { + sip_selected_destroy(selected); + } + } + + ao2_t_cleanup(conference_data->dialog, "drop dialog"); + ast_string_field_free_memory(conference_data); + ast_free(conference_data); + + return NULL; +} + +static void sip_selected_destroy(struct sip_selected *selected) +{ + ast_string_field_free_memory(selected); + ast_free(selected); +} + +void sip_selected_destroy_all(struct sip_peer *peer) +{ + struct sip_selected *selected; + + while ((selected = AST_LIST_REMOVE_HEAD(&peer->selected, next))) { + sip_selected_destroy(selected); + } +} diff -durN asterisk-22.3.0.orig/channels/sip/config.c asterisk-22.3.0/channels/sip/config.c --- asterisk-22.3.0.orig/channels/sip/config.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/config.c 2025-04-17 11:11:22.792703782 +1200 @@ -0,0 +1,1389 @@ +/* + * 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/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/format.h" +#include "asterisk/format_cache.h" +#include "asterisk/pbx.h" +#include "asterisk/acl.h" +#include "asterisk/stasis.h" +#include "asterisk/parking.h" +#include "asterisk/cli.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/request.h" +#include "include/stimer.h" +#include "include/proxy.h" +#include "include/domain.h" +#include "include/auth_realms.h" +#include "include/peers.h" +#include "include/realtime.h" +#include "include/registry.h" +#include "include/mwi_subscriptions.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/events.h" + +#define SIP_DEFAULT_TIMER_T1 500 /* SIP timer T1 (according to RFC 3261) */ +#define SIP_DEFAULT_T1_MIN 100 /* 100 MS for minimal roundtrip time */ + +/* Default values, set and reset in reload_config before reading configuration These are default values in the source. There are other + * recommended values in the sip.conf.sample for new installations. These may differ to keep backwards compatibility, yet encouraging new + * behaviour on new installations */ +#define SIP_DEFAULT_EXPIRY 120 +#define SIP_DEFAULT_MIN_EXPIRY 60 +#define SIP_DEFAULT_MAX_EXPIRY 3600 +#define SIP_DEFAULT_MWI_EXPIRY 3600 + +#define SIP_DEFAULT_REGISER_TIMEOUT 20 +#define SIP_DEFAULT_TCP_AUTH_LIMIT 100 +#define SIP_DEFAULT_TCP_AUTH_TIMEOUT 30 + +#define SIP_DEFAULT_USERAGENT "Asterisk PBX" /* Default Useragent */ +#define SIP_DEFAULT_SDP_SESSION "Asterisk PBX" /* Default SDP session name, (s=) */ +#define SIP_DEFAULT_SIP_USERNAME "Asterisk-SIP" /* Default SDP username field in (o=) */ +#define SIP_DEFAULT_RTP_ENGINE "asterisk" /* Default RTP engine to use for sessions */ +#define SIP_DEFAULT_CONTEXT "default" /* The default context for [general] section as well as devices */ +#define SIP_DEFAULT_MOH_INTERPRET "default" /* The default music class */ +#define SIP_DEFAULT_MOH_SUGGEST "" +#define SIP_DEFAULT_MWI_EXTEN "asterisk" /* Default voicemail extension */ +#define SIP_DEFAULT_CALLERID "asterisk" /* Default caller ID */ +#define SIP_DEFAULT_MWI_FROM "" + +#define SIP_DEFAULT_QUALIFY FALSE /* Don't monitor devices */ +#define SIP_DEFAULT_QUALIFY_GAP 100 +#define SIP_DEFAULT_QUALIFY_PEERS 1 /* Number of peers to qualify at one time */ +#define SIP_DEFAULT_QUALIFY_FREQ (60 * 1000) /* Qualification: How often to check for the host to be up */ +#define SIP_DEFAULT_QUALIFY_MAX 2000 /* Qualification: Must be faster than 2 seconds by default */ + +#define SIP_DEFAULT_KEEPALIVE 0 /* Don't send keep alive packets */ +#define SIP_DEFAULT_KEEPALIVE_INTERVAL 60 /* Send keep alive packets at 60 second intervals */ + +#define SIP_DEFAULT_MAX_FORWARDS 70 +#define SIP_DEFAULT_ALLOW_TRANSFER TRUE +#define SIP_DEFAULT_ALLOW_SUBSCRIBE TRUE +#define SIP_DEFAULT_AUTO_FRAMING FALSE +#define SIP_DEFAULT_CALL_COUNTER FALSE /* Do not enable call counters by default */ +#define SIP_DEFAULT_SRV_LOOKUP FALSE /* Recommended setting is ON */ +#define SIP_DEFAULT_SEND_DIVERSION TRUE +#define SIP_DEFAULT_SHRINK_CALLERID TRUE +#define SIP_DEFAULT_PEDANTIC_CHECKING TRUE /* Follow SIP standards for dialog matching */ +#define SIP_DEFAULT_MAXCALLBITRATE 384 /* Max bitrate for video */ +#define SIP_DEFAULT_NOTIFY_RINGING TRUE /* Notify devicestate system on ringing state */ +#define SIP_DEFAULT_NOTIFY_HOLD FALSE /* Notify devicestate system on hold state */ + +#define SIP_DEFAULT_RTP_TIMEOUT 0 +#define SIP_DEFAULT_RTP_HOLD_TIMEOUT 0 +#define SIP_DEFAULT_RTP_KEEPALIVE FALSE /* Default RTPkeepalive setting */ +#define SIP_DEFAULT_DIRECT_RTP_SETUP FALSE + +#define SIP_DEFAULT_NOTIFY_CALLERID SIP_NOTIFY_CALLERID_DISABLED /* Include CID with ringing notifications */ + +#define SIP_DEFAULT_ALLOW_EXTERNAL_DOMAINS TRUE /* Allow external domains */ +#define SIP_DEFAULT_REALM "asterisk" /* Realm for HTTP digest authentication */ +#define SIP_DEFAULT_DOMAINS_AS_REALM FALSE /* Use the domain option to guess the realm for registration and invite requests */ +#define SIP_DEFAULT_MATCH_EXTERNAL_ADDRESS_LOCALLY FALSE /* Match extern IP locally default setting */ +#define SIP_DEFAULT_MATCH_AUTH_USERNAME FALSE + +#define SIP_DEFAULT_ALWAYS_AUTH_REJECT TRUE /* Don't reject authentication requests always */ +#define SIP_DEFAULT_AUTH_OPTIONS FALSE +#define SIP_DEFAULT_AUTH_MESSAGE TRUE +#define SIP_DEFAULT_ACCEPT_OUTOFCALL_MESSAGE TRUE + +#define SIP_DEFAULT_REALTIME_UPDATE_PEER TRUE +#define SIP_DEFAULT_DYNAMIC_EXCLUDE_STATIC TRUE + +#define SIP_DEFAULT_STIMER_MAX_SE 1800 /* Session-Timer Default Session-Expires period (RFC 4028) */ +#define SIP_DEFAULT_STIMER_MIN_SE 90 /* Session-Timer Default Min-SE period (RFC 4028) */ + +#define SIP_DEFAULT_TOS_SIP 0 /* Call signalling packets should be marked as DSCP CS3, but the default is 0 to be compatible + * with previous versions. */ +#define SIP_DEFAULT_TOS_AUDIO 0 /* Audio packets should be marked as DSCP EF (Expedited Forwarding), but the default is 0 + * to be compatible with previous versions. */ +#define SIP_DEFAULT_TOS_VIDEO 0 /* Video packets should be marked as DSCP AF41, but the default is 0 to be compatible with + * previous versions. */ +#define SIP_DEFAULT_TOS_TEXT 0 /* Text packets should be marked as XXXX XXXX, but the default is 0 to be compatible with + * previous versions. */ +#define SIP_DEFAULT_COS_SIP 4 /* Level 2 class of service for SIP signalling */ +#define SIP_DEFAULT_COS_AUDIO 5 /* Level 2 class of service for audio media */ +#define SIP_DEFAULT_COS_VIDEO 6 /* Level 2 class of service for video media */ +#define SIP_DEFAULT_COS_TEXT 5 /* Level 2 class of service for text media (T.140) */ + +static void sip_config_parse_notify(void); + +ast_mutex_t sip_reload_lock; +int sip_reloading = FALSE; /* Flag for avoiding multiple reloads at the same time */ +enum channelreloadreason sip_reload_reason; /* Reason for last reload/load of configuration */ + +struct sip_config sip_config; /* SIP configuration data. in the future we could have multiple of these (per domain, per device group etc) */ +struct ast_config *sip_notify_types = NULL; /* The list of manual NOTIFY types we know how to send */ + +struct ast_jb_conf sip_jb_config; /* Global jitterbuffer configuration */ +struct ast_tls_config sip_tls_config; /* Default TLS connection configuration */ + +/* 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; + +/* 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. SIP debug and recordhistory state will not change */ +int sip_config_parse(enum channelreloadreason reason) +{ + 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 bind_port, timerb_set, timert1_set, subscribe_network_change, auto_sip_domains, subscribe_acl_change, + min_subexpiry_set, max_subexpiry_set; + time_t reload_start, reload_end; + + reload_start = time(0); + + ast_unload_realtime("sippeers"); + memset(&flags, 0, sizeof(flags)); + + if (reason != CHANNEL_MODULE_LOAD && reason != CHANNEL_ACL_RELOAD && + !ast_test_flag(&sip_config.flags[1], SIP_REALTIME_CACHE_PEERS)) { + 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) { + ast_log(LOG_NOTICE, "Unable to load config sip.conf\n"); + return -1; + } else if (config == CONFIG_STATUS_FILEUNCHANGED) { + sip_config_parse_notify(); + 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; + } + + sip_config.contact_acl = ast_free_acl_list(sip_config.contact_acl); + sip_tls_config.enabled = FALSE; /* Default: Disable TLS */ + + /* 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; + + if (reason != CHANNEL_MODULE_LOAD) { + ast_debug(4, "SIP reload started\n"); + + sip_domain_destroy_all(); + sip_auth_realm_destroy_all(); + + sip_registry_destroy_all(); + sip_mwi_subscription_destroy_all(); + + ao2_t_callback(sip_peers, OBJ_NODATA | OBJ_MULTIPLE, sip_peer_set_removed, NULL, "dialog set removed"); + } + + /* Reset certificate handling for TLS and DTLS sessions */ + if (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); /*XXX 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; + + ast_clear_flag(&sip_config.flags[0], AST_FLAGS_ALL); + ast_clear_flag(&sip_config.flags[1], AST_FLAGS_ALL); + ast_clear_flag(&sip_config.flags[2], AST_FLAGS_ALL); + + /* Reset channel settings to default before re-configuring */ + ast_set2_flag(&sip_config.flags[1], SIP_DEFAULT_ALLOW_SUBSCRIBE, SIP_ALLOW_SUBSCRIBE); /* Default for all devices: TRUE */ + ast_set2_flag(&sip_config.flags[1], SIP_DEFAULT_ALLOW_TRANSFER, SIP_ALLOW_TRANSFER); + + ast_set_flag(&sip_config.flags[1], SIP_ALLOW_OVERLAP_YES); /* Default for all devices: Yes */ + ast_set_flag(&sip_config.flags[0], SIP_DTMF_RFC2833); /* Default DTMF setting: RFC2833 */ + ast_set_flag(&sip_config.flags[0], SIP_DIRECT_MEDIA); /* Allow re-invites */ + ast_set_flag(&sip_config.flags[2], SIP_NAT_AUTO_RPORT); /* Default to nat=auto_force_rport */ + + /* Reset IP addresses */ + ast_sockaddr_parse(&sip_config.bind_address, "0.0.0.0:0", 0); + ast_sockaddr_setnull(&sip_our_address); + + /* Free memory for local network address mask */ + ast_free_ha(sip_config.local_address); + sip_config.local_address = NULL; + + ast_sockaddr_setnull(&sip_config.external_address); + ast_sockaddr_setnull(&sip_config.media_address); + ast_sockaddr_setnull(&sip_config.rtp_bind_address); + + memset(&sip_config.proxy, 0, sizeof(sip_config.proxy)); + sip_config.proxy.force = FALSE; /* Don't force proxy usage, use route: headers */ + + sip_config.transports = AST_TRANSPORT_UDP; + sip_config.primary_transport = AST_TRANSPORT_UDP; + sip_config.tcp_enabled = FALSE; + + sip_config.srv_lookup = SIP_DEFAULT_SRV_LOOKUP; + + sip_config.tos_sip = SIP_DEFAULT_TOS_SIP; + sip_config.tos_audio = SIP_DEFAULT_TOS_AUDIO; + sip_config.tos_video = SIP_DEFAULT_TOS_VIDEO; + sip_config.tos_text = SIP_DEFAULT_TOS_TEXT; + sip_config.cos_sip = SIP_DEFAULT_COS_SIP; + sip_config.cos_audio = SIP_DEFAULT_COS_AUDIO; + sip_config.cos_video = SIP_DEFAULT_COS_VIDEO; + sip_config.cos_text = SIP_DEFAULT_COS_TEXT; + + sip_config.external_host[0] = '\0'; /* External host name (for behind NAT DynDNS support) */ + sip_config.external_expiry = 0; /* Expiration for DNS re-issuing */ + sip_config.external_refresh = 10; + sip_config.external_tcp_port = 0; + sip_config.external_tls_port = 0; + + sip_config.send_diversion = SIP_DEFAULT_SEND_DIVERSION; + sip_config.notify_ringing = SIP_DEFAULT_NOTIFY_RINGING; + sip_config.notify_hold = SIP_DEFAULT_NOTIFY_HOLD; /* Keep track of hold status for a peer */ + sip_config.notify_callerid = SIP_DEFAULT_NOTIFY_CALLERID; + + sip_config.direct_rtp_setup = SIP_DEFAULT_DIRECT_RTP_SETUP; /* Experimental feature, disabled by default */ + sip_config.always_auth_reject = SIP_DEFAULT_ALWAYS_AUTH_REJECT; + sip_config.auth_options_requests = SIP_DEFAULT_AUTH_OPTIONS; + sip_config.auth_message_requests = SIP_DEFAULT_AUTH_MESSAGE; + + sip_config.message_context[0] = '\0'; + sip_config.accept_outofcall_message = SIP_DEFAULT_ACCEPT_OUTOFCALL_MESSAGE; + + sip_config.disallowed_methods = 0; + sip_config.contact_acl = NULL; /* Reset the contact ACL */ + + snprintf(sip_config.useragent, sizeof(sip_config.useragent), "%s %s", SIP_DEFAULT_USERAGENT, ast_get_version()); + snprintf(sip_config.sdp_session, sizeof(sip_config.sdp_session), "%s %s", SIP_DEFAULT_SDP_SESSION, ast_get_version()); + snprintf(sip_config.sdp_username, sizeof(sip_config.sdp_username), "%s", SIP_DEFAULT_SIP_USERNAME); + + ast_copy_string(sip_config.realm, S_OR(ast_config_AST_SYSTEM_NAME, SIP_DEFAULT_REALM), sizeof(sip_config.realm)); + sip_config.domains_as_realm = SIP_DEFAULT_DOMAINS_AS_REALM; + + ast_copy_string(sip_config.callerid, SIP_DEFAULT_CALLERID, sizeof(sip_config.callerid)); + ast_copy_string(sip_config.mwi_from, SIP_DEFAULT_MWI_FROM, sizeof(sip_config.mwi_from)); + + sip_config.register_timeout = SIP_DEFAULT_REGISER_TIMEOUT; + sip_config.register_max_attempts = 0; + sip_config.register_retry_on_403 = 0; + + sip_config.pedantic_checking = SIP_DEFAULT_PEDANTIC_CHECKING; + + sip_config.auto_framing = SIP_DEFAULT_AUTO_FRAMING; + sip_config.call_counter = SIP_DEFAULT_CALL_COUNTER; + sip_config.match_auth_username = SIP_DEFAULT_MATCH_AUTH_USERNAME; /* Match auth username if available instead of From: */ + + sip_config.rtp_timeout = SIP_DEFAULT_RTP_TIMEOUT; + sip_config.rtp_hold_timeout = SIP_DEFAULT_RTP_HOLD_TIMEOUT; + sip_config.rtp_keepalive = SIP_DEFAULT_RTP_KEEPALIVE; + + sip_config.realtime_auto_clear = 120; + sip_config.realtime_update_peer = SIP_DEFAULT_REALTIME_UPDATE_PEER; + sip_config.dynamic_exclude_static = SIP_DEFAULT_DYNAMIC_EXCLUDE_STATIC; /* Exclude static peers */ + + sip_config.allow_external_domains = SIP_DEFAULT_ALLOW_EXTERNAL_DOMAINS; /* Allow external invites */ + sip_config.match_external_address_locally = SIP_DEFAULT_MATCH_EXTERNAL_ADDRESS_LOCALLY; + sip_config.premature_media_filter = TRUE; + + /* Peer qualification settings */ + sip_config.qualify_peers = SIP_DEFAULT_QUALIFY_PEERS; + sip_config.qualify_max = SIP_DEFAULT_QUALIFY_MAX; + sip_config.qualify_gap = SIP_DEFAULT_QUALIFY_GAP; + sip_config.qualify_freq = SIP_DEFAULT_QUALIFY_FREQ; + + /* Initialize some reasonable defaults at SIP reload (used both for channel and as default for devices */ + ast_copy_string(sip_config.context, SIP_DEFAULT_CONTEXT, sizeof(sip_config.context)); + sip_config.subscribe_context[0] = '\0'; + sip_config.max_forwards = SIP_DEFAULT_MAX_FORWARDS; + + sip_config.language[0] = '\0'; + sip_config.from_domain[0] = '\0'; + sip_config.from_domain_port = 0; + + sip_config.keepalive = SIP_DEFAULT_KEEPALIVE; + sip_config.zone[0] = '\0'; + sip_config.max_call_bitrate = SIP_DEFAULT_MAXCALLBITRATE; + + ast_copy_string(sip_config.moh_interpret, SIP_DEFAULT_MOH_INTERPRET, sizeof(sip_config.moh_interpret)); + ast_copy_string(sip_config.moh_suggest, SIP_DEFAULT_MOH_SUGGEST, sizeof(sip_config.moh_suggest)); + ast_copy_string(sip_config.mwi_exten, SIP_DEFAULT_MWI_EXTEN, sizeof(sip_config.mwi_exten)); + + ast_copy_string(sip_config.rtp_engine, SIP_DEFAULT_RTP_ENGINE, sizeof(sip_config.rtp_engine)); + ast_copy_string(sip_config.parkinglot, DEFAULT_PARKINGLOT, sizeof(sip_config.parkinglot)); + + /* Debugging settings, always default to off */ + sip_config.dump_history = FALSE; + sip_config.record_history = FALSE; + + sip_config.relax_dtmf = FALSE; + sip_config.auth_failure_events = FALSE; + + sip_config.timer_t1 = SIP_DEFAULT_TIMER_T1; + sip_config.timer_b = SIP_DEFAULT_TIMER_T1 * 64; + sip_config.t1_min = SIP_DEFAULT_T1_MIN; + + sip_config.shrink_callerid = SIP_DEFAULT_SHRINK_CALLERID; + sip_config.tcp_auth_limit = SIP_DEFAULT_TCP_AUTH_LIMIT; + sip_config.tcp_auth_timeout = SIP_DEFAULT_TCP_AUTH_TIMEOUT; + + sip_config.min_expiry = SIP_DEFAULT_MIN_EXPIRY; + sip_config.max_expiry = SIP_DEFAULT_MAX_EXPIRY; + sip_config.default_expiry = SIP_DEFAULT_EXPIRY; + sip_config.min_subscribe_expiry = SIP_DEFAULT_MIN_EXPIRY; + sip_config.max_subscribe_expiry = SIP_DEFAULT_MAX_EXPIRY; + sip_config.mwi_expiry = SIP_DEFAULT_MWI_EXPIRY; + + /* Session-Timers */ + sip_config.stimer_mode = SIP_STIMER_MODE_ACCEPT; + sip_config.stimer_refresher = SIP_STIMER_REFRESHER_UAS; + sip_config.stimer_min_se = SIP_DEFAULT_STIMER_MIN_SE; + sip_config.stimer_max_se = SIP_DEFAULT_STIMER_MAX_SE; + + ast_clear_flag(&sip_config.flags[1], SIP_FAX_DETECT); + ast_clear_flag(&sip_config.flags[1], SIP_VIDEO_SUPPORT | SIP_VIDEO_SUPPORT_ALWAYS); + ast_clear_flag(&sip_config.flags[1], SIP_TEXT_SUPPORT); + ast_clear_flag(&sip_config.flags[1], SIP_IGNORE_SDP_VERSION); + + sip_debug &= ~SIP_DEBUG_CONFIG; + + timerb_set = FALSE; + timert1_set = FALSE; + + subscribe_network_change = TRUE; + subscribe_acl_change = FALSE; + auto_sip_domains = FALSE; + + min_subexpiry_set = FALSE; + max_subexpiry_set = FALSE; + + bind_port = 0; + ast_sockaddr_copy(&old_bind_address, &sip_config.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) { + /* handle jb conf */ + if (!ast_jb_read_conf(&sip_jb_config, variable->name, variable->value)) { + continue; + } + + /* handle tls conf, don't allow setting of tlsverifyclient as it isn't supported by chan_sip */ + if (!strcasecmp(variable->name, "tlsverifyclient")) { + ast_log(LOG_WARNING, "Ignoring %s at line %d\n", variable->name, variable->lineno); + continue; + } else if (!ast_tls_read_conf(&sip_tls_config, &sip_tls_session, variable->name, variable->value)) { + continue; + } + + if (!strcasecmp(variable->name, "nat")) { + char *nat, *option; + + nat = ast_strdupa(variable->value); + + while ((option = strsep(&nat, ","))) { + if (ast_false(option)) { + ast_clear_flag(&sip_config.flags[0], SIP_NAT_FORCE_RPORT); + ast_clear_flag(&sip_config.flags[1], SIP_SYMMETRIC_RTP); + ast_clear_flag(&sip_config.flags[2], SIP_NAT_AUTO_RPORT | SIP_NAT_AUTO_COMEDIA); + + break; /* It doesn't make sense to have no + something else */ + } else if (!strcasecmp(option, "yes")) { + ast_log(LOG_WARNING, + "Option %s of '%s' is no longer supported at line %d, use nat=force_rport,comedia instead\n", + variable->name, option, variable->lineno); + } else if (!strcasecmp(option, "force_rport") && !ast_test_flag(&sip_config.flags[2], SIP_NAT_AUTO_RPORT)) { + ast_set_flag(&sip_config.flags[0], SIP_NAT_FORCE_RPORT); + } else if (!strcasecmp(option, "comedia") && !ast_test_flag(&sip_config.flags[2], SIP_NAT_AUTO_COMEDIA)) { + ast_set_flag(&sip_config.flags[1], SIP_SYMMETRIC_RTP); + } else if (!strcasecmp(option, "auto_force_rport")) { + ast_set_flag(&sip_config.flags[2], SIP_NAT_AUTO_RPORT); + /* In case someone did something dumb like nat=force_rport,auto_force_rport */ + ast_clear_flag(&sip_config.flags[0], SIP_NAT_FORCE_RPORT); + } else if (!strcasecmp(option, "auto_comedia")) { + ast_set_flag(&sip_config.flags[2], SIP_NAT_AUTO_COMEDIA); + /* In case someone did something dumb like nat=comedia,auto_comedia*/ + ast_clear_flag(&sip_config.flags[1], SIP_SYMMETRIC_RTP); + } + } + } else if (!strcasecmp(variable->name, "context")) { + ast_copy_string(sip_config.context, variable->value, sizeof(sip_config.context)); + } else if (!strcasecmp(variable->name, "subscribecontext")) { + ast_copy_string(sip_config.subscribe_context, variable->value, sizeof(sip_config.subscribe_context)); + } else if (!strcasecmp(variable->name, "callcounter")) { + sip_config.call_counter = ast_true(variable->value); + } 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, "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, "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")) { + /* Field cannot contain spaces */ + if (!strchr(variable->value, ' ')) { + ast_copy_string(sip_config.sdp_username, variable->value, sizeof(sip_config.sdp_username)); + } else { + ast_log(LOG_WARNING, "'%s' must not contain spaces at line %dt\n", variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "allowtransfer")) { + ast_set2_flag(&sip_config.flags[1], ast_true(variable->value), SIP_ALLOW_TRANSFER); + } else if (!strcasecmp(variable->name, "rtcachefriends")) { + ast_set2_flag(&sip_config.flags[1], ast_true(variable->value), SIP_REALTIME_CACHE_PEERS); + } else if (!strcasecmp(variable->name, "rtsavesysname")) { + sip_config.realtime_save_sysname = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "rtsavepath")) { + sip_config.realtime_save_path = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "rtupdate")) { + sip_config.realtime_update_peer = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "ignoreregexpire")) { + sip_config.realtime_ignore_expiry = ast_true(variable->value); + } 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_DEFAULT | PARSE_IN_RANGE, + &sip_config.timer_t1, SIP_DEFAULT_TIMER_T1, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %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_DEFAULT | PARSE_IN_RANGE, + &sip_config.timer_b, sip_config.timer_t1 * 64, 500, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } else { + timerb_set = TRUE; + } + } else if (!strcasecmp(variable->name, "t1_min")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.t1_min, SIP_DEFAULT_T1_MIN, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "transport")) { + char *transport, *option; + + sip_config.transports = 0; + sip_config.primary_transport = 0; + + transport = ast_strdupa(variable->value); + + while ((option = strsep(&transport, ","))) { + option = ast_skip_blanks(option); + + if (!strncasecmp(option, "udp", 3)) { + sip_config.transports |= AST_TRANSPORT_UDP; + } else if (!strncasecmp(option, "tcp", 3)) { + sip_config.transports |= AST_TRANSPORT_TCP; + } else if (!strncasecmp(option, "tls", 3)) { + sip_config.transports |= AST_TRANSPORT_TLS; + } else { + ast_log(LOG_NOTICE, "Invalid %s option '%s' at line %d, if no other %s is specified, udp will be used\n", + variable->name, option, variable->lineno, variable->name); + } + + if (sip_config.primary_transport == 0) { + sip_config.primary_transport = sip_config.transports; + } + } + } else if (!strcasecmp(variable->name, "tcpenable")) { + if (!ast_false(variable->value)) { + ast_debug(2, "Enabling TCP socket for listening\n"); + sip_config.tcp_enabled = TRUE; + } + } else if (!strcasecmp(variable->name, "tcpbindaddr")) { + if (ast_parse_arg(variable->value, PARSE_ADDR, &sip_tcp_session.local_address)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + + ast_debug(2, "Setting TCP socket address to %s\n", ast_sockaddr_stringify(&sip_tcp_session.local_address)); + } else if (!strcasecmp(variable->name, "dynamic_exclude_static") || !strcasecmp(variable->name, "dynamic_excludes_static")) { + sip_config.dynamic_exclude_static = ast_true(variable->value); + } 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, &sip_config.contact_acl, &ha_error, &subscribe_acl_change); + + if (ha_error) { + ast_log(LOG_ERROR, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + return -1; + } + } else if (!strcasecmp(variable->name, "rtautoclear")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.realtime_auto_clear, 0, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + + ast_set2_flag(&sip_config.flags[1], + sip_config.realtime_auto_clear || ast_true(variable->value), SIP_REALTIME_AUTO_CLEAR); + } else if (!strcasecmp(variable->name, "usereqphone")) { + ast_set2_flag(&sip_config.flags[0], ast_true(variable->value), SIP_USER_EQ_PHONE); + } else if (!strcasecmp(variable->name, "prematuremedia")) { + sip_config.premature_media_filter = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "relaxdtmf")) { + sip_config.relax_dtmf = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "vmexten")) { + ast_copy_string(sip_config.mwi_exten, variable->value, sizeof(sip_config.mwi_exten)); + } else if (!strcasecmp(variable->name, "rtptimeout")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.rtp_timeout, SIP_DEFAULT_RTP_TIMEOUT, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %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_DEFAULT | PARSE_IN_RANGE, + &sip_config.rtp_hold_timeout, SIP_DEFAULT_RTP_HOLD_TIMEOUT, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %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_DEFAULT | PARSE_IN_RANGE, + &sip_config.rtp_keepalive, SIP_DEFAULT_RTP_KEEPALIVE, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, 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, "notifymimetype")) { + ast_log(LOG_WARNING, "Opton '%s' is no longer supported at line %d\n", variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "directrtpsetup")) { + sip_config.direct_rtp_setup = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "notifyringing")) { + sip_config.notify_ringing = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "notifyhold")) { + sip_config.notify_hold = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "notifycid")) { + if (!strcasecmp(variable->value, "ignore-context")) { + sip_config.notify_callerid = SIP_NOTIFY_CALLERID_IGNORE_CONTEXT; + } else if (ast_true(variable->value)) { + sip_config.notify_callerid = SIP_NOTIFY_CALLERID_ENABLED; + } else { + sip_config.notify_callerid = SIP_NOTIFY_CALLERID_DISABLED; + } + } else if (!strcasecmp(variable->name, "alwaysauthreject")) { + sip_config.always_auth_reject = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "auth_options_requests")) { + sip_config.auth_options_requests = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "auth_message_requests")) { + sip_config.auth_message_requests = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "accept_outofcall_message")) { + sip_config.accept_outofcall_message = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "outofcall_message_context")) { + ast_copy_string(sip_config.message_context, variable->value, sizeof(sip_config.message_context)); + } 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 *zone; + + if (!(zone = ast_get_indication_zone(variable->value))) { + ast_log(LOG_ERROR, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } else { + ast_tone_zone_unref(zone); + ast_copy_string(sip_config.zone, variable->value, sizeof(sip_config.zone)); + } + } else if (!strcasecmp(variable->name, "language")) { + ast_copy_string(sip_config.language, variable->value, sizeof(sip_config.language)); + } else if (!strcasecmp(variable->name, "regcontext")) { + ast_log(LOG_WARNING, "Opton '%s' is no longer supported at line %d\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, "Opton '%s' is no longer supported at line %d (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "send_diversion")) { + sip_config.send_diversion = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "callerid")) { + ast_copy_string(sip_config.callerid, variable->value, sizeof(sip_config.callerid)); + } else if (!strcasecmp(variable->name, "mwi_from")) { + ast_copy_string(sip_config.mwi_from, variable->value, sizeof(sip_config.mwi_from)); + } else if (!strcasecmp(variable->name, "fromdomain")) { + char *from_domain_port; + + ast_copy_string(sip_config.from_domain, variable->value, sizeof(sip_config.from_domain)); + + if ((from_domain_port = strchr(sip_config.from_domain, ':'))) { + *from_domain_port++ = '\0'; + + if (ast_parse_arg(from_domain_port, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.from_domain_port, SIP_STANDARD_PORT, 0, USHRT_MAX)) { + ast_log(LOG_NOTICE, "'%s' is not a valid port number for fromdomain\n", from_domain_port); + } + } else { + sip_config.from_domain_port = SIP_STANDARD_PORT; + } + } else if (!strcasecmp(variable->name, "outboundproxy")) { + struct sip_proxy *proxy; + + if (!(proxy = sip_proxy_build(variable->value, variable->lineno, &sip_config.proxy))) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + continue; + } + } 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, "match_auth_username")) { + sip_config.match_auth_username = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "srvlookup")) { + sip_config.srv_lookup = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "pedantic")) { + sip_config.pedantic_checking = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "maxexpiry")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.max_expiry, SIP_DEFAULT_MAX_EXPIRY, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "minexpiry")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.min_expiry, SIP_DEFAULT_MIN_EXPIRY, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "defaultexpiry")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.default_expiry, SIP_DEFAULT_EXPIRY, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "submaxexpiry")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.max_subscribe_expiry, SIP_DEFAULT_MAX_EXPIRY, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } else { + max_subexpiry_set = TRUE; + } + } else if (!strcasecmp(variable->name, "subminexpiry")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.min_subscribe_expiry, SIP_DEFAULT_MIN_EXPIRY, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } else { + min_subexpiry_set = TRUE; + } + } else if (!strcasecmp(variable->name, "mwiexpiry")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.mwi_expiry, SIP_DEFAULT_MWI_EXPIRY, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %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_DEFAULT | PARSE_IN_RANGE, + &sip_config.tcp_auth_timeout, SIP_DEFAULT_TCP_AUTH_TIMEOUT, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %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_DEFAULT | PARSE_IN_RANGE, + &sip_config.tcp_auth_limit, SIP_DEFAULT_TCP_AUTH_LIMIT, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "sip_debug")) { + if (ast_true(variable->value)) { + sip_debug |= SIP_DEBUG_CONFIG; + } + } else if (!strcasecmp(variable->name, "dumphistory")) { + sip_config.dump_history = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "recordhistory")) { + sip_config.record_history = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "registertimeout")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.register_timeout, SIP_DEFAULT_REGISER_TIMEOUT, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %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_DEFAULT | PARSE_IN_RANGE, + &sip_config.register_max_attempts, 1, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "register_retry_403")) { + sip_config.register_retry_on_403 = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "bindaddr") || !strcasecmp(variable->name, "udpbindaddr")) { + if (ast_parse_arg(variable->value, PARSE_ADDR, &sip_config.bind_address)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "bindport")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &bind_port, SIP_STANDARD_PORT, 0, USHRT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "localnet")) { + struct ast_ha *local_address; + int ha_error; + + ha_error = 0; + + if (!(local_address = ast_append_ha("d", variable->value, sip_config.local_address, &ha_error))) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } else { + sip_config.local_address = local_address; + } + + if (ha_error) { + ast_log(LOG_ERROR, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "mediaaddr")) { + if (ast_parse_arg(variable->value, PARSE_ADDR, &sip_config.media_address)) + ast_log(LOG_WARNING, "Invalid %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 %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "externaddr") || !strcasecmp(variable->name, "externip")) { + if (ast_parse_arg(variable->value, PARSE_ADDR, &sip_config.external_address)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + + sip_config.external_expiry = 0; + } else if (!strcasecmp(variable->name, "externhost")) { + 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 %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + + sip_config.external_expiry = time(NULL); + } else if (!strcasecmp(variable->name, "externrefresh")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.external_refresh, 10, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "externtcpport")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.external_tcp_port, SIP_STANDARD_PORT, 0, USHRT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d, must be a positive integer between 1 and 65535\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "externtlsport")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.external_tls_port, SIP_STANDARD_TLS_PORT, 0, USHRT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d, must be a positive integer between 1 and 65535\n", + variable->name, variable->value, variable->lineno); + } + } 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 %s codec '%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 %s codec '%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "preferred_codec_only")) { + ast_set2_flag(&sip_config.flags[1], ast_true(variable->value), SIP_PREFERRED_CODEC_ONLY); + } else if (!strcasecmp(variable->name, "autoframing")) { + sip_config.auto_framing = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "allowexternaldomains")) { + sip_config.allow_external_domains = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "autodomain")) { + auto_sip_domains = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "domain")) { + sip_domain_build(variable->value, variable->lineno, SIP_DOMAIN_CONFIG); + } else if (!strcasecmp(variable->name, "register")) { + sip_registry_build(variable->value, variable->lineno); + } else if (!strcasecmp(variable->name, "mwi")) { + sip_mwi_subscription_build(variable->value, variable->lineno); + } else if (!strcasecmp(variable->name, "tos_sip")) { + if (ast_str2tos(variable->value, &sip_config.tos_sip)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "tos_audio")) { + if (ast_str2tos(variable->value, &sip_config.tos_audio)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "tos_video")) { + if (ast_str2tos(variable->value, &sip_config.tos_video)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "tos_text")) { + if (ast_str2tos(variable->value, &sip_config.tos_text)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "cos_sip")) { + if (ast_str2cos(variable->value, &sip_config.cos_sip)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "cos_audio")) { + if (ast_str2cos(variable->value, &sip_config.cos_audio)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "cos_video")) { + if (ast_str2cos(variable->value, &sip_config.cos_video)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "cos_text")) { + if (ast_str2cos(variable->value, &sip_config.cos_text)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "qualify")) { + if (!strcasecmp(variable->value, "no")) { + sip_config.qualify_max = 0; + } else if (!strcasecmp(variable->value, "yes")) { + sip_config.qualify_max = SIP_DEFAULT_QUALIFY_MAX; + } else if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.qualify_max, 0, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %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, "keepalive")) { + if (!strcasecmp(variable->value, "no")) { + sip_config.keepalive = 0; + } else if (!strcasecmp(variable->value, "yes")) { + sip_config.keepalive = SIP_DEFAULT_KEEPALIVE_INTERVAL; + } else if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.keepalive, 0, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Keep alive default should be 'yes', 'no', or a number of milliseconds at line %d\n", + variable->lineno); + } + } else if (!strcasecmp(variable->name, "qualifyfreq")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.qualify_freq, SIP_DEFAULT_QUALIFY_FREQ, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } else { + sip_config.qualify_freq *= 1000; /* ms */ + } + } else if (!strcasecmp(variable->name, "authfailureevents")) { + sip_config.auth_failure_events = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "maxcallbitrate")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.max_call_bitrate, SIP_DEFAULT_MAXCALLBITRATE, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "matchexternaddrlocally") || !strcasecmp(variable->name, "matchexterniplocally")) { + sip_config.match_external_address_locally = ast_true(variable->value); + } else if (!strcasecmp(variable->name, "session-timers")) { + if (!strcasecmp(variable->value, "accept")) { + sip_config.stimer_mode = SIP_STIMER_MODE_ACCEPT; + } else if (!strcasecmp(variable->value, "refuse")) { + sip_config.stimer_mode = SIP_STIMER_MODE_REFUSE; + } else if (!strcasecmp(variable->value, "originate")) { + sip_config.stimer_mode = SIP_STIMER_MODE_ORIGINATE; + } else { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + sip_config.stimer_mode = SIP_STIMER_MODE_ACCEPT; + } + } else if (!strcasecmp(variable->name, "session-expires")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.stimer_max_se, SIP_DEFAULT_STIMER_MAX_SE, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + sip_config.stimer_max_se = SIP_DEFAULT_STIMER_MAX_SE; + } + } else if (!strcasecmp(variable->name, "session-minse")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.stimer_min_se, SIP_DEFAULT_STIMER_MIN_SE, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "session-refresher")) { + if (!strcasecmp(variable->value, "uac")) { + sip_config.stimer_refresher = SIP_STIMER_REFRESHER_UAC; + } else if (!strcasecmp(variable->value, "uas")) { + sip_config.stimer_refresher = SIP_STIMER_REFRESHER_UAS; + } else { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + sip_config.stimer_refresher = SIP_STIMER_REFRESHER_UAS; + } + } else if (!strcasecmp(variable->name, "storesipcause")) { + ast_log(LOG_WARNING, "Option '%s' has been removed at line %d, use HANGUPCAUSE instead\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "qualifygap")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.qualify_gap, SIP_DEFAULT_QUALIFY_GAP, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %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_DEFAULT | PARSE_IN_RANGE, + &sip_config.qualify_peers, SIP_DEFAULT_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, "disallowed_methods")) { + sip_parse_methods(&sip_config.disallowed_methods, variable->value); + } else if (!strcasecmp(variable->name, "shrinkcallerid")) { + if (ast_true(variable->value)) { + sip_config.shrink_callerid = TRUE; + } else if (ast_false(variable->value)) { + sip_config.shrink_callerid = FALSE; + } else { + ast_log(LOG_WARNING, "Invalid %s '%s' is not valid at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "use_q850_reason")) { + ast_set2_flag(&sip_config.flags[1], ast_true(variable->value), SIP_Q850_REASON); + } else if (!strcasecmp(variable->name, "maxforwards")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &sip_config.max_forwards, SIP_DEFAULT_MAX_FORWARDS, 1, 255)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "subscribe_network_change_event")) { + if (ast_true(variable->value)) { + subscribe_network_change = TRUE; + } else if (ast_false(variable->value)) { + subscribe_network_change = FALSE; + } else { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", variable->name, variable->value, 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, "icesupport")) { + ast_set2_flag(&sip_config.flags[2], ast_true(variable->value), SIP_ICE_SUPPORT); + } 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, "parkinglot")) { + ast_copy_string(sip_config.parkinglot, variable->value, sizeof(sip_config.parkinglot)); + } else if (!strcasecmp(variable->name, "refer_addheaders")) { + 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, "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 (always disabled)\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "sendrpid") || !strcasecmp(variable->name, "trustrpid")) { + ast_log(LOG_WARNING, "Option '%s' can not be set globally anymore at line %d, set on peer instead\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "supportpath")) { + ast_log(LOG_WARNING, "Option '%s' can not be set globally anymore at line %d, set on peer instead\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, "trust_id_outbound")) { + ast_log(LOG_WARNING, "Option '%s' can not be set globally anymore at line %d, set on peer instead\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "g726nonstandard")) { + ast_log(LOG_WARNING, "Option '%s' can not be set globally anymore at line %d, set on peer instead\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "dtmfmode")) { + ast_log(LOG_WARNING, "Option '%s' can not be set globally anymore at line %d, set on peer instead\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "directmedia") || !strcasecmp(variable->name, "canreinvite")) { + ast_log(LOG_WARNING, "Option '%s' can not be set globally anymore at line %d, set on peer instead\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "promiscredir")) { + ast_log(LOG_WARNING, "Option '%s' can not be set globally anymore at line %d, set on peer instead\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "videosupport") || !strcasecmp(variable->name, "textsupport")) { + ast_log(LOG_WARNING, "Option '%s' can not be set globally anymore at line %d, set on peer instead\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "allowoverlap") || !strcasecmp(variable->name, "allowsubscribe")) { + ast_log(LOG_WARNING, "Option '%s' can not be set globally anymore at line %d, set on peer instead\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "ignoresdpversion")) { + ast_log(LOG_WARNING, "Option '%s' can not be set globally anymore at line %d, set on peer instead\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "faxdetect")) { + ast_log(LOG_WARNING, "Option '%s' can not be set globally anymore at line %d, set on peer instead\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "rfc2833compensate")) { + ast_log(LOG_WARNING, "Option '%s' can not be set globally anymore at line %d, set on peer instead\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "rtcp_mux")) { + ast_log(LOG_WARNING, "Option '%s' can not be set globally anymore at line %d, set on peer instead\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "dndbusy") || !strcasecmp(variable->name, "huntgroup_default")) { + ast_log(LOG_WARNING, "Option '%s' can not be set globally anymore at line %d, set on peer instead\n", + variable->name, variable->lineno); + } else if (!strcasecmp(variable->name, "cisco_usecallmanager")) { + ast_log(LOG_WARNING, "Option '%s' can not be set globally anymore at line %d, set on peer instead\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' can not be set globally anymore at line %d, set on peer instead\n", + variable->name, variable->lineno); + } + } + + /* For backwards compatibility the corresponding registration timer value is used if subscription timer value isn't + * set by configuration */ + if (!min_subexpiry_set) { + sip_config.min_subscribe_expiry = sip_config.min_expiry; + } + + if (!max_subexpiry_set) { + sip_config.max_subscribe_expiry = sip_config.max_expiry; + } + + if (subscribe_network_change) { + sip_network_change_subscribe(); + } else { + sip_network_change_unsubscribe(); + } + + if (sip_config.timer_t1 < sip_config.t1_min) { + ast_log(LOG_WARNING, "Timer 't1min' (%d) cannot be greater than 't1timer' (%d). Setting 't1timer' to the value of 't1min'\n", + sip_config.t1_min, sip_config.timer_t1); + sip_config.timer_t1 = sip_config.t1_min; + } + + if (sip_config.timer_b < sip_config.timer_t1 * 64) { + if (timerb_set && timert1_set) { + ast_log(LOG_WARNING, "Timer B has been set lower than recommended (%d < 64 * timert1=%d)\n", + sip_config.timer_b, sip_config.timer_t1); + } else if (timerb_set) { + if ((sip_config.timer_t1 = sip_config.timer_b / 64) < sip_config.t1_min) { + ast_log(LOG_WARNING, "Timer B has been set lower than recommended (%d < 64 * timert1=%d)\n", + sip_config.timer_b, sip_config.timer_t1); + sip_config.timer_t1 = sip_config.t1_min; + sip_config.timer_b = sip_config.timer_t1 * 64; + } + } else { + sip_config.timer_b = sip_config.timer_t1 * 64; + } + } + + if (!sip_config.allow_external_domains && AST_LIST_EMPTY(&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 not or badly configured, set default transports */ + if (!sip_config.tcp_enabled && (sip_config.transports & AST_TRANSPORT_TCP)) { + ast_log(LOG_WARNING, "Cannot use 'tcp' transport with tcpenable=no. Removing from available transports\n"); + sip_config.primary_transport &= ~AST_TRANSPORT_TCP; + sip_config.transports &= ~AST_TRANSPORT_TCP; + } + + if (!sip_tls_config.enabled && (sip_config.transports & AST_TRANSPORT_TLS)) { + ast_log(LOG_WARNING, "Cannot use 'tls' transport with tlsenable=no. Removing from available transports\n"); + sip_config.primary_transport &= ~AST_TRANSPORT_TLS; + sip_config.transports &= ~AST_TRANSPORT_TLS; + } + + if (!sip_config.transports) { + ast_log(LOG_WARNING, "No valid transports available, falling back to 'udp'\n"); + sip_config.transports = sip_config.primary_transport = AST_TRANSPORT_UDP; + } else if (!sip_config.primary_transport) { + ast_log(LOG_WARNING, "No valid default transport. Selecting 'udp' as default\n"); + sip_config.primary_transport = AST_TRANSPORT_UDP; + } + + /* Build list of authentication to various SIP realms, i.e. service providers */ + for (variable = ast_variable_browse(config, "authentication"); variable; variable = variable->next) { + /* Format for authentication is auth = username:password@realm */ + if (!strcasecmp(variable->name, "auth")) { + sip_auth_realm_build(&sip_auth_realms, variable->value, variable->lineno); + } + } + + if (bind_port) { + if (ast_sockaddr_port(&sip_config.bind_address)) { + ast_log(LOG_WARNING, "Option 'bindport' is also specified in 'bindaddr', using %d\n", bind_port); + } + + ast_sockaddr_set_port(&sip_config.bind_address, bind_port); + } + + if (!ast_sockaddr_port(&sip_config.bind_address)) { + ast_sockaddr_set_port(&sip_config.bind_address, SIP_STANDARD_PORT); + } + + /* Set UDP address and open socket */ + ast_sockaddr_copy(&sip_our_address, &sip_config.bind_address); + + if (ast_find_ourip(&sip_our_address, &sip_config.bind_address, 0)) { + ast_log(LOG_WARNING, "Unable to get own IP address, SIP disabled\n"); + ast_config_destroy(config); + + return 0; + } + + ast_mutex_lock(&sip_netsock_lock); + + if (sip_socket_fd > -1 && ast_sockaddr_cmp(&old_bind_address, &sip_config.bind_address)) { + close(sip_socket_fd); + sip_socket_fd = -1; + } + + if (sip_socket_fd < 0) { + sip_socket_fd = socket(ast_sockaddr_is_ipv6(&sip_config.bind_address) ? AF_INET6 : AF_INET, SOCK_DGRAM, 0); + + if (sip_socket_fd < 0) { + ast_log(LOG_WARNING, "Unable to create SIP socket: %s\n", strerror(errno)); + + ast_config_destroy(config); + ast_mutex_unlock(&sip_netsock_lock); + + return -1; + } else { + /* Allow SIP clients on the same host to access us: */ + int reuse = TRUE; + + setsockopt(sip_socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); + ast_enable_packet_fragmentation(sip_socket_fd); + + if (ast_bind(sip_socket_fd, &sip_config.bind_address) < 0) { + ast_log(LOG_WARNING, "Failed to bind to %s: %s\n", + ast_sockaddr_stringify(&sip_config.bind_address), strerror(errno)); + + close(sip_socket_fd); + sip_socket_fd = -1; + } else { + ast_verb(2, "SIP Listening on %s\n", ast_sockaddr_stringify(&sip_config.bind_address)); + ast_set_qos(sip_socket_fd, sip_config.tos_sip, sip_config.cos_sip, "SIP"); + } + } + } else { + ast_set_qos(sip_socket_fd, sip_config.tos_sip, sip_config.cos_sip, "SIP"); + } + + 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.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 >= 0) { + 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 SIP socket: %s\n", strerror(errno)); + } + + ast_set_qos(sip_tcp_session.accept_fd, sip_config.tos_sip, sip_config.cos_sip, "SIP"); + } + } + + /* Start TLS server if needed */ + memcpy(sip_tls_session.tls_cfg, &sip_tls_config, sizeof(sip_tls_config)); + + if (ast_ssl_setup(sip_tls_session.tls_cfg)) { + if (ast_sockaddr_isnull(&sip_tls_session.local_address)) { + ast_sockaddr_copy(&sip_tls_session.local_address, &sip_config.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, "TLS Server start failed. Not listening on TLS socket\n"); + sip_tls_session.tls_cfg = NULL; + } + + if (sip_tls_session.accept_fd >= 0) { + 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 SIP socket: %s\n", strerror(errno)); + sip_tls_session.tls_cfg = NULL; + + } + ast_set_qos(sip_tls_session.accept_fd, sip_config.tos_sip, sip_config.cos_sip, "SIP"); + } + } 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") || !strcasecmp(category, "authentication")) { + continue; + } + + if (!(type = ast_variable_retrieve(config, category, "type"))) { + ast_log(LOG_WARNING, "Section '%s' lacks type\n", category); + continue; + } else if (!strcasecmp(type, "peer") || !strcasecmp(type, "friend")) { + if ((peer = sip_peer_build(category, ast_variable_browse(config, category), FALSE, FALSE))) { + int global_nat, peer_nat; + + global_nat = !!ast_test_flag(&sip_config.flags[0], SIP_NAT_FORCE_RPORT); + peer_nat = !!ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT); + + if (reason == CHANNEL_MODULE_LOAD && peer_nat != global_nat) { + 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_t_link(sip_peers, peer, "link peer"); + + if (!ast_sockaddr_isnull(&peer->address)) { + ao2_t_link(sip_peers_by_address, peer, "unlink peer"); + } + + ao2_t_cleanup(peer, "drop peer"); + } + } else { + ast_log(LOG_WARNING, "Unknown section type '%s' for '%s'\n", type, category); + } + } + + /* Add default domains - host name, IP address and IP:port only do this if user added any sip domain with "localdomains" + * In order to *not* break backwards compatibility. Some phones address us at IP only, some with additional port number */ + if (auto_sip_domains) { + char host[MAXHOSTNAMELEN]; + + /* First our default IP address */ + if (!ast_sockaddr_isnull(&sip_config.bind_address) && !ast_sockaddr_is_any(&sip_config.bind_address)) { + sip_domain_build(ast_sockaddr_stringify_addr(&sip_config.bind_address), 0, SIP_DOMAIN_AUTO); + } else if (!ast_sockaddr_isnull(&sip_our_address) && !ast_sockaddr_is_any(&sip_our_address)) { + /* Our internal IP address, if configured */ + sip_domain_build(ast_sockaddr_stringify_addr(&sip_our_address), 0, SIP_DOMAIN_AUTO); + } else { + ast_log(LOG_NOTICE, "Unable to add wildcard IP address to domain list, please add IP address to domain manually\n"); + } + + /* If TCP is running on a different IP than UDP, then add it too */ + if (!ast_sockaddr_isnull(&sip_tcp_session.local_address) && + ast_sockaddr_cmp_addr(&sip_config.bind_address, &sip_tcp_session.local_address)) { + sip_domain_build(ast_sockaddr_stringify_addr(&sip_tcp_session.local_address), 0, SIP_DOMAIN_AUTO); + } + + /* If TLS is running on a different IP than UDP and TCP, then add that too */ + if (!ast_sockaddr_isnull(&sip_tls_session.local_address) && + ast_sockaddr_cmp_addr(&sip_config.bind_address, &sip_tls_session.local_address) && + ast_sockaddr_cmp_addr(&sip_tcp_session.local_address, &sip_tls_session.local_address)) { + sip_domain_build(ast_sockaddr_stringify_addr(&sip_tls_session.local_address), 0, SIP_DOMAIN_AUTO); + } + + /* Our extern IP address, if configured */ + if (!ast_sockaddr_isnull(&sip_config.external_address)) { + sip_domain_build(ast_sockaddr_stringify_addr(&sip_config.external_address), 0, SIP_DOMAIN_AUTO); + } + + /* Extern host name (NAT traversal support) */ + if (!ast_strlen_zero(sip_config.external_host)) { + sip_domain_build(sip_config.external_host, 0, SIP_DOMAIN_AUTO); + } + + /* Our host name */ + if (!gethostname(host, sizeof(host))) { + sip_domain_build(host, 0, SIP_DOMAIN_AUTO); + } + } + + /* Release configuration from memory */ + ast_config_destroy(config); + sip_config_parse_notify(); + + reload_end = time(0); + ast_debug(4, "SIP reload config done (%ds)\n", (int) (reload_end - reload_start)); + + /* If an ACL change subscription is needed and doesn't exist, we need one. */ + if (subscribe_acl_change) { + sip_acl_change_subscribe(); + } + + return 0; +} + +static void sip_config_parse_notify(void) +{ + struct ast_flags flags; + + /* Load the list of manual NOTIFY types to support */ + if (sip_notify_types) { + ast_config_destroy(sip_notify_types); + } + + memset(&flags, 0, sizeof(flags)); + + if ((sip_notify_types = ast_config_load("sip_notify.conf", flags)) == CONFIG_STATUS_FILEINVALID) { + ast_log(LOG_ERROR, "Contents of sip_notify.conf are invalid and cannot be parsed\n"); + sip_notify_types = NULL; + } +} + +void sip_config_reload(void) +{ + ast_mutex_lock(&sip_reload_lock); + + if (sip_reloading) { + ast_verb(3, "Previous SIP reload not yet done\n"); + } else { + sip_reloading = TRUE; + sip_reload_reason = CHANNEL_CLI_RELOAD; + } + + ast_mutex_unlock(&sip_reload_lock); + sip_monitor_restart(); +} diff -durN asterisk-22.3.0.orig/channels/sip/dialog.c asterisk-22.3.0/channels/sip/dialog.c --- asterisk-22.3.0.orig/channels/sip/dialog.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/dialog.c 2025-04-17 11:11:22.794703729 +1200 @@ -0,0 +1,4447 @@ +/* + * 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/netsock.h" +#include "include/route.h" +#include "include/response.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/auth_realms.h" +#include "include/domain.h" +#include "include/peers.h" +#include "include/registry.h" +#include "include/mwi_subscriptions.h" +#include "include/dialog.h" +#include "include/sdp.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/chan_tech.h" +#include "include/manager.h" +#include "include/fax.h" + +#define SIP_MAX_HISTORY 50 /* Max entires in the history list for a sip_dialog */ + +/* Results from the parse_register() function */ +enum { + SIP_PARSE_REGISTER_FAILED = -2, + SIP_PARSE_REGISTER_DENIED = -1, + SIP_PARSE_REGISTER_UPDATE = 0, + SIP_PARSE_REGISTER_QUERY = 1, +}; + +static void sip_dialog_destroy(void *data); +static int sip_dialog_delete_sched(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_rtp_check_timeout(struct sip_dialog *dialog); +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 int sip_dialog_check_authorization(struct sip_dialog *dialog, struct sip_request *request, const char *username, + const char *secret, const char *md5secret, const char *uri, int reliable); +static int __sip_dialog_check_peer_authorization(struct sip_dialog *dialog, char *name, struct sip_request *request, char *uri, + struct sip_peer **auth_peer, int reliable); + +static int sip_dialog_parse_register(struct sip_dialog *dialog, struct sip_peer *peer, struct sip_request *request, + int *address_changed); + +static int __sip_dialog_stop_need_reinvite(const void *data); +static int __sip_dialog_stop_reinvite(const void *data); + +static int sip_dialog_provisonal_keepalive(const void *data); +static int __sip_dialog_sched_provisional_keepalive(const void *data); +static int __sip_dialog_cancel_provisional_keepalive(const void *data); + +static void sip_history_dump(struct sip_dialog *dialog); + +/* Here we implement the container for dialogs (sip_dialog), defining generic wrapper functions to ease the transition + * from the current implementation (a single linked list) to a different container. In addition to a reference to the + * container, we need functions to lock/unlock the container and individual items, and functions to add/remove references + * to the individual items. */ +struct ao2_container *sip_dialogs; + +/* Here we implement the container for dialogs which are in the sip_dialog_need_destroy state to iterate only through + * the dialogs unlink them instead of iterate through all dialogs */ +struct ao2_container *sip_dialogs_need_destroy; + +/* 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_rtp_check; + +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 use_global_nat, const int method, + struct sip_request *request, ast_callid logger_callid) +{ + struct sip_dialog *dialog; + + if (!(dialog = ao2_t_alloc_options(sizeof(*dialog), sip_dialog_destroy, AO2_ALLOC_OPT_LOCK_MUTEX, "alloc dialog"))) { + return NULL; + } + + if (ast_string_field_init(dialog, 512)) { + ao2_t_ref(dialog, -1, "drop dialog"); + return NULL; + } + + if (logger_callid) { + dialog->logger_callid = logger_callid; + } + + if (socket && !ast_sockaddr_isnull(&socket->address)) { + ast_sockaddr_copy(&dialog->address, &socket->address); + sip_socket_copy(&dialog->socket, socket); + + sip_get_our_address(dialog->socket.transport, &dialog->address, &dialog->our_address); + } else { + ast_sockaddr_copy(&dialog->our_address, &sip_our_address); + + /* Set transport to 0 otherwise sip_dialog_build_from_peer will fail */ + sip_socket_set_transport(&dialog->socket, 0); + } + + 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_t_ref(dialog, -1, "drop dialog"); + + 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 (request) { + /* 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(request->via_branch) && !strncmp(request->via_branch, SIP_MAGIC_COOKIE, 7)) { + ast_string_field_set(dialog, via_sent_by, request->via_sent_by); + ast_string_field_set(dialog, via_branch, request->via_branch); + } + + /* Store initial incoming CSeq, validation must take place before dialog creation in sip_request_find_dialog */ + dialog->initial_incoming_cseq = request->cseq; + } + + dialog->method = method; + dialog->initial_request.method = SIP_METHOD_UNKNOWN; + + dialog->invite_sched_id = -1; + dialog->need_reinvite_sched_id = -1; + dialog->reinvite_sched_id = -1; + dialog->auto_destruct_sched_id = -1; + + dialog->request_queue_sched_id = -1; + dialog->provisional_keepalive_sched_id = -1; + + dialog->t38_sched_id = -1; + + dialog->subscribe_events = SIP_SUBSCRIBE_NONE; + dialog->extension_state_id = -1; + + dialog->remote_sdp_version = -1; + dialog->sdp_changed = TRUE; + + ast_copy_string(dialog->zone, sip_config.zone, sizeof(dialog->zone)); + dialog->max_forwards = sip_config.max_forwards; + + if (method != SIP_METHOD_OPTIONS) { /* Qualify peers has it's own system */ + 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) */ + } + + /* Copy global flags to this dialog at setup. */ + ast_copy_flags(&dialog->flags[0], &sip_config.flags[0], SIP_FLAGS0_MASK); + ast_copy_flags(&dialog->flags[1], &sip_config.flags[1], SIP_FLAGS1_MASK); + ast_copy_flags(&dialog->flags[2], &sip_config.flags[2], SIP_FLAGS2_MASK); + + dialog->record_history = sip_config.record_history; + + dialog->allowed_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 = SIP_INITIAL_CSEQ; + + ast_string_field_build(dialog, branch, "%s%08x", SIP_MAGIC_COOKIE, (unsigned int) ast_random()); + sip_dialog_build_local_tag(dialog); + + if (sip_methods[method].need_rtp) { + dialog->max_call_bitrate = sip_config.max_call_bitrate; + dialog->auto_framing = sip_config.auto_framing; + } + + if (use_global_nat && socket) { + /* Setup NAT structure according to global settings if we have an address */ + ast_sockaddr_copy(&dialog->received_address, &socket->address); + + sip_dialog_check_via(dialog, request); + sip_dialog_set_rtp_nat(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_string_field_set(dialog, moh_interpret, sip_config.moh_interpret); + ast_string_field_set(dialog, moh_suggest, sip_config.moh_suggest); + ast_format_cap_append_from_cap(dialog->format_cap, sip_config.format_cap, AST_MEDIA_TYPE_UNKNOWN); + + if (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833 || ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + dialog->non_format_cap |= AST_RTP_DTMF; + } + + ast_string_field_set(dialog, context, sip_config.context); + ast_string_field_set(dialog, parkinglot, sip_config.parkinglot); + ast_string_field_set(dialog, rtp_engine, sip_config.rtp_engine); + + AST_LIST_HEAD_INIT_NOLOCK(&dialog->request_queue); + AST_LIST_HEAD_INIT_NOLOCK(&dialog->sdp_media); + AST_LIST_HEAD_INIT_NOLOCK(&dialog->history); + AST_LIST_HEAD_INIT_NOLOCK(&dialog->route.hops); + + /* Add to active dialog list */ + ao2_t_link(sip_dialogs, dialog, "link dialog"); + + ast_debug(1, "Allocating new %s for '%s' %s\n", + sip_methods[method].name, dialog->call_id, dialog->audio_rtp ? "with RTP" : "no RTP"); + + return dialog; +} + +/* ao2 destructor for SIP dialog structure */ +static void sip_dialog_destroy(void *data) +{ + struct sip_dialog *dialog; + struct sip_request *request; + struct sip_history *history; + + dialog = (struct sip_dialog *) data; + + ast_debug(3, "Destroying '%s'\n", dialog->call_id); + + if (sip_dialog_debug(dialog)) { + ast_verb(3, "Really destroying %s '%s'\n", sip_methods[dialog->method].name, dialog->call_id); + } + + if (ast_test_flag(&dialog->flags[0], SIP_CALL_COUNTER_INUSE) || ast_test_flag(&dialog->flags[1], SIP_ONHOLD)) { + sip_dialog_update_call_counter(dialog, SIP_DEC_CALL_LIMIT); + 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) { + if (dialog->peer->qualify_dialog == dialog) { + ao2_t_cleanup(dialog->peer->qualify_dialog, "drop dialog"); + dialog->peer->qualify_dialog = NULL; + } + + /* Remove link from peer to subscription of MWI */ + if (dialog->peer->mwi_dialog == dialog) { + ao2_t_cleanup(dialog->peer->mwi_dialog, "drop dialog"); + dialog->peer->mwi_dialog = NULL; + } + + /* Remove link from peer to subscription for Feature Events */ + if (dialog->peer->feature_events_dialog) { + ao2_t_cleanup(dialog->peer->feature_events_dialog, "drop dialog"); + dialog->peer->feature_events_dialog = NULL; + } + + ao2_t_cleanup(dialog->peer, "drop peer"); + dialog->peer = NULL; + } + + if (dialog->registry) { + if (dialog->registry->dialog == dialog) { + ao2_t_cleanup(dialog->registry->dialog, "drop registry"); + dialog->registry->dialog = NULL; + } + + ao2_t_replace(dialog->registry, NULL, "drop registry"); + } + + if (dialog->mwi) { + dialog->mwi->dialog = NULL; + dialog->mwi = NULL; + } + + if (sip_config.dump_history) { + sip_history_dump(dialog); + } + + sip_proxy_set(dialog, NULL); + + /* Destroy Session-Timers if allocated */ + ast_free(dialog->stimer); + dialog->stimer = NULL; + + /* Free RTP and SRTP instances */ + sip_dialog_destroy_rtp(dialog); + + if (dialog->udptl) { + ast_udptl_destroy(dialog->udptl); + dialog->udptl = NULL; + } + + sip_route_clear(&dialog->route); + sip_request_destroy(&dialog->initial_request); + + /* Clear history */ + while ((history = AST_LIST_REMOVE_HEAD(&dialog->history, next))) { + ast_free(history); + } + + dialog->history_count = 0; + + while ((request = AST_LIST_REMOVE_HEAD(&dialog->request_queue, next))) { + ast_free(request); + } + + sip_sdp_media_destroy(dialog); + + if (dialog->channel_variables) { + ast_variables_destroy(dialog->channel_variables); + dialog->channel_variables = NULL; + } + + if (dialog->notify_headers) { + ast_variables_destroy(dialog->notify_headers); + dialog->notify_headers = NULL; + } + + if (dialog->message_headers) { + ast_variables_destroy(dialog->message_headers); + dialog->message_headers = NULL; + } + + if (dialog->secure_video_rtp) { + ast_sdp_srtp_destroy(dialog->secure_video_rtp); + dialog->secure_video_rtp = NULL; + } + + if (dialog->direct_media_acl) { + dialog->direct_media_acl = ast_free_acl_list(dialog->direct_media_acl); + } + + ast_string_field_free_memory(dialog); + + if (dialog->socket.tcptls_session) { + ao2_ref(dialog->socket.tcptls_session, -1); + dialog->socket.tcptls_session = NULL; + } + + if (dialog->auth_realms) { + ao2_t_ref(dialog->auth_realms, -1, "drop auth_realms"); + dialog->auth_realms = NULL; + } + + dialog->named_callgroups = ast_unref_namedgroups(dialog->named_callgroups); + dialog->named_pickupgroups = ast_unref_namedgroups(dialog->named_pickupgroups); + + 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); + + if (dialog->last_device_state_info) { + ao2_ref(dialog->last_device_state_info, -1); + dialog->last_device_state_info = NULL; + } +} + +/* Initiate a call in the SIP channel */ +struct ast_channel *sip_dialog_alloc_channel(struct sip_dialog *dialog, int state, const char *peer_name, + const struct ast_assigned_ids *assigned_ids, const struct ast_channel *requestor_channel, ast_callid callid) +{ + static unsigned int next_channel; /* 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 NULL; + } + + /* 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->accountcode, + dialog->exten, dialog->context, assigned_ids, requestor_channel, dialog->amaflags, dialog->peer->endpoint, + "SIP/%s-%08x", S_OR(peer_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->accountcode, + dialog->exten, dialog->context, assigned_ids, requestor_channel, dialog->amaflags, + "SIP/%s-%08x", S_OR(peer_name, dialog->from_domain), + (unsigned int) ast_atomic_fetchadd_int((int *) &next_channel, +1)); + } + + if (!channel) { + ast_log(LOG_WARNING, "Unable to allocate channel\n"); + + ao2_ref(format_cap, -1); + ao2_lock(dialog); + + return NULL; + } + + 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_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))) { /* get the best audio format */ + int framing = ast_format_cap_get_format_framing(outgoing_format_cap, 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 NULL; + } + } + + 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_test_flag(&dialog->flags[1], SIP_VIDEO_SUPPORT_ALWAYS)) { + need_video = TRUE; + } else if (ast_format_cap_count(dialog->outgoing_format_cap)) { + need_video = ast_format_cap_has_type(dialog->outgoing_format_cap, AST_MEDIA_TYPE_VIDEO); /* Outbound call */ + } else { + need_video = ast_format_cap_has_type(dialog->joint_format_cap, AST_MEDIA_TYPE_VIDEO); /* Inbound call */ + } + + 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)) { + need_text = ast_format_cap_has_type(dialog->outgoing_format_cap, AST_MEDIA_TYPE_TEXT); /* Outbound call */ + } else { + need_text = ast_format_cap_has_type(dialog->joint_format_cap, AST_MEDIA_TYPE_TEXT); /* Inbound call */ + } + } + + 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); + + if (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_INBAND || ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + if (dialog->audio_rtp) { + ast_rtp_instance_dtmf_mode_set(dialog->audio_rtp, AST_RTP_DTMF_MODE_INBAND); + } + } else if (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833) { + if (dialog->audio_rtp) { + ast_rtp_instance_dtmf_mode_set(dialog->audio_rtp, AST_RTP_DTMF_MODE_RFC2833); + } + } + + /* 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 (ast_test_flag(&dialog->flags[2], SIP_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 (ast_test_flag(&dialog->flags[2], SIP_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_t_bump(dialog, "bump dialog")); + + ast_channel_callgroup_set(channel, dialog->callgroup); + ast_channel_pickupgroup_set(channel, dialog->pickupgroup); + + ast_channel_named_callgroups_set(channel, dialog->named_callgroups); + ast_channel_named_pickupgroups_set(channel, dialog->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->parkinglot)) { + ast_channel_parkinglot_set(channel, dialog->parkinglot); + } + + if (!ast_strlen_zero(dialog->accountcode)) { + ast_channel_accountcode_set(channel, dialog->accountcode); + } + + if (dialog->amaflags) { + ast_channel_amaflags_set(channel, dialog->amaflags); + } + + if (!ast_strlen_zero(dialog->language)) { + ast_channel_language_set(channel, dialog->language); + } + + if (!ast_strlen_zero(dialog->zone)) { + struct ast_tone_zone *zone; + + if (!(zone = ast_get_indication_zone(dialog->zone))) { + ast_log(LOG_ERROR, "Unknown country code '%s' for tonezone\n", dialog->zone); + } + + ast_channel_zone_set(channel, zone); + } + + sip_dialog_set_channel(dialog, channel); + sip_module_ref(); + + ast_channel_context_set(channel, dialog->context); + + /* 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->exten); + + ao2_unlock(dialog); + ast_channel_unlock(channel); + + if (!ast_exists_extension(NULL, dialog->context, dialog->exten, 1, dialog->caller_number)) { + ast_uri_decode(exten, ast_uri_sip_user); + } + + ast_channel_lock(channel); + ao2_lock(dialog); + + 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->exten) && strcmp(dialog->exten, "s")) { + ast_channel_dialed(channel)->number.str = ast_strdup(dialog->exten); + } + + if (!ast_strlen_zero(dialog->uri)) { + pbx_builtin_setvar_helper(channel, "SIPURI", dialog->uri); + } + + if (!ast_strlen_zero(dialog->domain)) { + pbx_builtin_setvar_helper(channel, "SIPDOMAIN", dialog->domain); + } + + if (!ast_strlen_zero(dialog->call_id)) { + pbx_builtin_setvar_helper(channel, "SIPCALLID", dialog->call_id); + } + + 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))); + } + + if (dialog->record_history) { + sip_history_append(dialog, "NewChan", "Channel %s from %s", ast_channel_name(channel), dialog->call_id); + } + + ast_channel_stage_snapshot_done(channel); + + return channel; +} + +/* 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) +{ + struct ast_channel *channel; + + ao2_t_bump(dialog, "bump dialog"); + + ao2_t_unlink(sip_dialogs, dialog, "unlink dialog"); + ao2_t_unlink(sip_dialogs_need_destroy, dialog, "unlink dialog"); + ao2_t_unlink(sip_dialogs_rtp_check, dialog, "unlink dialog"); + + /* Unlink us from the channel if we have one */ + if ((channel = sip_dialog_lock_with_channel(dialog))) { + ast_debug(1, "Detaching from channel %s\n", ast_channel_name(channel)); + + ao2_t_cleanup(ast_channel_tech_pvt(channel), "drop channel"); + ast_channel_tech_pvt_set(channel, NULL); + + ast_channel_unlock(channel); + ast_channel_unref(channel); + + sip_dialog_set_channel(dialog, NULL); + } + + ao2_unlock(dialog); + + if (dialog->registry) { + if (dialog->registry->dialog == dialog) { + ao2_t_cleanup(dialog->registry->dialog, "drop dialog"); + dialog->registry->dialog = NULL; + } + + ao2_t_replace(dialog->registry, NULL, "drop registry"); + } + + /* Remove link from peer to subscription of MWI */ + if (dialog->peer && dialog->peer->mwi_dialog == dialog) { + ao2_t_cleanup(dialog->peer->mwi_dialog, "drop dialog"); + dialog->peer->mwi_dialog = NULL; + } + + /* Remove link from peer to subscription for Feature Events */ + if (dialog->peer && dialog->peer->feature_events_dialog == dialog) { + ao2_t_cleanup(dialog->peer->feature_events_dialog, "drop dialog"); + dialog->peer->feature_events_dialog = NULL; + } + + if (dialog->peer && dialog->peer->qualify_dialog == dialog) { + ao2_t_cleanup(dialog->peer->qualify_dialog, "drop dialog"); + dialog->peer->qualify_dialog = NULL; + } + + if (dialog->extension_state_id != -1) { + ast_extension_state_del(dialog->extension_state_id, sip_extension_state_event); + dialog->extension_state_id = -1; + } + + if (dialog->conference) { + ao2_ref(dialog->conference, -1); + dialog->conference = NULL; + } + + if (dialog->record_outgoing_dialog) { + ao2_t_cleanup(dialog->record_outgoing_dialog, "drop dialog"); + dialog->record_outgoing_dialog = NULL; + } + + if (dialog->record_incoming_dialog) { + ao2_t_cleanup(dialog->record_incoming_dialog, "drop dialog"); + dialog->record_incoming_dialog = NULL; + } + + ao2_t_bump(dialog, "bump dialog"); + + if (ast_sched_add(sip_sched_context, 0, sip_dialog_delete_sched, dialog) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(dialog, "drop dialog"); + } + + ao2_t_cleanup(dialog, "drop dialog"); +} + +/* Run by the sched thread. */ +static int sip_dialog_delete_sched(const void *data) +{ + struct sip_dialog *dialog; + struct sip_packet *packet; + + dialog = (struct sip_dialog *) data; + + /* remove all current packets in this dialog */ + ao2_lock(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_t_ref(packet, -1, "drop packet")); + ao2_t_ref(packet, -1, "drop packet"); + } + + ao2_unlock(dialog); + + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->need_reinvite_sched_id, ao2_t_cleanup(dialog, "drop dialog")); + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->invite_sched_id, ao2_t_cleanup(dialog, "drop dialog")); + + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->reinvite_sched_id, ao2_t_cleanup(dialog, "drop dialog")); + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->auto_destruct_sched_id, ao2_t_cleanup(dialog, "drop dialog")); + + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->request_queue_sched_id, ao2_t_cleanup(dialog, "drop dialog")); + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->provisional_keepalive_sched_id, ao2_t_cleanup(dialog, "drop dialog")); + + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->t38_sched_id, ao2_t_cleanup(dialog, "drop dialog")); + + if (dialog->stimer) { + dialog->stimer->active = FALSE; + + sip_stimer_delete_sched(dialog); + } + + ao2_t_cleanup(dialog, "drop dialog"); + + 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_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_methods[dialog->method].name, 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_methods[dialog->method].name, dialog->call_id); + + ao2_unlock(dialog); + return 0; + } + + ao2_unlock(dialog); + /* No, the unlink should handle this. the CMP_MATCH will unlink this dialog from the dialog hash table */ + sip_dialog_unlink(dialog); + + return 0; /* the unlink_all should unlink this from the table, so.... 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); + + if (dialog->auto_destruct_sched_id != -1) { + sip_history_append(dialog, "CancelDestroy", ""); + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->auto_destruct_sched_id, ao2_t_cleanup(dialog, "drop dialog")); + } + + ao2_unlock(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + return 0; +} + +void sip_dialog_cancel_destroy(struct sip_dialog *dialog) +{ + if (dialog->final_destruction_scheduled) { + return; + } + + ao2_t_bump(dialog, "bump dialog"); + + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_cancel_destroy, dialog) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(dialog, "drop dialog"); + ast_log(LOG_WARNING, "Unable to cancel destruction. Expect bad things\n"); + } +} + +/* 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); + + if (dialog->auto_destruct_sched_id != -1) { + sip_history_append(dialog, "CancelDestroy", ""); + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->auto_destruct_sched_id, ao2_t_cleanup(dialog, "drop dialog")); + } + + if (dialog->record_history) { + sip_history_append(dialog, "SchedDestroy", "%dms", when); + } + + ao2_t_bump(dialog, "bump dialog"); + + if ((dialog->auto_destruct_sched_id = ast_sched_add(sip_sched_context, when, sip_dialog_auto_destruct, dialog)) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(dialog, "drop dialog"); + } + + if (dialog->stimer) { + sip_stimer_stop(dialog); + } + + ao2_unlock(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + return 0; +} + +int sip_dialog_sched_destroy(struct sip_dialog *dialog, int when) +{ + struct sip_sched_data *sched_data; + + if (dialog->final_destruction_scheduled) { + return 0; /* already set final destruction */ + } + + if (when < 0) { + if (dialog->timer_t1 == 0) { + dialog->timer_t1 = sip_config.timer_t1; /* Set timer T1 if not set (RFC 3261) */ + } + + if (dialog->timer_b == 0) { + dialog->timer_b = sip_config.timer_b; /* Set timer B if not set (RFC 3261) */ + } + + when = dialog->timer_t1 * 64; + } + + if (sip_dialog_debug(dialog)) { + ast_verb(3, "Scheduling destruction of %s '%s' in %dms\n", + sip_methods[dialog->method].name, dialog->call_id, when); + } + + if (!(sched_data = ast_malloc(sizeof(*sched_data)))) { + /* Uh Oh. Expect bad behavior. */ + return -1; + } + + sched_data->dialog = dialog; + sched_data->when = when; + + ao2_t_bump(dialog, "bump dialog"); + + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_sched_destroy, sched_data) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(dialog, "drop dialog"); + ast_free(sched_data); + + return -1; + } + + return 0; +} + +/* Kill a SIP dialog (called only by the scheduler), The scheduler has a reference to this dialog when p->autokillid != -1, + * and we are called using that reference. So if the event is not rescheduled, we need to call ao2_t_cleanup(). */ +int sip_dialog_auto_destruct(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_events && + (dialog->subscribe_events == SIP_SUBSCRIBE_DIALOG_INFO_XML || dialog->subscribe_events == SIP_SUBSCRIBE_PIDF_XML)) { + struct ast_state_cb_info state_info = {.exten_state = AST_EXTENSION_DEACTIVATED}; + + sip_send_notify_with_extension_state(dialog, &state_info, TRUE); /* Send last notification */ + dialog->subscribe_events = SIP_SUBSCRIBE_NONE; + + sip_history_append(dialog, "Subscribestatus", "timeout"); + 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); + sip_history_append(dialog, "ReliableXmit", "timeout"); + + if (dialog->ongoing_reinvite || dialog->method == SIP_METHOD_CANCEL || dialog->method == 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 dialog '%s' with owner %s in place (Method: %s). Rescheduling destruction for 10000 ms\n", + dialog->call_id, ast_channel_name(channel), sip_methods[dialog->method].name); + + 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_destruct_sched_id = -1; + + if (dialog->refer_status && !dialog->already_gone) { + ast_debug(3, "Finally hanging up channel after transfer of '%s'\n", dialog->call_id); + sip_history_append(dialog, "ReferBYE", "Sending BYE on transferer call leg %s", dialog->call_id); + + sip_dialog_stop_rtp(dialog); + sip_send_bye(dialog); + + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + ao2_unlock(dialog); + } else { + ast_debug(3, "Auto-destroying '%s'\n", dialog->call_id); + sip_history_append(dialog, "AutoDestroy", "%s", dialog->call_id); + + ao2_unlock(dialog); + sip_dialog_unlink(dialog); /* once it's unlinked and unrefd everywhere, it'll be freed automagically */ + } + + ao2_t_cleanup(dialog, "drop dialog"); + + 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) { + /* XXX fails on possible deadlock */ + if (!ast_channel_trylock(dialog->channel)) { + sip_history_append(dialog, "Cong", "Auto-congesting (timer)"); + 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, SIP_DEFAULT_TIMEOUT); + } + + ao2_unlock(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + return 0; +} + +/* Check RTP Timeout on dialogs */ +int sip_dialog_rtp_check(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 || dialog->video_rtp) { + res = sip_dialog_rtp_check_timeout(dialog); + } else { + /* Dialog has no active RTP or VRTP. unlink it from sip_dialogs_rtp_check. */ + res = CMP_MATCH; + } + + ao2_unlock(dialog); + + return res; +} + +static int sip_dialog_rtp_check_timeout(struct sip_dialog *dialog) +{ + int timeout, hold_timeout, keepalive; + time_t now; + + if (!dialog->audio_rtp) { + /* We have no RTP. Since we don't do much with video RTP for now, stop checking this dialog. */ + return CMP_MATCH; + } + + /* If we have no active owner, no need to check timers */ + if (!dialog->channel) { + return CMP_MATCH; + } + + /* If the call is redirected outside Asterisk, no need to check timers */ + if (!ast_sockaddr_isnull(&dialog->audio_redirect_address)) { + return CMP_MATCH; + } + + /* If the call is involved in a T38 fax session do not check RTP timeout */ + if (dialog->t38_state == SIP_T38_ENABLED) { + return CMP_MATCH; + } + /* If the call is not in UP state return for later check. */ + if (ast_channel_state(dialog->channel) != AST_STATE_UP) { + return 0; + } + + /* Store these values locally to avoid multiple function calls */ + timeout = ast_rtp_instance_get_timeout(dialog->audio_rtp); + hold_timeout = ast_rtp_instance_get_hold_timeout(dialog->audio_rtp); + keepalive = ast_rtp_instance_get_keepalive(dialog->audio_rtp); + + /* If we have no timers set, return now */ + if (!keepalive && !timeout && !hold_timeout) { + return CMP_MATCH; + } + + time(&now); + + /* Check AUDIO RTP keepalives */ + if (dialog->last_rtp_sent && keepalive && (now > dialog->last_rtp_sent + keepalive)) { + /* Need to send an empty RTP packet */ + dialog->last_rtp_sent = time(NULL); + ast_rtp_instance_sendcng(dialog->audio_rtp, 0); + } + + /* Check AUDIO RTP timers */ + if (dialog->last_rtp_received && (timeout || hold_timeout) && (now > dialog->last_rtp_received + timeout)) { + if (!ast_test_flag(&dialog->flags[1], SIP_ONHOLD) || (hold_timeout && (now > dialog->last_rtp_received + hold_timeout))) { + /* Needs a hangup */ + if (timeout) { + if (!dialog->channel || ast_channel_trylock(dialog->channel)) { + /* Don't block, just try again later. If there was no owner, the call is dead already. */ + return 0; + } + + ast_log(LOG_NOTICE, "Disconnecting '%s' due to lack of RTP activity in %ld seconds\n", + ast_channel_name(dialog->channel), (long) (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); + } + + /* finally unlink the dialog from sip_dialogs_rtp_check. */ + return CMP_MATCH; + } + } + } + + return 0; +} + +/* 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 (ast_test_flag(&dialog->flags[0], SIP_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_send_cancel(dialog); + + /* If the cancel occurred on an initial invite, cancel the pending BYE */ + if (!ast_test_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED)) { + ast_clear_flag(&dialog->flags[0], SIP_PENDING_BYE | SIP_NEED_REINVITE); + } + + /* Actually don't destroy us yet, wait for the 487 on our original INVITE, but do set an autodestruct 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); + } + + /* Perhaps there is an SD change INVITE outstanding */ + sip_send_bye(dialog); + ast_clear_flag(&dialog->flags[0], SIP_PENDING_BYE | SIP_NEED_REINVITE); + } + + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + } else if (ast_test_flag(&dialog->flags[0], SIP_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_send_reinvite_with_sdp(dialog, FALSE, dialog->t38_state == SIP_T38_LOCAL_REINVITE); + ast_clear_flag(&dialog->flags[0], SIP_NEED_REINVITE); + } + } +} + +/* 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_t_cleanup(dialog, "drop dialog"); + + return 0; +} + +void sip_dialog_sched_check_pending(struct sip_dialog *dialog) +{ + ao2_t_bump(dialog, "bump dialog"); + + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_sched_check_pending, dialog) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(dialog, "drop dialog"); + } +} + +int sip_dialog_debug(struct sip_dialog *dialog) +{ + const struct ast_sockaddr *address = sip_dialog_get_address(dialog); + + if (sip_debug && + (ast_sockaddr_isnull(&sip_debug_address) || !ast_sockaddr_cmp_addr(&sip_debug_address, address))) { + return TRUE; + } + + return FALSE; +} + +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->received_address, &from_dialog->received_address); + + ast_copy_flags(&to_dialog->flags[0], &from_dialog->flags[0], SIP_FLAGS0_MASK); + ast_copy_flags(&to_dialog->flags[1], &from_dialog->flags[1], SIP_FLAGS1_MASK); + ast_copy_flags(&to_dialog->flags[2], &from_dialog->flags[2], SIP_FLAGS2_MASK); + + sip_get_our_address(to_dialog->socket.transport, &to_dialog->address, &to_dialog->our_address); + + ast_string_field_set(to_dialog, to_host, from_dialog->to_host); + to_dialog->port_in_uri = from_dialog->port_in_uri; + to_dialog->from_domain_port = from_dialog->from_domain_port; + + ast_string_field_set(to_dialog, full_contact, from_dialog->full_contact); + ast_string_field_set(to_dialog, username, from_dialog->username); + ast_string_field_set(to_dialog, from_user, from_dialog->from_user); + ast_string_field_set(to_dialog, from_name, from_dialog->from_name); + + 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) { + /* done */ + break; + } + + /* 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_t_find(sip_dialogs, call_id, OBJ_SEARCH_KEY, "bump dialog"))) { + SCOPED_LOCK(lock, dialog, ao2_lock, ao2_unlock); + + if (strcmp(dialog->local_tag, local_tag) || strcmp(dialog->remote_tag, remote_tag)) { + ao2_t_ref(dialog, -1, "drop dialog"); + return NULL; + } + } + + 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; + + 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_t_find(sip_dialogs, (char *) call_id, OBJ_SEARCH_KEY, "bump dialog"))) { + return NULL; + } + + if (sip_config.pedantic_checking) { + int from_mismatch, to_mismatch; + SCOPED_LOCK(lock, dialog, ao2_lock, ao2_unlock); + + /* 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 && ast_test_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED)) || to_mismatch) { + if (from_mismatch) { + ast_debug(4, "Matched %s on '%s' pedantic From: tag check fails; From: tag='%s' Remote tag='%s'\n", + dialog->outgoing_call ? "outgoing": "incoming", + dialog->call_id, S_OR(from_tag, ""), dialog->remote_tag); + } + + if (to_mismatch) { + ast_debug(4, "Matched %s on '%s' pedantic To: tag check fails; To: tag='%s' Local tag='%s'\n", + dialog->outgoing_call ? "outgoing": "incoming", + dialog->call_id, S_OR(to_tag, ""), dialog->local_tag); + } + + ao2_t_cleanup(dialog, "drop dialog"); + return NULL; + } + + if (to_tag) { + ast_debug(4, "Matched %s call, Remote tag='%s', Local tag='%s'\n", + dialog->outgoing_call ? "outgoing": "incoming", dialog->remote_tag, dialog->local_tag); + } + + if (dialog->channel) { + *channel = ast_channel_ref(dialog->channel); + } else { + *channel = NULL; + } + } + + 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 (!sip_methods[dialog->method].need_rtp) { + return 0; + } + + 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.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(dialog->rtp_engine, sip_sched_context, &address, NULL))) { + return -1; + } + + if (!ast_test_flag(&dialog->flags[2], SIP_ICE_SUPPORT) && (ice = ast_rtp_instance_get_ice(dialog->audio_rtp))) { + ice->stop(dialog->audio_rtp); + } + + if (ast_test_flag(&dialog->flags[1], SIP_VIDEO_SUPPORT_ALWAYS) || + (ast_test_flag(&dialog->flags[1], SIP_VIDEO_SUPPORT) && ast_format_cap_has_type(dialog->format_cap, AST_MEDIA_TYPE_VIDEO))) { + if (!(dialog->video_rtp = ast_rtp_instance_new(dialog->rtp_engine, sip_sched_context, &address, NULL))) { + return -1; + } + + if (!ast_test_flag(&dialog->flags[2], SIP_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 (ast_test_flag(&dialog->flags[1], SIP_TEXT_SUPPORT)) { + if (!(dialog->text_rtp = ast_rtp_instance_new(dialog->rtp_engine, sip_sched_context, &address, NULL))) { + return -1; + } + + if (!ast_test_flag(&dialog->flags[2], SIP_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_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, + ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833); + ast_rtp_instance_set_prop(dialog->audio_rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, + ast_test_flag(&dialog->flags[0], SIP_RFC2833_COMPENSATE)); + + ast_rtp_instance_set_qos(dialog->audio_rtp, sip_config.tos_audio, sip_config.cos_audio, "SIP RTP"); + sip_dialog_set_rtp_nat(dialog); + + return 0; +} + +/* Cleanup the RTP and SRTP portions of a dialog. This procedure excludes vsrtp as it is initialized differently. */ +static void sip_dialog_destroy_rtp(struct sip_dialog *dialog) +{ + if (dialog->audio_rtp) { + ast_rtp_instance_destroy(dialog->audio_rtp); + dialog->audio_rtp = NULL; + } + + if (dialog->video_rtp) { + ast_rtp_instance_destroy(dialog->video_rtp); + dialog->video_rtp = NULL; + } + + if (dialog->text_rtp) { + 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_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); + } +} + +/* 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) +{ + struct sip_auth_realm_head *auth_realms; + + /* 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_peer_check_transport(peer, dialog->socket.transport)) { + return -1; + } + + if (!(ast_sockaddr_isnull(&peer->address) && ast_sockaddr_isnull(&peer->default_address)) && + (!peer->qualify_max || (peer->last_qualify >= 0 && peer->last_qualify <= peer->qualify_max))) { + ast_sockaddr_copy(&dialog->address, ast_sockaddr_isnull(&peer->address) ? &peer->default_address : &peer->address); + ast_sockaddr_copy(&dialog->received_address, &dialog->address); + ast_sockaddr_copy(&dialog->socket.address, &dialog->address); + } else { + return -1; + } + + sip_socket_copy(&dialog->socket, &peer->socket); + + /* Get flags directly from peer only as they are needed using dialog->peer */ + ast_copy_flags(&dialog->flags[0], &peer->flags[0], SIP_FLAGS0_MASK); + ast_copy_flags(&dialog->flags[1], &peer->flags[1], SIP_FLAGS1_MASK); + ast_copy_flags(&dialog->flags[2], &peer->flags[2], SIP_FLAGS2_MASK); + + /* 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); + } + + /* XXX: get fields directly from peer only as they are needed using dialog->peer */ + ast_string_field_set(dialog, peer_name, peer->name); + ast_string_field_set(dialog, auth_name, peer->username); + ast_string_field_set(dialog, username, peer->username); + + ast_string_field_set(dialog, peer_secret, peer->secret); + ast_string_field_set(dialog, peer_md5secret, peer->md5secret); + + ast_string_field_set(dialog, moh_suggest, peer->moh_suggest); + ast_string_field_set(dialog, moh_interpret, peer->moh_interpret); + + ast_string_field_set(dialog, to_host, peer->host); + ast_string_field_set(dialog, full_contact, peer->full_contact); + + ast_string_field_set(dialog, context, peer->context); + + 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); + + ast_string_field_set(dialog, mwi_from, peer->mwi_from); + ast_string_field_set(dialog, accountcode, peer->accountcode); + + if (!ast_strlen_zero(peer->parkinglot)) { + ast_string_field_set(dialog, parkinglot, peer->parkinglot); + } + + ast_string_field_set(dialog, rtp_engine, peer->rtp_engine); + sip_proxy_set(dialog, sip_proxy_get(dialog, peer)); + + dialog->callgroup = peer->callgroup; + dialog->pickupgroup = peer->pickupgroup; + + ast_unref_namedgroups(dialog->named_callgroups); + ast_unref_namedgroups(dialog->named_pickupgroups); + + dialog->named_callgroups = ast_ref_namedgroups(peer->named_callgroups); + dialog->named_pickupgroups = ast_ref_namedgroups(peer->named_pickupgroups); + + ast_copy_string(dialog->zone, peer->zone, sizeof(dialog->zone)); + dialog->joint_non_format_cap = dialog->non_format_cap; + 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_address(sip_route_first_uri(&dialog->route), &dialog->address); + } + + dialog->amaflags = peer->amaflags; + + dialog->rtp_timeout = peer->rtp_timeout; + dialog->rtp_hold_timeout = peer->rtp_hold_timeout; + dialog->rtp_keepalive = peer->rtp_keepalive; + + if (sip_dialog_alloc_rtp(dialog)) { + return -1; + } + + if (dialog->audio_rtp) { /* Audio */ + ast_rtp_instance_set_prop(dialog->audio_rtp, AST_RTP_PROPERTY_DTMF, + ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833); + ast_rtp_instance_set_prop(dialog->audio_rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, + ast_test_flag(&dialog->flags[0], SIP_RFC2833_COMPENSATE)); + + /* Set Frame packetization */ + dialog->auto_framing = peer->auto_framing; + ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(dialog->audio_rtp), ast_format_cap_get_framing(dialog->format_cap)); + } + + /* Update dialog authorization credentials */ + ao2_lock(peer); + + if ((auth_realms = peer->auth_realms)) { + ao2_t_ref(auth_realms, +1, "bump auth_realms"); + } + + ao2_unlock(peer); + ao2_lock(dialog); + + if (dialog->auth_realms) { + ao2_t_ref(dialog->auth_realms, -1, "drop auth_realms"); + } + + dialog->auth_realms = auth_realms; + ao2_unlock(dialog); + + dialog->max_call_bitrate = peer->max_call_bitrate; + dialog->t38_max_datagram = peer->t38_max_datagram; + + dialog->disallowed_methods = peer->disallowed_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 (!ast_strlen_zero(peer->from_user)) { + ast_string_field_set(dialog, from_user, peer->from_user); + } + + if (!ast_strlen_zero(peer->language)) { + ast_string_field_set(dialog, language, peer->language); + } + + /* Set timer T1 to RTT for this peer (if known by qualify=). Minimum is settable or default to 100 ms. If there is a + * maxms and lastms from a qualify use that over a manual T1 value. Otherwise, use the peer's T1 value. */ + if (peer->qualify_max && peer->last_qualify) { + dialog->timer_t1 = peer->last_qualify < sip_config.t1_min ? sip_config.t1_min : peer->last_qualify; + } 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 */ + if (peer->timer_b) { + dialog->timer_b = peer->timer_b; + } else { + dialog->timer_b = dialog->timer_t1 * 64; + } + + if (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833 ||ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + dialog->non_format_cap |= AST_RTP_DTMF; + } else { + dialog->non_format_cap &= ~AST_RTP_DTMF; + } + + dialog->direct_media_acl = ast_duplicate_acl_list(peer->direct_media_acl); + + if (peer->call_limit) { + ast_set_flag(&dialog->flags[0], SIP_CALL_LIMIT); + } + + if (!dialog->port_in_uri) { + dialog->port_in_uri = peer->port_in_uri; + } + + if (peer->channel_variables) { + dialog->channel_variables = ast_variables_dup(peer->channel_variables); + } + + if (peer->from_domain_port) { + dialog->from_domain_port = peer->from_domain_port; + } + + dialog->caller_presentation = peer->caller_presentation; + dialog->do_not_disturb = peer->do_not_disturb; + + if (!ast_strlen_zero(peer->call_forward)) { + ast_string_field_set(dialog, call_forward, peer->call_forward); + } + + return 0; +} + +/* create address structure from device name Or, if peer not found, find it in the global DNS */ +int sip_dialog_build(struct sip_dialog *dialog, const char *peer_name, struct ast_sockaddr *address, int new_dialog) +{ + struct sip_peer *peer; + int res; + + 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) */ + + res = 0; + + if ((peer = sip_peer_find(peer_name, TRUE, FALSE))) { + if (new_dialog) { + sip_socket_set_transport(&dialog->socket, 0); + } + + res = sip_dialog_build_from_peer(dialog, peer); + + dialog->peer = ao2_t_bump(peer, "bump peer"); + ao2_t_cleanup(peer, "drop peer"); + + return res; + } else if (ast_check_digits(peer_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, "Purely numeric hostname (%s) and not a peer, rejecting!\n", peer_name); + return -1; + } else { + dialog->rtp_timeout = sip_config.rtp_timeout; + dialog->rtp_hold_timeout = sip_config.rtp_hold_timeout; + dialog->rtp_keepalive = sip_config.rtp_keepalive; + + if (sip_dialog_alloc_rtp(dialog)) { + return -1; + } + } + + if (address) { + /* This address should be updated using dnsmgr */ + ast_sockaddr_copy(&dialog->address, address); + } else { + char service[MAXHOSTNAMELEN + 64], host[MAXHOSTNAMELEN], *sep; + 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. */ + ast_copy_string(host, peer_name, sizeof(host)); + + if ((sep = strchr(host, ':'))) { + *sep++ = '\0'; + port = sip_str2port(sep, 0); + } else { + port = 0; + } + + 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 ((res = ast_get_srv(NULL, host, sizeof(host), &port, service)) < 1) { + ast_copy_string(host, peer_name, sizeof(host)); + } + } + + 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)); + sip_set_method_allowed(&dialog->allowed_methods, sip_config.disallowed_methods, FALSE); + + 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->received_address, &dialog->address); + 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@%s", + (unsigned long) ast_random(), (unsigned long) ast_random(), (unsigned long) ast_random(), (unsigned long) ast_random(), + !ast_sockaddr_isnull(&dialog->our_address) ? + ast_sockaddr_stringify_remote(&dialog->our_address) : ast_sockaddr_stringify(&sip_our_address)); +} + +/* 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_update 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_update) +{ + if (dialog->stale_nonce || force_update || ast_strlen_zero(dialog->nonce)) { + ast_string_field_build(dialog, nonce, "%08lx", (unsigned long) ast_random()); /* Create nonce for challenge */ + dialog->stale_nonce = FALSE; + } +} + +/* Build contact header. This is the Contact header that we send out in SIP requests and responses involving this sip_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_request *request) +{ + char encoded_user[512], *user; + int use_tls; + + use_tls = FALSE; + + if (request) { + char *record_route; + + record_route = ast_strdupa(sip_request_get_header(request, "Record-Route")); + record_route = sip_get_in_brackets(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_request_get_header(request, "Contact")); + contact = sip_get_in_brackets(contact); + + if (!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->exten, 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_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), ast_str_to_lower(transport)); + } +} + +/* Build route list from Record-Route header */ +void sip_dialog_build_route(struct sip_dialog *dialog, struct sip_request *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_clear(&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; + + record_route = sip_request_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 */ + char *contact, *uri; + + contact = ast_strdupa(sip_request_get_header(request, "Contact")); + uri = sip_get_in_brackets(contact); + + sip_route_add(&dialog->route, uri, FALSE); + } + + /* For debugging dump what we ended up with */ + if (request->debug) { + sip_route_dump(&dialog->route); + } +} + +/* Set the peers nat flags if they are using auto_* settings */ +void sip_dialog_set_nat(struct sip_dialog *dialog, struct sip_peer *peer) +{ + if (!dialog || !peer) { + return; + } + + if (ast_test_flag(&peer->flags[2], SIP_NAT_AUTO_RPORT)) { + if (dialog->nat_detected) { + ast_set_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT); + } else { + ast_clear_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT); + } + } + + if (ast_test_flag(&peer->flags[2], SIP_NAT_AUTO_COMEDIA)) { + if (dialog->nat_detected) { + ast_set_flag(&peer->flags[1], SIP_SYMMETRIC_RTP); + } else { + ast_clear_flag(&peer->flags[1], SIP_SYMMETRIC_RTP); + } + } +} + +/* Set nat mode on the various data sockets */ +void sip_dialog_set_rtp_nat(struct sip_dialog *dialog) +{ + int nat = !!ast_test_flag(&dialog->flags[1], SIP_SYMMETRIC_RTP); + + if (dialog->audio_rtp) { + ast_debug(1, "Setting NAT on RTP to %s\n", nat ? "On" : "Off"); + ast_rtp_instance_set_prop(dialog->audio_rtp, AST_RTP_PROPERTY_NAT, nat); + } + + if (dialog->video_rtp) { + ast_debug(1, "Setting NAT on VRTP to %s\n", nat ? "On" : "Off"); + ast_rtp_instance_set_prop(dialog->video_rtp, AST_RTP_PROPERTY_NAT, nat); + } + + if (dialog->udptl) { + ast_debug(1, "Setting NAT on UDPTL to %s\n", nat ? "On" : "Off"); + ast_udptl_setnat(dialog->udptl, nat); + } + + if (dialog->text_rtp) { + ast_debug(1, "Setting NAT on TRTP to %s\n", nat ? "On" : "Off"); + ast_rtp_instance_set_prop(dialog->text_rtp, AST_RTP_PROPERTY_NAT, nat); + } +} + +/* 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->received_address, address)) { + char *host, *received_host; + + host = ast_strdupa(ast_sockaddr_stringify_addr(address)); + received_host = ast_strdupa(ast_sockaddr_stringify_addr(&dialog->received_address)); + + ast_debug(3, "NAT detected for %s / %s\n", host, received_host); + dialog->nat_detected = TRUE; + + if (ast_test_flag(&dialog->flags[2], SIP_NAT_AUTO_RPORT)) { + ast_set_flag(&dialog->flags[0], SIP_NAT_FORCE_RPORT); + } + + if (ast_test_flag(&dialog->flags[2], SIP_NAT_AUTO_COMEDIA)) { + ast_set_flag(&dialog->flags[1], SIP_SYMMETRIC_RTP); + } + } else { + dialog->nat_detected = FALSE; + + if (ast_test_flag(&dialog->flags[2], SIP_NAT_AUTO_RPORT)) { + ast_clear_flag(&dialog->flags[0], SIP_NAT_FORCE_RPORT); + } + + if (ast_test_flag(&dialog->flags[2], SIP_NAT_AUTO_COMEDIA)) { + ast_clear_flag(&dialog->flags[1], SIP_SYMMETRIC_RTP); + } + } +} + +/* check Via: header for hostname, port and rport request/answer */ +void sip_dialog_check_via(struct sip_dialog *dialog, struct sip_request *request) +{ + struct ast_sockaddr address; + int port; + + if (request->via_rport) { /* rport query, not answer */ + ast_set_flag(&dialog->flags[1], SIP_RPORT_PRESENT); + ast_set_flag(&dialog->flags[0], SIP_NAT_RPORT_PRESENT); + } + + 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->received_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, "Sending to %s, %s\n", ast_sockaddr_stringify(sip_dialog_get_address(dialog)), sip_nat_mode2str(dialog->flags)); +} + +void sip_dialog_set_need_destroy(struct sip_dialog *dialog, const char *reason) +{ + if (dialog->final_destruction_scheduled) { + return; /* This is already scheduled for final destruction, let the scheduler take care of it. */ + } + + sip_history_append(dialog, "NeedDestroy", "Setting need destroy because %s", reason); + + if (!dialog->need_destroy) { + dialog->need_destroy = TRUE; + ao2_t_link(sip_dialogs_need_destroy, dialog, "link dialog"); + } +} + +/* Encapsulate setting of SIP_ALREADYGONE to be able to trace it with debugging */ +void sip_dialog_set_already_gone(struct sip_dialog *dialog) +{ + ast_debug(3, "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) : ""); + } +} + +/* Set destination from SIP URI. Parse uri to h (host) and port - uri is already just the part inside the <> general form + * we are expecting is sip[s]:username[:password][;parameter]@host[:port][;...]. If there's a port given, turn NAPTR/SRV off. + * NAPTR might indicate SIPS preference even for SIP: uri's. If there's a sips: uri scheme, TLS will be required. */ +void sip_dialog_set_address(struct sip_dialog *dialog, const char *uri) +{ + char *maddr, host[MAXHOSTNAMELEN]; + const char *sep; + int uri_len, use_tls; + + ast_debug(1, "Parsing <%s> for address/port to send to\n", uri); + + use_tls = FALSE; + + /* Find and parse host */ + if ((sep = strchr(uri, '@'))) { + uri = sep + 1; + } else { + if (!strncasecmp(uri, "sip:", 4)) { + uri += 4; + } else if (!strncasecmp(uri, "sips:", 5)) { + uri += 5; + use_tls = TRUE; + } + } + + uri_len = strcspn(uri, ";>") + 1; + + if (uri_len > sizeof(host)) { + uri_len = sizeof(host); + } + + ast_copy_string(host, uri, uri_len); + uri += uri_len - 1; /* XXX bug here if string has been trimmed to sizeof(host) */ + + if (ast_sockaddr_resolve_first_af(&dialog->address, host, 0, AST_AF_INET)) { + ast_log(LOG_WARNING, "Unable to find address for host '%s'\n", host); + return; + } + + /* Got the host - but maybe there's a "maddr=" to override address? */ + if ((maddr = strstr(uri, "maddr="))) { + int maddr_len, port; + + maddr += 6; + maddr_len = strcspn(maddr, "; ") + 1; + + if (maddr_len > sizeof(host)) { + maddr_len = sizeof(host); + } + + ast_copy_string(host, maddr, maddr_len); + port = ast_sockaddr_port(&dialog->address); + + if (ast_sockaddr_resolve_first_af(&dialog->address, host, PARSE_PORT_FORBID, AST_AF_INET)) { + ast_log(LOG_WARNING, "Unable to find address for host '%s'\n", host); + return; + } + + ast_sockaddr_set_port(&dialog->address, port); + } + + if (!ast_sockaddr_port(&dialog->address)) { + ast_sockaddr_set_port(&dialog->address, use_tls ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + } + + ast_debug(1, "Set destination to %s\n", ast_sockaddr_stringify(&dialog->address)); +} + +/* 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_request *request) +{ + char *uri, *user, *domain, *port, *from, *from_user, *decoded_user; + RAII_VAR(struct ast_features_pickup_config *, pickup_config, NULL, ao2_cleanup); + const char *pickup_exten; + + if (!(pickup_config = ast_get_chan_features_pickup_config(dialog->channel))) { + ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n"); + pickup_exten = ""; + } else { + /* Don't need to duplicate since channel is locked for the duration of this function */ + pickup_exten = pickup_config->pickupexten; + } + + /* Find the request URI */ + uri = ast_strdupa(request->uri); + + if (sip_parse_uri(uri, "sip:,sips:", &user, &domain, NULL)) { + ast_log(LOG_WARNING, "Invalid URI '%s'\n", uri); + return SIP_DESTINATION_INVALID_URI; + } + + sip_pedantic_decode(user); + sip_pedantic_decode(domain); + + ast_sockaddr_split_hostport(domain, &domain, &port, PARSE_PORT_IGNORE); + ast_string_field_set(dialog, domain, domain); + + if (ast_strlen_zero(user)) { + /* Either there really was no extension found or the request URI had encoded nulls that made the string + * "empty". Use "s" as the extension. */ + user = "s"; + } + + /* 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_request_get_header(request, "From")); + + if (!ast_strlen_zero(from)) { + from = sip_get_in_brackets(from); + + if (sip_parse_uri(from, "sip:,sips:", &from_user, &domain, NULL)) { + ast_log(LOG_WARNING, "Invalid From: '%s'\n", from); + return SIP_DESTINATION_INVALID_URI; + } + + sip_pedantic_decode(from_user); + sip_pedantic_decode(domain); + ast_sockaddr_split_hostport(domain, &domain, &port, PARSE_PORT_IGNORE); + + ast_string_field_set(dialog, from_domain, domain); + } else { + from_user = NULL; + } + + if (!AST_LIST_EMPTY(&sip_domains)) { + char context[AST_MAX_EXTENSION] = ""; + + if (!sip_domain_check(dialog->domain, context, sizeof(context))) { + if (!sip_config.allow_external_domains && + (request->method == SIP_METHOD_INVITE || request->method == SIP_METHOD_REFER)) { + ast_debug(1, "Got SIP %s to non-local domain '%s'; refusing request\n", + sip_methods[request->method].name, dialog->domain); + + return SIP_DESTINATION_REFUSED; + } + } + + /* If we don't have a peer (i.e. we're a guest call), overwrite the original context */ + if (!ast_test_flag(&dialog->flags[1], SIP_HAVE_PEER_CONTEXT) && !ast_strlen_zero(context)) { + ast_string_field_set(dialog, context, context); + } + } + + /* If the request coming in is a subscription and subscribecontext has been specified use it */ + if (request->method == SIP_METHOD_SUBSCRIBE && !ast_strlen_zero(dialog->subscribe_context)) { + ast_string_field_set(dialog, context, dialog->subscribe_context); + } + + ast_debug(1, "Looking for %s in %s (domain %s)\n", user, dialog->context, dialog->domain); + + /* Since extensions.conf can have unescaped characters, try matching a decoded uri in addition to the non-decoded uri. */ + decoded_user = ast_strdupa(user); + ast_uri_decode(decoded_user, ast_uri_sip_user); + + /* If this is a subscription we actually just need to see if a hint exists for the extension */ + if (request->method == SIP_METHOD_SUBSCRIBE) { + int set_decoded = FALSE; + + if (ast_get_hint(NULL, 0, NULL, 0, NULL, dialog->context, user) || + (ast_get_hint(NULL, 0, NULL, 0, NULL, dialog->context, decoded_user) && (set_decoded = TRUE))) { + if (request == &dialog->initial_request) { + ast_string_field_set(dialog, exten, set_decoded ? decoded_user : user); + } + + return SIP_DESTINATION_EXTEN_FOUND; + } else { + return SIP_DESTINATION_EXTEN_NOT_FOUND; + } + } else { + /* Check the dialplan for the username part of the request URI, the domain will be stored in the SIPDOMAIN variable, + * Return 0 if we have a matching extension */ + if (ast_exists_extension(NULL, dialog->context, user, 1, S_OR(dialog->caller_number, from_user))) { + if (request == &dialog->initial_request) { + ast_string_field_set(dialog, exten, user); + } + + return SIP_DESTINATION_EXTEN_FOUND; + } + + if (ast_exists_extension(NULL, dialog->context, decoded_user, 1, S_OR(dialog->caller_number, from_user)) || + !strcmp(decoded_user, pickup_exten)) { + if (request == &dialog->initial_request) { + ast_string_field_set(dialog, exten, decoded_user); + } + + return SIP_DESTINATION_EXTEN_FOUND; + } + } + + if (ast_test_flag(&sip_config.flags[1], SIP_ALLOW_OVERLAP) && + (ast_canmatch_extension(NULL, dialog->context, user, 1, S_OR(dialog->caller_number, from_user)) || + ast_canmatch_extension(NULL, dialog->context, decoded_user, 1, S_OR(dialog->caller_number, from_user)) || + !strncmp(decoded_user, pickup_exten, strlen(decoded_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_request *request) +{ + if (!ast_strlen_zero(dialog->realm)) { + return; + } + + if (sip_config.domains_as_realm && !AST_LIST_EMPTY(&sip_domains)) { + char *from, *to, *domain; + + from = ast_strdupa(sip_request_get_header(request, "From")); + from = sip_get_in_brackets(from); + + if (!sip_parse_uri(from, "sip:,", NULL, &domain, NULL)) { + if (!ast_strlen_zero(domain)) { + domain = strsep(&domain, ":"); + } + + /* Check From header first */ + if (sip_domain_check(domain, NULL, 0)) { + ast_string_field_set(dialog, realm, domain); + return; + } + } + + to = ast_strdupa(sip_request_get_header(request, "To")); + to = sip_get_in_brackets(to); + + if (!sip_parse_uri(to, "sip:,", NULL, &domain, NULL)) { + if (!ast_strlen_zero(domain)) { + domain = strsep(&domain, ":"); + } + + /* Check To header */ + if (sip_domain_check(domain, NULL, 0)) { + ast_string_field_set(dialog, realm, domain); + return; + } + } + } + + /* Use default realm from config file */ + ast_string_field_set(dialog, 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 (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_INBAND || + ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + if (dialog->audio_rtp) { + ast_rtp_instance_dtmf_mode_set(dialog->audio_rtp, AST_RTP_DTMF_MODE_INBAND); + } + + features |= DSP_FEATURE_DIGIT_DETECT; + } + + if (ast_test_flag(&dialog->flags[1], 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 (sip_config.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; + } + } +} + +/* A wrapper for sip_parse_allow geared toward sip_dialogs This function, in addition to setting the allowed methods for a + * sip_dialog also will take into account the setting of the SIP_REMOTE_PARTY_ID_UPDATE flag. */ +unsigned int sip_dialog_set_allowed_methods(struct sip_dialog *dialog, struct sip_request *request) +{ + sip_parse_allow(dialog, request); + sip_set_method_allowed(&dialog->allowed_methods, dialog->disallowed_methods, FALSE); + + return dialog->allowed_methods; +} + +/* The real destination address for a write */ +const struct ast_sockaddr *sip_dialog_get_address(struct sip_dialog *dialog) +{ + if (ast_test_flag(&dialog->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&dialog->flags[0], SIP_NAT_RPORT_PRESENT)) { + return &dialog->received_address; + } + + return &dialog->address; +} + +/* Change hold state for a call */ +void sip_dialog_change_onhold(struct sip_dialog *dialog, struct sip_request *request, int onhold, int send_recv) +{ + if (sip_config.notify_hold && (!onhold || !ast_test_flag(&dialog->flags[1], SIP_ONHOLD))) { + if (dialog->peer) { + /* If they put someone on hold, increment the value... otherwise decrement it */ + ast_atomic_fetchadd_int(&dialog->peer->onhold, onhold ? +1 : -1); + + /* Request device state update */ + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", dialog->peer->name); + } + } + + sip_history_append(dialog, onhold ? "Hold" : "Unhold", "Change hold"); + + if (!onhold) { /* Put off remote hold */ + ast_clear_flag(&dialog->flags[1], SIP_ONHOLD); /* Clear both flags */ + return; + } + + /* No address for RTP, we're on hold */ + /* Ensure hold flags are cleared so that overlapping flags do not conflict */ + ast_clear_flag(&dialog->flags[1], SIP_ONHOLD); + + if (send_recv == SIP_SDP_SEND_ONLY) { /* One directional hold (sendonly/recvonly) */ + ast_set_flag(&dialog->flags[1], SIP_ONHOLD_RECV_ONLY); + } else if (send_recv == SIP_SDP_SEND_INACTIVE) { /* Inactive stream */ + ast_set_flag(&dialog->flags[1], SIP_ONHOLD_INACTIVE); + } else { + ast_set_flag(&dialog->flags[1], SIP_ONHOLD_ACTIVE); + } +} + +/* sip_dialog_update_call_counter: Handle call_limit for SIP device. Setting a call-limit will cause calls above the limit not + * to be accepted. Thought: For realtime, we should probably update storage with inuse counter. */ +int sip_dialog_update_call_counter(struct sip_dialog *dialog, int event) +{ + struct sip_peer *peer; + + ast_debug(3, "Updating call counter for %s call\n", dialog->outgoing_call ? "outgoing" : "incoming"); + + /* Test if we need to check call limits, in order to avoid realtime lookups if we do not need it */ + if (!ast_test_flag(&dialog->flags[0], SIP_CALL_LIMIT) && !ast_test_flag(&dialog->flags[1], SIP_ONHOLD)) { + return 0; + } + + /* Check the list of devices */ + if (!dialog->peer) { + ast_debug(2, "%s is not a local device, no call limit\n", dialog->peer_name); + return 0; + } + + peer = ao2_t_bump(dialog->peer, "bump peer"); + + switch (event) { + /* incoming and outgoing affects the inuse counter */ + case SIP_DEC_CALL_LIMIT: + /* Decrement ringing count if applicable */ + ao2_lock(dialog); + ao2_lock(peer); + + if (peer->ringing > 0) { + if (ast_test_flag(&dialog->flags[0], SIP_CALL_COUNTER_RINGING)) { + peer->ringing--; + ast_clear_flag(&dialog->flags[0], SIP_CALL_COUNTER_RINGING); + } + } else { + peer->ringing = 0; + } + + if (peer->inuse > 0) { + if (ast_test_flag(&dialog->flags[0], SIP_CALL_COUNTER_INUSE)) { + peer->inuse--; + ast_clear_flag(&dialog->flags[0], SIP_CALL_COUNTER_INUSE); + } + } else { + peer->inuse = 0; + } + + + if (ast_test_flag(&dialog->flags[1], SIP_ONHOLD) && sip_config.notify_hold) { + peer->onhold--; + ast_clear_flag(&dialog->flags[1], SIP_ONHOLD); + } + + ao2_unlock(peer); + ao2_unlock(dialog); + + ast_debug(2, "Call %s %s '%s' removed from call limit %d\n", + dialog->outgoing_call ? "to" : "from", "peer", peer->name, peer->call_limit); + + break; + case SIP_INC_CALL_RINGING: + case SIP_INC_CALL_LIMIT: + /* If call limit is active and we have reached the limit, reject the call */ + if (peer->call_limit > 0) { + if (peer->inuse >= peer->call_limit) { + ast_log(LOG_NOTICE, "Call %s %s '%s' rejected due to usage limit of %d\n", + dialog->outgoing_call ? "to" : "from", "peer", peer->name, peer->call_limit); + ao2_t_cleanup(peer, "bump peer"); + + return -1; + } + } + + ao2_lock(dialog); + ao2_lock(peer); + + if (event == SIP_INC_CALL_RINGING) { + if (!ast_test_flag(&dialog->flags[0], SIP_CALL_COUNTER_RINGING)) { + peer->ringing++; + ast_set_flag(&dialog->flags[0], SIP_CALL_COUNTER_RINGING); + } + } + + if (!ast_test_flag(&dialog->flags[0], SIP_CALL_COUNTER_INUSE)) { + peer->inuse++; + ast_set_flag(&dialog->flags[0], SIP_CALL_COUNTER_INUSE); + } + + ao2_unlock(peer); + ao2_unlock(dialog); + + ast_debug(2, "Call %s %s '%s' is %d out of %d\n", + dialog->outgoing_call ? "to" : "from", "peer", peer->name, peer->inuse, peer->call_limit); + + break; + case SIP_DEC_CALL_RINGING: + ao2_lock(dialog); + ao2_lock(peer); + + if (ast_test_flag(&dialog->flags[0], SIP_CALL_COUNTER_RINGING)) { + if (peer->ringing > 0) { + peer->ringing--; + } + + ast_clear_flag(&dialog->flags[0], SIP_CALL_COUNTER_RINGING); + } + + ao2_unlock(peer); + ao2_unlock(dialog); + + break; + default: + break; + } + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + ao2_t_cleanup(peer, "drop peer"); + + return 0; +} + +/* Notify peer that the connected line has changed */ +void sip_dialog_update_connected_line(struct sip_dialog *dialog) +{ + struct ast_party_id connected_line; + + if (!ast_test_flag(&dialog->flags[0], SIP_SEND_REMOTE_PARTY_ID)) { + return; + } + + connected_line = ast_channel_connected_effective_id(dialog->channel); + + sip_history_append(dialog, "ConnectedLine", "%s party is now %s <%s>", + ast_test_flag(&dialog->flags[0], SIP_OUTGOING) ? "Calling" : "Called", + S_COR(connected_line.name.valid, connected_line.name.str, ""), + S_COR(connected_line.number.valid, connected_line.number.str, "")); + + if (ast_channel_state(dialog->channel) == AST_STATE_UP || ast_test_flag(&dialog->flags[0], SIP_OUTGOING)) { + if (sip_allowed_method(&dialog->allowed_methods, SIP_METHOD_UPDATE)) { + sip_send_update(dialog); + } else if (!dialog->pending_invite_cseq && + (dialog->invite_state == SIP_INVITE_CONFIRMED || dialog->invite_state == SIP_INVITE_TERMINATED)) { + sip_send_reinvite_with_sdp(dialog, TRUE, dialog->t38_state == SIP_T38_ENABLED); + } else { + /* We cannot send the update yet, so we have to wait until we can */ + ast_set_flag(&dialog->flags[0], SIP_NEED_REINVITE); + } + } else { + if (ast_channel_state(dialog->channel) == AST_STATE_RING && !ast_test_flag(&dialog->flags[0], SIP_PROGRESS_SENT)) { + sip_send_response_with_remote_party_id(dialog, "180 Ringing", &dialog->initial_request); + ast_set_flag(&dialog->flags[0], SIP_RINGING); + } else if (ast_channel_state(dialog->channel) == AST_STATE_RINGING) { + sip_send_response_with_remote_party_id(dialog, "183 Session Progress", &dialog->initial_request); + ast_set_flag(&dialog->flags[0], SIP_PROGRESS_SENT); + } else { + ast_set_flag(&dialog->flags[1], SIP_CONNECTEDLINE_UPDATE_PENDING); + + ast_debug(1, "Unable able to send update to '%s' in state '%s'\n", + ast_channel_name(dialog->channel), ast_state2str(ast_channel_state(dialog->channel))); + } + } +} + +void sip_dialog_queue_connected_line_update(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) +{ + struct sip_request resp; + + if (ast_channel_state(dialog->channel) == AST_STATE_UP || ast_test_flag(&dialog->flags[0], SIP_OUTGOING)) { + return; + } + + sip_response_prepare(&resp, dialog, "181 Call Is Being Forwarded", &dialog->initial_request); + sip_request_add_diversion(&resp, dialog); + + sip_response_send(dialog, &resp, SIP_SEND_UNRELIABLE, 0); +} + +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_request *response, int method) +{ + char *www_authenticate; + struct sip_authorization_data authorization_data; + + www_authenticate = ast_strdupa(sip_request_get_header(response, response->code == 401 ? "WWW-Authenticate" : "Proxy-Authenticate")); + + if (ast_strlen_zero(www_authenticate)) { + return -1; + } + + sip_parse_authorization(www_authenticate, &authorization_data); + + /* Reset nonce count */ + if (!ast_strlen_zero(authorization_data.nonce) && strcmp(dialog->nonce, authorization_data.nonce)) { + dialog->nonce_count = 0; + } + + ast_string_field_set(dialog, realm, authorization_data.realm); + ast_string_field_set(dialog, domain, authorization_data.domain); + ast_string_field_set(dialog, nonce, authorization_data.nonce); + ast_string_field_set(dialog, opaque, authorization_data.opaque); + ast_string_field_set(dialog, qop, authorization_data.qop); + + /* Save auth data for following registrations */ + if (dialog->registry) { + if (strcmp(dialog->registry->nonce, dialog->nonce)) { + ast_string_field_set(dialog->registry, realm, dialog->realm); + ast_string_field_set(dialog->registry, domain, dialog->domain); + ast_string_field_set(dialog->registry, nonce, dialog->nonce); + ast_string_field_set(dialog->registry, opaque, dialog->opaque); + ast_string_field_set(dialog->registry, qop, dialog->qop); + + dialog->registry->nonce_count = 0; + } + } + + return sip_dialog_build_authorization(dialog, 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 *username, *secret, *md5secret; + struct sip_auth_realm_head *auth_realms; + struct sip_auth_realm *auth_realm; /* Realm authentication credential */ + struct ast_str *authorization; + + if (!ast_strlen_zero(dialog->domain)) { + snprintf(uri, sizeof(uri), "%s:%s", dialog->socket.transport == AST_TRANSPORT_TLS ? "sips" : "sip", dialog->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->username, ast_sockaddr_stringify_host_remote(&dialog->address)); + } + + /* Check if we have peer credentials */ + ao2_lock(dialog); + + if ((auth_realms = dialog->auth_realms)) { + ao2_t_ref(auth_realms, +1, "bump auth_realms"); + } + + ao2_unlock(dialog); + + if (!(auth_realm = sip_auth_realm_find(auth_realms, dialog->realm))) { + /* If not, check global credentials */ + if (auth_realms) { + ao2_t_ref(auth_realms, -1, "drop auth_realms"); + } + + ast_mutex_lock(&sip_auth_realm_lock); + + if ((auth_realms = sip_auth_realms)) { + ao2_t_ref(auth_realms, +1, "bump auth_realms"); + } + + ast_mutex_unlock(&sip_auth_realm_lock); + auth_realm = sip_auth_realm_find(auth_realms, dialog->realm); + } + + if (auth_realm) { + ast_debug(3, "Using realm %s from peer %s %s\n", auth_realm->username, dialog->peer_name, dialog->username); + + username = auth_realm->username; + secret = auth_realm->secret; + md5secret = auth_realm->md5secret; + + ast_debug(1, "Using realm %s authentication for '%s'\n", dialog->realm, dialog->call_id); + } else { + /* No authentication, use peer or register= config */ + username = dialog->auth_name; + secret = dialog->peer && !ast_strlen_zero(dialog->peer->remote_secret) ? dialog->peer->remote_secret : dialog->peer_secret; + md5secret = dialog->peer_md5secret; + } + + if (ast_strlen_zero(username)) { + /* We have no authentication */ + if (auth_realms) { + ao2_t_ref(auth_realms, -1, "drop auth_realms"); + } + + return -1; + } + + /* Calculate SIP digest response */ + snprintf(a1, sizeof(a1), "%s:%s:%s", username, dialog->realm, secret); + snprintf(a2, sizeof(a2), "%s:%s", sip_methods[method].name, uri); + + if (!ast_strlen_zero(md5secret)) { + ast_copy_string(a1_hash, md5secret, sizeof(a1_hash)); + } else { + ast_md5_hash(a1_hash, a1); + } + + ast_md5_hash(a2_hash, a2); + + dialog->nonce_count++; + snprintf(cnonce, sizeof(cnonce), "%08lx", (unsigned long) ast_random()); + + if (!ast_strlen_zero(dialog->qop)) { + snprintf(response, sizeof(response), "%s:%s:%08x:%s:%s:%s", + a1_hash, dialog->nonce, (unsigned int) dialog->nonce_count, cnonce, "auth", a2_hash); + } else { + snprintf(response, sizeof(response), "%s:%s:%s", a1_hash, dialog->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\"", + username, dialog->realm, uri, dialog->nonce, response_hash); + + /* We hard code our qop to "auth" for now. */ + if (!ast_strlen_zero(dialog->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->opaque)) { + ast_str_append(&authorization, 0, ",opaque=\"%s\"", dialog->opaque); + } + + ast_string_field_set(dialog, authorization, ast_str_buffer(authorization)); + + sip_history_append(dialog, "AuthResp", "Auth response sent for %s in realm %s nonce-count %d", + username, dialog->realm, dialog->nonce_count); + + if (auth_realms) { + ao2_t_ref(auth_realms, -1, "drop auth_realms"); + } + + return 0; +} + +/* Check user authorization from peer definition */ +static int sip_dialog_check_authorization(struct sip_dialog *dialog, struct sip_request *request, const char *username, + const char *secret, const char *md5secret, const char *uri, int reliable) +{ + char *authorization; + struct sip_authorization_data authorization_data; + int bogus, wrong_nonce, authenticated; + char a1[256], a2[512], a1_hash[64], a2_hash[64], response[1024], response_hash[64]; + const char *nonce; + + /* Always OK if no secret */ + if (ast_strlen_zero(secret) && ast_strlen_zero(md5secret)) { + return SIP_AUTHORIZATION_SUCCESSFUL; + } else if (ast_test_flag(&dialog->flags[1], SIP_CISCO_USECALLMANAGER) && + (request->method == SIP_METHOD_REFER || request->method == SIP_METHOD_PUBLISH)) { + return SIP_AUTHORIZATION_SUCCESSFUL; /* Buggy Cisco USECALLMANAGER 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_request_get_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_send_response_with_www_authenticate(dialog, "401 Unauthorized", request, reliable, FALSE); + /* Schedule auto destroy in 32 seconds (according to RFC 3261) */ + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + } + + return SIP_AUTHORIZATION_CHALLENGE_SENT; + } 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); /* Create nonce for challenge */ + sip_send_response_with_www_authenticate(dialog, "401 Unauthorized", request, reliable, FALSE); + + /* Schedule auto destroy in 32 seconds */ + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return SIP_AUTHORIZATION_CHALLENGE_SENT; + } + + sip_parse_authorization(authorization, &authorization_data); + + /* We cannot rely on the bogus_peer having a bad md5 value. Someone could + * use it to construct valid auth. */ + if (md5secret && !strcmp(md5secret, SIP_BOGUS_PEER_MD5SECRET)) { + bogus = TRUE; + } else { + bogus = FALSE; + } + + /* Verify that digest username matches the username we auth as */ + if (strcmp(username, S_OR(authorization_data.username, "")) && !bogus) { + ast_log(LOG_WARNING, "Authorization username mismatch, have '%s', digest has '%s'\n", + username, authorization_data.username); + + /* Oops, we're trying something here */ + return SIP_AUTHORIZATION_USERNAME_MISMATCH; + } + + /* Verify nonce from request 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, S_OR(authorization_data.nonce, "")) || dialog->stale_nonce) { + wrong_nonce = TRUE; + nonce = S_OR(authorization_data.nonce, ""); + } else { + wrong_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(md5secret)) { + ast_copy_string(a1_hash, md5secret, sizeof(a1_hash)); + } else { + snprintf(a1, sizeof(a1), "%s:%s:%s", username, dialog->realm, secret); + ast_md5_hash(a1_hash, a1); + } + + /* compute the expected response to compare with what we received */ + snprintf(a2, sizeof(a2), "%s:%s", sip_methods[request->method].name, S_OR(authorization_data.uri, 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, S_OR(authorization_data.response, "")) && !bogus; + + if (wrong_nonce) { + if (authenticated) { + ast_debug(1, "Correct authorization, but based on stale nonce received from '%s'\n", + sip_request_get_header(request, "From")); + + /* We got working auth token, based on stale nonce . */ + sip_dialog_build_nonce(dialog, FALSE); + sip_send_response_with_www_authenticate(dialog, "401 Unauthorized", 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_log(LOG_NOTICE, "Bad authorization received from '%s'\n", + sip_request_get_header(request, "To")); + } + + sip_dialog_build_nonce(dialog, TRUE); + } else if (request->debug) { + ast_log(LOG_NOTICE, "Duplicate authorization received from '%s'\n", + sip_request_get_header(request, "To")); + } + + sip_send_response_with_www_authenticate(dialog, "401 Unauthorized", request, reliable, FALSE); + } + + /* Schedule auto destroy in 32 seconds */ + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return SIP_AUTHORIZATION_CHALLENGE_SENT; + } + + if (authenticated) { + sip_history_append(dialog, "AuthOK", "Auth challenge successful for %s", username); + return SIP_AUTHORIZATION_SUCCESSFUL; + } + + /* 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; +} + +/* Validate device authentication */ +static int __sip_dialog_check_peer_authorization(struct sip_dialog *dialog, char *peer_name, struct sip_request *request, + char *uri, struct sip_peer **auth_peer, int reliable) +{ + struct sip_peer *peer, *bogus_peer; + int res; + + /* First find devices based on username */ + if (!(peer = sip_peer_find(peer_name, TRUE, FALSE))) { + /* Then find devices based on IP */ + peer = sip_peer_find_by_address(&dialog->received_address, dialog->socket.transport, TRUE, FALSE); + } + + if (!peer) { + ast_debug(1, "No matching peer for '%s' from '%s'\n", + peer_name, ast_sockaddr_stringify(&dialog->received_address)); + + /* If you don't mind, we can return 404s for devices that do not exist: username disclosure. If we allow + * guests, there is no way around that. */ + if (!sip_config.always_auth_reject) { + return SIP_AUTHORIZATION_DONT_KNOW; + } + + /* 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. */ + if (!(peer = ao2_t_bump(sip_bogus_peer, "bump peer"))) { + return SIP_AUTHORIZATION_DONT_KNOW; + } + + bogus_peer = peer; + } else { + bogus_peer = NULL; + } + + if (!ast_apply_acl(peer->acl, &dialog->received_address, "SIP Peer ACL: ")) { + ast_debug(1, "Found peer '%s' for '%s', but fails host access\n", peer->name, peer_name); + ao2_t_cleanup(peer, "drop peer"); + + return SIP_AUTHORIZATION_ACL_FAILED; + } + + if (peer != bogus_peer) { + ast_debug(1, "Found peer '%s' for '%s' from %s\n", peer->name, peer_name, ast_sockaddr_stringify(&dialog->received_address)); + } + + /* Take the peer */ + ast_copy_flags(&dialog->flags[0], &peer->flags[0], SIP_FLAGS0_MASK); + ast_copy_flags(&dialog->flags[1], &peer->flags[1], SIP_FLAGS1_MASK); + ast_copy_flags(&dialog->flags[2], &peer->flags[2], SIP_FLAGS2_MASK); + + /* Copy SIP extensions profile to peer */ + if (dialog->options) { + peer->options = dialog->options; + } + + sip_dialog_set_rtp_nat(dialog); + + ast_string_field_set(dialog, auth_name, peer->name); + ast_string_field_set(dialog, peer_secret, peer->secret); + ast_string_field_set(dialog, peer_md5secret, peer->md5secret); + + /* Cisco peers only auth using the credentials of the primary peer */ + if (ast_test_flag(&dialog->flags[1], SIP_CISCO_USECALLMANAGER) && peer->cisco_line_index > 1) { + ast_string_field_set(dialog, auth_name, peer->cisco_auth_name); + } + + if (!(res = sip_dialog_check_authorization(dialog, request, dialog->auth_name, dialog->peer_secret, + dialog->peer_md5secret, uri, reliable))) { + /* sip_peer_build, called through sip_peer_find, is not able to check the sip_dialog->nat_detected + * flag in order to determine if the peer is behind NAT or not when SIP_NAT_AUTO_RPORT or SIP_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_dialog_set_nat(dialog, peer); + + if (dialog->nat_detected && ast_test_flag(&peer->flags[2], SIP_NAT_AUTO_RPORT)) { + ast_sockaddr_copy(&peer->address, &dialog->received_address); + } + + /* If we have a call limit, set flag */ + if (peer->call_limit) { + ast_set_flag(&dialog->flags[0], SIP_CALL_LIMIT); + } + + dialog->disallowed_methods = peer->disallowed_methods; + sip_dialog_set_allowed_methods(dialog, request); + + if (peer->caller_presentation) { /* Peer calling pres setting will override RPID */ + dialog->caller_presentation = peer->caller_presentation; + } + + if (peer->qualify_max && peer->last_qualify) { + dialog->timer_t1 = peer->last_qualify < sip_config.t1_min ? sip_config.t1_min : peer->last_qualify; + } else { + dialog->timer_t1 = peer->timer_t1; + } + + /* Set timer B to control transaction timeouts */ + if (peer->timer_b) { + dialog->timer_b = peer->timer_b; + } else { + dialog->timer_b = dialog->timer_t1 * 64; + } + + ast_string_field_set(dialog, peer_name, peer->name); + ast_string_field_set(dialog, subscribe_context, peer->subscribe_context); + ast_string_field_set(dialog, moh_interpret, peer->moh_interpret); + ast_string_field_set(dialog, moh_suggest, peer->moh_suggest); + + if (!ast_strlen_zero(peer->parkinglot)) { + ast_string_field_set(dialog, parkinglot, peer->parkinglot); + } + + ast_string_field_set(dialog, rtp_engine, peer->rtp_engine); + + if (request->method == SIP_METHOD_INVITE) { + /* destroy old channel vars and copy in new ones. */ + ast_variables_destroy(dialog->channel_variables); + dialog->channel_variables = NULL; + + if (peer->channel_variables) { + dialog->channel_variables = ast_variables_dup(peer->channel_variables); + } + } + + if (auth_peer) { + ao2_t_ref(peer, +1, "bump peer"); + *auth_peer = peer; /* Add a ref to the object here, to keep it in memory a bit longer if it is realtime */ + } + + if (!ast_strlen_zero(peer->username)) { + ast_string_field_set(dialog, username, peer->username); + /* Use the default username for authentication on outbound calls */ + ast_string_field_set(dialog, auth_name, peer->username); + } + + if (!sip_parse_remote_party_id(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); + } + + if (peer->caller_presentation) { + dialog->caller_presentation = peer->caller_presentation; + } + } + + if (!ast_strlen_zero(peer->caller_tag)) { + ast_string_field_set(dialog, caller_tag, peer->caller_tag); + } + + ast_string_field_set(dialog, full_contact, peer->full_contact); + + if (!ast_strlen_zero(peer->context)) { + ast_string_field_set(dialog, context, peer->context); + } + + if (!ast_strlen_zero(peer->message_context)) { + ast_string_field_set(dialog, message_context, peer->message_context); + } + + if (!ast_strlen_zero(peer->mwi_from)) { + ast_string_field_set(dialog, mwi_from, peer->mwi_from); + } + + ast_string_field_set(dialog, language, peer->language); + ast_string_field_set(dialog, accountcode, peer->accountcode); + dialog->amaflags = peer->amaflags; + + dialog->callgroup = peer->callgroup; + dialog->pickupgroup = peer->pickupgroup; + + ast_unref_namedgroups(dialog->named_callgroups); + dialog->named_callgroups = ast_ref_namedgroups(peer->named_callgroups); + ast_unref_namedgroups(dialog->named_pickupgroups); + dialog->named_pickupgroups = ast_ref_namedgroups(peer->named_pickupgroups); + + 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); + + ast_copy_string(dialog->zone, peer->zone, sizeof(dialog->zone)); + + if (peer->max_forwards > 0) { + dialog->max_forwards = peer->max_forwards; + } + + 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)) { + /* Set Frame packetization */ + if (dialog->audio_rtp) { + ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(dialog->audio_rtp), + ast_format_cap_get_framing(peer->format_cap)); + dialog->auto_framing = peer->auto_framing; + } + } else { + res = SIP_AUTHORIZATION_RTP_FAILED; + } + + dialog->rtp_timeout = peer->rtp_timeout; + dialog->rtp_hold_timeout = peer->rtp_hold_timeout; + dialog->rtp_keepalive = peer->rtp_keepalive; + + dialog->max_call_bitrate = peer->max_call_bitrate; + dialog->t38_max_datagram = peer->t38_max_datagram; + + if (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833 || + ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + dialog->non_format_cap |= AST_RTP_DTMF; + } else { + dialog->non_format_cap &= ~AST_RTP_DTMF; + } + } + + ao2_t_cleanup(peer, "drop peer"); + + return res; +} + +/* 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_check_peer_authorization(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer **auth_peer, int reliable) +{ + char *from, *user, *domain, *port, *uri; + struct ast_str *caller_name; + int res; + + uri = ast_strdupa(request->uri); + sip_remove_uri_parameters(uri); /* trim extra stuff */ + + if (ast_strlen_zero(dialog->exten)) { + if (!strncasecmp(uri, "sip:", 4)) { + uri += 4; + } else if (!strncasecmp(uri, "sips:", 5)) { + uri += 5; + } + + uri = strsep(&uri, "@"); + ast_string_field_set(dialog, exten, uri); + + if (ast_strlen_zero(dialog->our_contact)) { + sip_dialog_build_contact(dialog, request); + } + } + + from = ast_strdupa(sip_request_get_header(request, "From")); + + /* strip the display-name portion off the beginning of the FROM header. */ + caller_name = ast_str_alloca(128); + + if (!sip_get_name(from, &caller_name)) { + ast_string_field_set(dialog, caller_name, ast_str_buffer(caller_name)); + } + + from = sip_get_in_brackets(from); + /* save the URI part of the From header */ + ast_string_field_set(dialog, from, from); + + if (sip_parse_uri(from, "sip:,sips:", &user, &domain, NULL)) { + ast_log(LOG_NOTICE, "From address missing 'sip:', using it anyway\n"); + } + + sip_pedantic_decode(user); + sip_pedantic_decode(domain); + + ast_sockaddr_split_hostport(domain, &domain, &port, PARSE_PORT_IGNORE); + + if (ast_strlen_zero(domain)) { + /* , never good */ + ast_log(LOG_ERROR, "Empty domain name in FROM header\n"); + return SIP_AUTHORIZATION_DONT_KNOW; + } + + if (ast_strlen_zero(user)) { + /* . Asterisk 1.4 and 1.6 have always treated that as a username, so we continue + * the tradition: uri is now . */ + user = domain; + } else { + /* Non-empty name, try to get caller id from it */ + char *caller_number; + + caller_number = ast_strdupa(user); + /* We need to be able to handle from-headers looking like */ + caller_number = strsep(&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 (sip_config.match_auth_username) { + /* This is experimental code to grab the search key from the Auth header's 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? */ + const char *authorization = sip_request_get_header(request, "Authorization"); + + if (ast_strlen_zero(authorization)) { + authorization = sip_request_get_header(request, "Proxy-Authorization"); + } + + if (!ast_strlen_zero(authorization) && (authorization = strstr(authorization, "username=\""))) { + user = ast_strdupa(authorization + 10); + user = strsep(&user, "\""); + } + } + + res = __sip_dialog_check_peer_authorization(dialog, user, request, uri, auth_peer, reliable); + + if (res != SIP_AUTHORIZATION_DONT_KNOW) { + return res; + } + + res = SIP_AUTHORIZATION_SECRET_FAILED; /* we don't want any guests, authentication will fail */ + + if (ast_test_flag(&dialog->flags[1], SIP_RPORT_PRESENT)) { + ast_set_flag(&dialog->flags[0], SIP_NAT_RPORT_PRESENT); + } + + return res; +} + +/* 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_check_register_authorization(struct sip_dialog *dialog, struct sip_request *request) +{ + struct sip_peer *peer; + char *to, *user, *domain, *port, *uri; + int addr_changed, res; + + uri = ast_strdupa(request->uri); + sip_remove_uri_parameters(uri); + + to = ast_strdupa(sip_request_get_header(request, "To")); + to = sip_remove_uri_parameters(sip_get_in_brackets(to)); + + if (sip_parse_uri(to, "sip:,sips:", &user, &domain, NULL)) { + ast_log(LOG_NOTICE, "Invalid to address: '%s' from %s (missing sip:)\n", + to, ast_sockaddr_stringify_addr(&dialog->received_address)); + return -1; + } + + sip_pedantic_decode(user); + sip_pedantic_decode(domain); + + ast_sockaddr_split_hostport(domain, &domain, &port, PARSE_PORT_IGNORE); + + if (ast_strlen_zero(domain)) { + /* , never good */ + sip_send_response(dialog, "404 Not Found", &dialog->initial_request); + + return SIP_AUTHORIZATION_UNKNOWN_DOMAIN; + } + + if (ast_strlen_zero(user)) { + /* , unsure whether valid for registration. RFC 3261, 10.2 states: "The To header + * field and the Request-URI field typically differ, as the former contains a user name." But, Asterisk has + * always treated the domain-only uri as a username: we allow admins to create accounts described by domain + * name. */ + user = domain; + } + + /* This here differs from 1.4 and 1.6: the domain matching ACLs were + * skipped if it was a domain-only URI (used as username). Here we treat + * as and won't forget to test the + * domain ACLs against host. */ + if (!AST_LIST_EMPTY(&sip_domains) && !sip_domain_check(domain, NULL, 0)) { + if (sip_config.always_auth_reject) { + sip_send_response_with_fake_authorization(dialog, &dialog->initial_request); + } else { + sip_send_response(dialog, "404 Not Found", &dialog->initial_request); + } + + return SIP_AUTHORIZATION_UNKNOWN_DOMAIN; + } + + /* Cisco USECALLMANAGER failover */ + if (strcasestr(sip_request_get_header(request, "Contact"), ";expires=0;cisco-keep-alive")) { + sip_send_response_with_date(dialog, "200 OK", request); + return 0; + } + + ast_string_field_set(dialog, exten, user); + sip_dialog_build_contact(dialog, request); + + if (request->ignore) { + /* Expires is a special case, where we only want to load the peer if this isn't a deregistration attempt */ + const char *expires; + int expiry; + + expires = sip_request_get_header(request, "Expires"); + + if (ast_strlen_zero(expires)) { /* No expires header; look in Contact */ + if ((expires = strcasestr(sip_request_get_header(request, "Contact"), ";expires="))) { + expiry = atoi(expires + 9); + } + } else { + expiry = atoi(expires); + } + + if (!ast_strlen_zero(expires) && expiry == 0) { + sip_send_response_with_date(dialog, "200 OK", request); + return 0; + } + } + + peer = sip_peer_find(user, TRUE, FALSE); + + /* If we don't want username disclosure, use the bogus_peer when a user is not found. */ + if (!peer && sip_config.always_auth_reject) { + peer = ao2_t_bump(sip_bogus_peer, "bump peer"); + } + + if (!(peer && ast_apply_acl(peer->acl, &dialog->received_address, "SIP Peer ACL: "))) { + /* Peer fails ACL check */ + if (peer) { + ao2_t_cleanup(peer, "drop peer"); + peer = NULL; + + res = SIP_AUTHORIZATION_ACL_FAILED; + } else { + res = SIP_AUTHORIZATION_NOT_FOUND; + } + } + + res = SIP_AUTHORIZATION_NOT_FOUND; + + if (peer) { + ao2_lock(peer); + + if (!peer->host_dynamic) { + ast_log(LOG_ERROR, "Peer '%s' is trying to register, but not configured as host=dynamic\n", peer->name); + res = SIP_AUTHORIZATION_PEER_NOT_DYNAMIC; + } else { + sip_dialog_set_nat(dialog, peer); + ast_copy_flags(&dialog->flags[0], &peer->flags[0], SIP_NAT_FORCE_RPORT); + + if (!(res = sip_dialog_check_authorization(dialog, request, + peer->name, peer->secret, peer->md5secret, uri, SIP_SEND_UNRELIABLE))) { + sip_dialog_cancel_destroy(dialog); + + if (sip_peer_check_transport(peer, dialog->socket.transport)) { + ast_set_flag(&dialog->flags[0], SIP_PENDING_BYE); + sip_send_response_with_date(dialog, "403 Forbidden", request); + + res = SIP_AUTHORIZATION_BAD_TRANSPORT; + } else { + /* We have a successful registration attempt with proper authentication, now, update the peer */ + switch (sip_dialog_parse_register(dialog, peer, request, &addr_changed)) { + case SIP_PARSE_REGISTER_DENIED: + ast_log(LOG_WARNING, "Registration denied because of contact ACL\n"); + sip_send_response_with_date(dialog, "603 Decline", request); + + res = 0; + break; + case SIP_PARSE_REGISTER_FAILED: + ast_log(LOG_WARNING, "Failed to parse contact info\n"); + sip_send_response_with_date(dialog, "400 Bad Request", request); + + res = 0; + break; + case SIP_PARSE_REGISTER_QUERY: + ast_string_field_set(dialog, full_contact, peer->full_contact); + sip_send_response_with_date(dialog, "200 OK", request); + + res = 0; + break; + case SIP_PARSE_REGISTER_UPDATE: + ast_string_field_set(dialog, full_contact, peer->full_contact); + + /* If expiry is 0, peer has been unregistered already */ + if (dialog->expiry > 0) { + sip_peer_update(peer, dialog->expiry); + } + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + ast_set2_flag(&dialog->flags[1], + ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER), SIP_CISCO_USECALLMANAGER); + + /* Say OK and ask subsystem to retransmit msg counter */ + if (dialog->expiry > 0 && ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER) && addr_changed) { + sip_send_response_with_optionsind(dialog, request); + + /* We only need to do an update if the peer addr has changed */ + sip_peer_register_aliases(peer); + sip_peer_send_bulk_update(peer); + sip_peer_update_subscriptions(peer); + + ao2_unlock(dialog); + sip_peer_send_qualify(peer, FALSE); + ao2_lock(dialog); + } else { + sip_send_response_with_date(dialog, "200 OK", request); + + if (dialog->expiry > 0) { + 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); + } + } + + res = 0; + break; + } + } + } + } + + ao2_unlock(peer); + } + + if (res < 0) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + char *peer_host, *peer_port; + + peer_host = ast_strdupa(ast_sockaddr_stringify_addr(&dialog->received_address)); + peer_port = ast_strdupa(ast_sockaddr_stringify_port(&dialog->received_address)); + + switch (res) { + case SIP_AUTHORIZATION_SECRET_FAILED: + /* Wrong password in authentication. Go away, don't try again until you fixed it */ + sip_send_response(dialog, "403 Forbidden", &dialog->initial_request); + + if (sip_config.auth_failure_events) { + blob = ast_json_pack("{s: s, s: s, s: s, s: s}", + "peer_status", "Rejected", "cause", "SIP_AUTHORIZATION_SECRET_FAILED", + "address", peer_host, "port", peer_port); + } + + break; + case SIP_AUTHORIZATION_USERNAME_MISMATCH: + /* Username and digest username does not match. Asterisk uses the From: username for + * authentication. We need the devices to use the same authentication user name until we support + * proper authentication by digest auth name */ + case SIP_AUTHORIZATION_NOT_FOUND: + case SIP_AUTHORIZATION_PEER_NOT_DYNAMIC: + case SIP_AUTHORIZATION_ACL_FAILED: + if (sip_config.always_auth_reject) { + sip_send_response_with_fake_authorization(dialog, &dialog->initial_request); + + if (sip_config.auth_failure_events) { + blob = ast_json_pack("{s: s, s: s, s: s, s: s}", + "peer_status", "Rejected", + "cause", res == SIP_AUTHORIZATION_PEER_NOT_DYNAMIC ? "SIP_AUTHORIZATION_PEER_NOT_DYNAMIC" : "URI_NOT_FOUND", + "address", peer_host, "port", peer_port); + } + } else if (res == SIP_AUTHORIZATION_PEER_NOT_DYNAMIC) { + sip_send_response(dialog, "403 Forbidden", &dialog->initial_request); + + if (sip_config.auth_failure_events) { + blob = ast_json_pack("{s: s, s: s, s: s, s: s}", + "peer_status", "Rejected", "cause", "SIP_AUTHORIZATION_PEER_NOT_DYNAMIC", + "address", peer_host, "port", peer_port); + } + } else { + sip_send_response(dialog, "404 Not Found", &dialog->initial_request); + + if (sip_config.auth_failure_events) { + blob = ast_json_pack("{s: s, s: s, s: s, s: s}", + "peer_status", "Rejected", + "cause", res == SIP_AUTHORIZATION_USERNAME_MISMATCH ? "SIP_AUTHORIZATION_USERNAME_MISMATCH" : "URI_NOT_FOUND", + "address", peer_host, "port", peer_port); + } + } + + break; + case SIP_AUTHORIZATION_BAD_TRANSPORT: + default: + break; + } + + if (peer && peer->endpoint) { + ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); + } + } + + if (peer) { + ao2_t_cleanup(peer, "drop peer"); + } + + return res; +} + +/* Parse contact header and save registration (peer registration) */ +static int sip_dialog_parse_register(struct sip_dialog *dialog, struct sip_peer *peer, struct sip_request *request, int *address_changed) +{ + char *user, *expires, *domain, *protocol, *first_contact; + const char *useragent; + struct ast_sockaddr old_address; + int expiry, iter, wildcard, single_binding; + enum ast_transport transport; + + expires = ast_strdupa(sip_request_get_header(request, "Expires")); + + if (!ast_strlen_zero(expires)) { + expiry = atoi(expires); + } else { + expiry = 0; + } + + wildcard = FALSE; + single_binding = FALSE; + + first_contact = NULL; + iter = 0; + + for (;;) { + char *contact = ast_strdupa(sip_request_next_header(request, "Contact", &iter)); + + if (ast_strlen_zero(contact)) { + break; + } + + /* No expires header, try look in Contact: */ + if (ast_strlen_zero(expires) && (expires = strstr(contact, ";expires="))) { + expiry = atoi(expires + 9); + } + + contact = sip_get_in_brackets(contact); + + if (!first_contact) { + first_contact = ast_strdupa(contact); + } + + if (!strcasecmp(contact, "*")) { + wildcard = TRUE; + } else { + single_binding = TRUE; + } + + if (wildcard && (expiry || single_binding)) { + /* Contact header parameter "*" detected, so punt if: Expires header is missing, Expires value is + * not zero, or another Contact header is present. */ + return SIP_PARSE_REGISTER_FAILED; + } + } + + if (expiry > sip_config.max_expiry) { + expiry = sip_config.max_expiry; + } else if (expiry < sip_config.min_expiry && expiry != 0) { + expiry = sip_config.min_expiry; + } + + dialog->expiry = expiry; + + /* 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(first_contact) && ast_strlen_zero(expires)) { + /* If we have an active registration, tell them when the registration is going to expire */ + if (peer->register_expire_sched_id != -1 && !ast_strlen_zero(peer->full_contact)) { + dialog->expiry = ast_sched_when(sip_sched_context, peer->register_expire_sched_id); + } + + return SIP_PARSE_REGISTER_QUERY; + } else if (!strcmp(first_contact, "*") || !expiry) { /* Unregister this peer */ + dialog->expiry = 0; + + /* This means remove all registrations and return OK */ + AST_SCHED_DEL_UNREF(sip_sched_context, peer->register_expire_sched_id, ao2_t_cleanup(peer, "drop peer")); + sip_peer_expire_register(ao2_t_bump(peer, "bump peer")); + + ast_verb(3, "Unregistered SIP peer '%s'\n", peer->name); + + return SIP_PARSE_REGISTER_UPDATE; + } + + /* Store whatever we got as a contact from the client */ + ast_string_field_set(peer, full_contact, first_contact); + + /* For the 200 OK, we should use the received contact */ + ast_string_field_build(dialog, our_contact, "<%s>", first_contact); + + /* Make sure it's a SIP URL */ + if (ast_strlen_zero(first_contact) || sip_parse_uri(first_contact, "sip:,sips:", &user, &domain, &protocol)) { + ast_log(LOG_NOTICE, "Not a valid SIP contact (missing sip:/sips:) trying to use anyway\n"); + } + + /* handle the transport type specified in Contact header. */ + if (ast_strlen_zero(protocol) || !(transport = sip_str2transport(protocol))) { + transport = dialog->socket.transport; + } + + /* if the peer's socket type is different than the Registration transport type, change it. If it got this far, it is a + * supported type, but check just in case */ + if ((peer->socket.transport != transport) && (peer->transports & transport)) { + sip_socket_set_transport(&peer->socket, transport); + } + + ast_sockaddr_copy(&old_address, &peer->address); + + /* If we were already linked into the sip_peers_by_address container unlink ourselves so nobody can find us */ + if (!ast_sockaddr_isnull(&peer->address) && (!peer->realtime || ast_test_flag(&sip_config.flags[1], SIP_REALTIME_CACHE_PEERS))) { + ao2_t_unlink(sip_peers_by_address, peer, "unlink peer"); + } + + if (!ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT) && !ast_test_flag(&dialog->flags[0], SIP_NAT_RPORT_PRESENT)) { + struct ast_sockaddr address; + + /* use the data provided in the Contact header for call routing */ + ast_debug(1, "Store REGISTER's Contact header for call routing\n"); + + if (ast_sockaddr_resolve_first_af(&address, domain, 0, AST_AF_INET)) { + ast_log(LOG_WARNING, "Invalid domain '%s'\n", domain); + + ast_string_field_set(peer, full_contact, ""); + ast_string_field_set(dialog, our_contact, ""); + + return SIP_PARSE_REGISTER_FAILED; + } + + /* 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, transport == AST_TRANSPORT_TLS ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + } + + ast_sockaddr_copy(&peer->address, &address); + } else { + /* Don't trust the contact field. Just use what they came to us with */ + ast_debug(1, "Store REGISTER's src-IP:port for call routing\n"); + ast_sockaddr_copy(&peer->address, &dialog->received_address); + } + + /* Check that they're allowed to register at this IP */ + if (ast_apply_acl(sip_config.contact_acl, &peer->address, "SIP contact ACL: ") != AST_SENSE_ALLOW || + ast_apply_acl(peer->contact_acl, &peer->address, "SIP contact ACL: ") != AST_SENSE_ALLOW) { + ast_log(LOG_WARNING, "Domain '%s' disallowed by contact ACL (violating IP %s)\n", domain, + ast_sockaddr_stringify_addr(&peer->address)); + + ast_string_field_set(peer, full_contact, ""); + ast_string_field_set(dialog, our_contact, ""); + + return SIP_PARSE_REGISTER_DENIED; + } + + /* if the Contact header information copied into peer->address matches the received address, and the transport types are the + * same, then copy socket data into the peer struct */ + if ((peer->socket.transport == dialog->socket.transport) && !ast_sockaddr_cmp(&peer->address, &dialog->received_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 || ast_test_flag(&peer->flags[1], SIP_REALTIME_CACHE_PEERS)) { + ao2_t_link(sip_peers_by_address, peer, "unlink peer"); + } + + /* Save SIP options profile */ + peer->options = dialog->options; + + if (!ast_strlen_zero(user) && ast_strlen_zero(peer->username)) { + ast_string_field_set(peer, username, user); + } + + AST_SCHED_DEL_UNREF(sip_sched_context, peer->register_expire_sched_id, ao2_t_cleanup(peer, "drop peer")); + + if (peer->realtime && !ast_test_flag(&peer->flags[1], SIP_REALTIME_CACHE_PEERS)) { + peer->register_expire_sched_id = -1; + } else { + if ((peer->register_expire_sched_id = ast_sched_add(sip_sched_context, + (expiry + 10) * 1000, sip_peer_expire_register, ao2_t_bump(peer, "bump peer"))) == -1) { + ao2_t_cleanup(peer, "drop peer"); + } + } + + if (!sip_parse_path(dialog, peer, request, NULL)) { + /* Tell the dialog to use the Path header in the response */ + ast_set2_flag(&dialog->flags[0], TRUE, SIP_USE_PATH); + } + + /* We might not immediately be able to reconnect via TCP, but try caching it anyhow */ + if (!peer->realtime_from_contact || !sip_config.realtime_update_peer) { + char data[512]; + + if (!sip_route_empty(&peer->path)) { + struct ast_str *path; + + if ((path = sip_route_list(&peer->path, FALSE, 0))) { + ast_db_put("SIP/PeerPath", peer->name, ast_str_buffer(path)); + ast_free(path); + } + } + + if (!ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER)) { + snprintf(data, sizeof(data), "%s %d %s %s", + ast_sockaddr_stringify(&peer->address), expiry, peer->username, peer->full_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); + } + + /* Is this a new IP address for us? */ + if ((*address_changed = ast_sockaddr_cmp(&peer->address, &old_address))) { + ast_verb(3, "Registered SIP peer '%s' at %s\n", peer->name, ast_sockaddr_stringify(&peer->address)); + + /* Clear off-hook counter in case of the on-hook notification not being received */ + peer->offhook = 0; + } + + /* Save REGISTER dialog Call-ID */ + ast_string_field_set(peer, register_call_id, dialog->call_id); + + if (ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER)) { + char *reason = ast_strdupa(sip_request_get_header(request, "Reason")); + + if (!strncmp(reason, "SIP;cause=200;text=", 19)) { + char *device_name, *inactive_load, *active_load, *ext; + + if ((device_name = strstr(reason, " Name="))) { + device_name = ast_strdupa(device_name + 6); + device_name = strsep(&device_name, " "); + } + + if ((active_load = strstr(reason, " ActiveLoad=")) || (active_load = strstr(reason, " Load="))) { + active_load = ast_strdupa(active_load + (!strncmp(active_load, " ActiveLoad=", 12) ? 12 : 6)); + + if ((ext = strstr(active_load, ".loads"))) { + *ext = '\0'; + } + + active_load = strsep(&active_load, " "); + } + + if ((inactive_load = strstr(reason, " InactiveLoad="))) { + inactive_load = ast_strdupa(inactive_load + 14); + + if ((ext = strstr(inactive_load, ".loads"))) { + *ext = '\0'; + } + + inactive_load = strsep(&inactive_load, " "); + } + + ast_string_field_set(peer, cisco_device_name, device_name); + ast_string_field_set(peer, cisco_active_load, active_load); + ast_string_field_set(peer, cisco_inactive_load, inactive_load); + } + } + + /* Save User agent */ + useragent = sip_request_get_header(request, "User-Agent"); + + if (strcasecmp(useragent, peer->useragent)) { + ast_string_field_set(peer, useragent, useragent); + } + + return SIP_PARSE_REGISTER_UPDATE; +} + +/* Add authentication on outbound SIP packet */ +int sip_dialog_handle_authentication(struct sip_dialog *dialog, struct sip_request *response, int method, int init) +{ + dialog->authentication_attempts++; + + if (dialog->authentication_attempts == SIP_MAX_AUTHENTICATION_ATTEMPTS) { + ast_log(LOG_NOTICE, "Failed to authenticate %s with host '%s'\n", + sip_methods[method].name, ast_sockaddr_stringify(&dialog->address)); + return -1; + } + + ast_debug(2, "Authentication attempt %d on %s\n", dialog->authentication_attempts, sip_methods[method].name); + + dialog->authorization_code = response->code; + + if (sip_dialog_parse_authorization(dialog, response, method)) { + /* No way to authenticate */ + return -1; + } + + if (method == SIP_METHOD_MESSAGE) { + if (dialog->record_history) { + sip_history_append(dialog, "MessageAuth", "Try: %d", dialog->authentication_attempts); + } + + return sip_send_message(dialog, FALSE, TRUE); + } else { + /* Now we have a reply digest */ + return sip_send_request(dialog, method, method == SIP_METHOD_INVITE, init, NULL); + } +} + +/* Reset the NEEDREINVITE flag after waiting when we get 491 on a Re-invite to avoid race conditions between Asterisk servers. */ +int sip_dialog_start_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); + + ast_set_flag(&dialog->flags[0], SIP_NEED_REINVITE); + 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_t_cleanup(dialog, "drop dialog"); + + return 0; +} + +/* 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_t_cleanup(dialog, "drop dialog")); + ao2_t_cleanup(dialog, "drop dialog"); + + return 0; +} + +void sip_dialog_stop_need_reinvite(struct sip_dialog *dialog) +{ + ao2_t_bump(dialog, "bump dialog"); + + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_stop_need_reinvite, dialog) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(dialog, "drop dialog"); + } +} + +/* 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_t_cleanup(dialog, "drop dialog"); + + return 0; +} + +/* 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_t_cleanup(dialog, "drop dialog")); + ao2_t_cleanup(dialog, "drop dialog"); + + return 0; +} + +void sip_dialog_stop_reinvite(struct sip_dialog *dialog) +{ + ao2_t_bump(dialog, "bump dialog"); + + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_stop_reinvite, dialog) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(dialog, "drop dialog"); + } +} + +/* Run by the sched thread. */ +static int sip_dialog_provisonal_keepalive(const void *data) +{ + struct sip_dialog *dialog; + const char *status_line; + int res; + + 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 (ast_test_flag(&dialog->flags[2], SIP_PROVISIONAL_KEEPALIVE_SDP)) { + ast_clear_flag(&dialog->flags[2], SIP_PROVISIONAL_KEEPALIVE_SDP); + sip_send_response_with_sdp(dialog, status_line, &dialog->initial_request, SIP_SEND_UNRELIABLE, FALSE, FALSE); + } else { + sip_send_response(dialog, status_line, &dialog->initial_request); + } + + res = SIP_PROVISIONAL_KEEPALIVE_TIMEOUT; + } else { + dialog->provisional_keepalive_sched_id = -1; + res = 0; + } + + ao2_unlock(dialog); + + if (!res) { + ao2_t_cleanup(dialog, "drop dialog"); + } + + return res; +} + +/* 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_t_cleanup(dialog, "drop dialog")); + + ao2_lock(dialog); + + if (dialog->invite_state < SIP_INVITE_COMPLETED) { + /* Provisional keepalive is still needed. */ + ao2_t_bump(dialog, "bump dialog"); + + if ((dialog->provisional_keepalive_sched_id = ast_sched_add(sip_sched_context, SIP_PROVISIONAL_KEEPALIVE_TIMEOUT, + sip_dialog_provisonal_keepalive, dialog)) == -1) { + ao2_t_cleanup(dialog, "drop dialog"); + } + } + + ao2_unlock(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + return 0; +} + +void sip_dialog_sched_provisional_keepalive(struct sip_dialog *dialog, int with_sdp) +{ + ao2_t_bump(dialog, "bump dialog"); + + ast_set2_flag(&dialog->flags[2], with_sdp, SIP_PROVISIONAL_KEEPALIVE_SDP); + + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_sched_provisional_keepalive, dialog) == -1) { + ao2_t_cleanup(dialog, "drop dialog"); + } +} + +/* 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_t_cleanup(dialog, "drop dialog")); + ao2_t_cleanup(dialog, "drop dialog"); + + return 0; +} + +void sip_dialog_cancel_provisional_keepalive(struct sip_dialog *dialog) +{ + ao2_t_bump(dialog, "bump dialog"); + + if (ast_sched_add(sip_sched_context, 0, __sip_dialog_cancel_provisional_keepalive, dialog) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(dialog, "drop dialog"); + } +} + +/* Append to SIP dialog history with arg list */ +void sip_history_append(struct sip_dialog *dialog, const char *type, const char *format, ...) +{ + va_list args; + char event[80]; + struct sip_history *history; + int event_len; + + if (!dialog) { + return; + } + + if (!dialog->record_history && !sip_config.record_history) { + return; + } + + if (dialog->history_count == SIP_MAX_HISTORY) { + history = AST_LIST_REMOVE_HEAD(&dialog->history, next); + dialog->history_count--; + ast_free(history); + } + + snprintf(event, sizeof(event), "%-15s ", type); + event_len = strlen(event); + + va_start(args, format); + vsnprintf(event + event_len, sizeof(event) - event_len, format, args); + va_end(args); + + if ((event_len = strcspn(event, "\r\n"))) { /* Trim up everything after \r or \n */ + event[event_len - 1] = '\0'; + } + + event_len = strlen(event) + 1; + + if (!(history = ast_calloc(1, sizeof(*history) + event_len))) { + return; + } + + memcpy(history->event, event, event_len); + + AST_LIST_INSERT_TAIL(&dialog->history, history, next); + dialog->history_count++; + + if (sip_log_level != -1) { + ast_log_dynamic_level(sip_log_level, "%s\n", event); + } +} + +/* Dump SIP history to debug log file at end of lifespan for SIP dialog */ +static void sip_history_dump(struct sip_dialog *dialog) +{ + struct sip_history *history; + static int error = FALSE; + + if (!dialog) { + return; + } + + if (!sip_debug && !DEBUG_ATLEAST(1)) { + if (!error) { + ast_log(LOG_NOTICE, "You must have debugging enabled (SIP or Asterisk) in order to dump SIP history\n"); + error = TRUE; + } + + return; + } + + if (dialog->history_count) { + int count; + + ast_log(LOG_DEBUG, "\nSIP history for '%s'\n", dialog->call_id); + + if (dialog->subscribe_events) { + ast_log(LOG_DEBUG, " * Subscription\n"); + } else { + ast_log(LOG_DEBUG, " * SIP Call\n"); + } + + count = 0; + + AST_LIST_TRAVERSE(&dialog->history, history, next) { + ast_log(LOG_DEBUG, " %-3.3d. %s\n", ++count, history->event); + } + + ast_log(LOG_DEBUG, "\nEnd of SIP history for '%s'\n", dialog->call_id); + } else { + ast_log(LOG_DEBUG, "Call '%s' has no history\n", dialog->call_id); + } +} + +void sip_extension_state_destroy(int id, void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + ao2_t_cleanup(dialog, "drop dialog"); +} + +/* Callback for the devicestate notification (SUBSCRIBE) support subsystem. If you add an "hint" priority to the extension in the + * dial plan, you will get notifications on device state changes */ +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 */ + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); /* Delete subscription in 32 secs */ + + ast_verb(2, "Extension state: Watcher for hint %s %s. Notify User %s\n", + dialog->exten, state_info->exten_state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", dialog->username); + + dialog->subscribe_events = SIP_SUBSCRIBE_NONE; + sip_history_append(dialog, "Subscribestatus", "%s", + state_info->exten_state == AST_EXTENSION_REMOVED ? "HintRemoved" : "Deactivated"); + + break; + default: + if (ast_test_flag(&dialog->flags[0], SIP_FORCE_STATE_CHANGE)) { + ast_clear_flag(&dialog->flags[0], SIP_FORCE_STATE_CHANGE); + /* 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, S_OR(state_info->presence_subtype, "")); + ast_string_field_set(dialog, last_presence_message, S_OR(state_info->presence_message, "")); + + break; + } + + if (dialog->subscribe_events != SIP_SUBSCRIBE_NONE) { /* Only send state NOTIFY if we know the format */ + if (!dialog->pending_invite_cseq) { + sip_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 */ + ast_set_flag(&dialog->flags[1], SIP_QUEUED_STATE_CHANGE); + } + } + + ast_debug(1, "Extension state changed %s@%s to %s for peer %s %s\n", + dialog->exten, dialog->context, ast_extension_state2str(state_info->exten_state), dialog->username, + ast_test_flag(&dialog->flags[1], SIP_QUEUED_STATE_CHANGE) ? "(queued)" : ""); + + ao2_unlock(dialog); + + return 0; +} diff -durN asterisk-22.3.0.orig/channels/sip/dialplan_apps.c asterisk-22.3.0/channels/sip/dialplan_apps.c --- asterisk-22.3.0.orig/channels/sip/dialplan_apps.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/dialplan_apps.c 2025-04-17 11:11:22.795703702 +1200 @@ -0,0 +1,748 @@ +/* + * 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/message.h" + +#include "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/peers.h" +#include "include/domain.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/chan_tech.h" +#include "include/dialplan_apps.h" + +/*** DOCUMENTATION + + + Change the dtmfmode for a SIP call. + + + + + + + + + + + Changes the dtmfmode for a SIP call. + + + + + 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 => n,SIPAddHeader(P-Asserted-Identity: sip:foo@bar) + same => n,SIPAddHeader(P-Preferred-Identity: sip:bar@foo) + + + same => n,SIPRemoveHeader() + + + same => n,SIPRemoveHeader(P-) + + + same => n,SIPRemoveHeader(P-Asserted-Identity:) + + Always returns 0. + + + + + Page a series of Cisco USECALLMANAGER 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. + + + ***/ + +/* Set the DTMFmode for an outbound SIP call (application) */ +int sip_app_dtmfmode(struct ast_channel *channel, const char *data) +{ + struct sip_dialog *dialog; + + if (!data) { + ast_log(LOG_WARNING, "This application requires the argument: inband, rfc2833\n"); + return 0; + } + + ast_channel_lock(channel); + + if (ast_channel_tech(channel) != &sip_tech) { + ast_log(LOG_WARNING, "Call this application only on SIP incoming calls\n"); + ast_channel_unlock(channel); + + return 0; + } + + if (!(dialog = ast_channel_tech_pvt(channel))) { + ast_channel_unlock(channel); + return 0; + } + + ao2_lock(dialog); + + if (!strcasecmp(data, "rfc2833")) { + ast_clear_flag(&dialog->flags[0], SIP_DTMF); + ast_set_flag(&dialog->flags[0], SIP_DTMF_RFC2833); + + dialog->joint_non_format_cap |= AST_RTP_DTMF; + } else if (!strcasecmp(data, "inband")) { + ast_clear_flag(&dialog->flags[0], SIP_DTMF); + ast_set_flag(&dialog->flags[0], SIP_DTMF_INBAND); + + dialog->joint_non_format_cap &= ~AST_RTP_DTMF; + } else { + ast_log(LOG_WARNING, "Invalid DMTF mode: %s\n", data); + } + + if (dialog->audio_rtp) { + ast_rtp_instance_set_prop(dialog->audio_rtp, AST_RTP_PROPERTY_DTMF, + ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833); + } + + if (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_INBAND || ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + sip_dialog_set_dsp_detect(dialog, TRUE); + } else { + sip_dialog_set_dsp_detect(dialog, FALSE); + } + + ao2_unlock(dialog); + ast_channel_unlock(channel); + + return 0; +} + +/* Add a SIP header to an outbound INVITE */ +int sip_app_addheader(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), "__SIPADDHEADER%.2d", count); + + /* Compare without the leading underscores */ + if (!pbx_builtin_getvar_helper(channel, name + 2)) { + break; + } + } + + if (count <= 50) { + size_t value_len; + + value_len = strlen(data); + value = ast_alloca(value_len + 1); + + ast_get_encoded_str(data, value, value_len + 1); + 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_app_removeheader(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), "SIPADDHEADER", 12) == 0) { + if (remove_all || !strncasecmp(ast_var_value(var), data, data_len)) { + ast_debug(1, "Removing SIP Header \"%s\" as %s\n", ast_var_value(var), ast_var_name(var)); + + AST_LIST_REMOVE_CURRENT(entries); + ast_var_delete(var); + } + } + } + + AST_LIST_TRAVERSE_SAFE_END; + ast_channel_unlock(channel); + + return 0; +} + +enum { + APP_CISCOPAGE_MULTICAST = 1 << 0, + APP_CISCOPAGE_PORT = 1 << 1, + APP_CISCOPAGE_VOLUME = 1 << 2, + APP_CISCOPAGE_DISPLAY = 1 << 3, + APP_CISCOPAGE_INCLUDE_BUSY = 1 << 4, + APP_CISCOPAGE_OFFHOOK = 1 << 5, + APP_CISCOPAGE_BEEP = 1 << 6 +}; + +enum { + APP_CISCOPAGE_ARG_MULTICAST, + APP_CISCOPAGE_ARG_PORT, + APP_CISCOPAGE_ARG_VOLUME, + APP_CISCOPAGE_ARG_DISPLAY, + APP_CISCOPAGE_ARG_ARRAY_SIZE +}; + +AST_APP_OPTIONS(sip_app_ciscopage_opts, BEGIN_OPTIONS + AST_APP_OPTION_ARG('m', APP_CISCOPAGE_MULTICAST, APP_CISCOPAGE_ARG_MULTICAST), + AST_APP_OPTION_ARG('p', APP_CISCOPAGE_PORT, APP_CISCOPAGE_ARG_PORT), + AST_APP_OPTION_ARG('v', APP_CISCOPAGE_VOLUME, APP_CISCOPAGE_ARG_VOLUME), + AST_APP_OPTION_ARG('d', APP_CISCOPAGE_DISPLAY, APP_CISCOPAGE_ARG_DISPLAY), + AST_APP_OPTION('b', APP_CISCOPAGE_INCLUDE_BUSY), + AST_APP_OPTION('o', APP_CISCOPAGE_OFFHOOK), + AST_APP_OPTION('a', APP_CISCOPAGE_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_app_ciscopage(struct ast_channel *channel, const char *data) +{ + char *option_args[APP_CISCOPAGE_ARG_ARRAY_SIZE]; + char *parse, *peer_name, *codec, display[64]; + int volume, port, multicast, include_busy, offhook, beep; + struct ast_format *format; + AST_LIST_HEAD_NOLOCK(, sip_page_target) targets; + struct sip_page_target *target; + RAII_VAR(struct ast_str *, content, NULL, ast_free_ptr); + struct ast_rtp_instance *multicast_rtp; + struct ast_sockaddr our_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; + } + + if (!ast_strlen_zero(args.options)) { + ast_app_parse_options(sip_app_ciscopage_opts, &options, option_args, args.options); + } else { + memset(&options, 0, sizeof(options)); + } + + if (ast_test_flag(&options, APP_CISCOPAGE_MULTICAST) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_MULTICAST])) { + if (!ast_sockaddr_parse(&our_address, option_args[APP_CISCOPAGE_ARG_MULTICAST], PARSE_PORT_FORBID)) { + ast_log(LOG_ERROR, "Invalid IP address '%s'\n", option_args[APP_CISCOPAGE_ARG_MULTICAST]); + return -1; + } + + if (!ast_sockaddr_is_ipv4_multicast(&our_address)) { + ast_log(LOG_ERROR, "IP address '%s' is not multicast\n", option_args[APP_CISCOPAGE_ARG_MULTICAST]); + return -1; + } + + multicast = TRUE; + } else { + multicast = FALSE; + } + + if (ast_test_flag(&options, APP_CISCOPAGE_PORT) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_PORT])) { + port = strtol(option_args[APP_CISCOPAGE_ARG_PORT], NULL, 10); + + if (port < 20480 || port > 32768 || port % 2) { + ast_log(LOG_ERROR, "Invalid port option '%s'\n", option_args[APP_CISCOPAGE_ARG_PORT]); + return -1; + } + } else { + port = 20480; + } + + if (ast_test_flag(&options, APP_CISCOPAGE_VOLUME) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_VOLUME])) { + volume = strtol(option_args[APP_CISCOPAGE_ARG_VOLUME], NULL, 10); + + if (volume < 1 || volume > 100) { + ast_log(LOG_ERROR, "Invalid volume option '%s'\n", option_args[APP_CISCOPAGE_ARG_VOLUME]); + return -1; + } + } else { + volume = -1; + } + + if (ast_test_flag(&options, APP_CISCOPAGE_DISPLAY) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_DISPLAY])) { + ast_xml_escape(option_args[APP_CISCOPAGE_ARG_DISPLAY], display, sizeof(display)); + } else { + display[0] = '\0'; + } + + beep = !!ast_test_flag(&options, APP_CISCOPAGE_BEEP); + include_busy = !!ast_test_flag(&options, APP_CISCOPAGE_INCLUDE_BUSY); + offhook = !!ast_test_flag(&options, APP_CISCOPAGE_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; + } + + res = -1; + multicast_rtp = NULL; + content = ast_str_create(4096); + + 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 = NULL; + struct ast_sockaddr their_address; + + if (!(peer = sip_peer_find(peer_name, TRUE, FALSE))) { + ast_log(LOG_ERROR, "No such peer '%s'\n", peer_name); + continue; + } + + if (!ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER)) { + ast_log(LOG_ERROR, "Peer '%s' does not have cisco_usecallmanager enabled\n", peer->name); + ao2_t_cleanup(peer, "drop peer"); + + continue; + } + + if (ast_sockaddr_isnull(&peer->address)) { + ast_log(LOG_ERROR, "Peer '%s' is not registered\n", peer->name); + ao2_t_cleanup(peer, "drop peer"); + + continue; + } + + if ((peer->offhook || peer->ringing || peer->inuse || peer->do_not_disturb) && !include_busy) { + ao2_t_cleanup(peer, "drop peer"); + continue; + } + + if (!multicast) { + sip_get_our_address(peer->socket.transport, &peer->address, &our_address); + ast_sockaddr_copy(&their_address, &peer->address); + ast_sockaddr_set_port(&their_address, port); + + if (!(rtp = ast_rtp_instance_new(peer->rtp_engine, sip_sched_context, &our_address, NULL))) { + ao2_t_cleanup(peer, "drop peer"); + goto cleanup; + } + + ast_rtp_instance_set_write_format(rtp, ast_channel_readformat(channel)); + ast_rtp_instance_set_remote_address(rtp, &their_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, FALSE, SIP_METHOD_REFER, NULL, 0))) { + ast_log(LOG_ERROR, "Unable to build sip dialog data for refer (memory/socket error)\n"); + ao2_t_cleanup(peer, "drop peer"); + + if (rtp) { + ast_rtp_instance_destroy(rtp); + } + + continue; + } + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + + ao2_t_cleanup(dialog, "drop dialog"); + ao2_t_cleanup(peer, "drop peer"); + + if (rtp) { + ast_rtp_instance_destroy(rtp); + } + + continue; + } + + ast_str_reset(content); + + if (!ast_strlen_zero(display)) { + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "notify_display\n"); + ast_str_append(&content, 0, "%s\n", display); + ast_str_append(&content, 0, "10\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "1\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + } + + if (beep) { + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "DtZipZip\n"); + ast_str_append(&content, 0, "all\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + } + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\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"); + ast_str_append(&content, 0, "%s\n", codec); + ast_str_append(&content, 0, "receive\n"); + ast_str_append(&content, 0, "
%s
\n", ast_sockaddr_stringify_fmt(&our_address, AST_SOCKADDR_STR_ADDR)); + ast_str_append(&content, 0, "%d\n", port); + ast_str_append(&content, 0, "
\n"); + ast_str_append(&content, 0, "
\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + sip_send_refer_with_content(dialog, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(dialog, "drop dialog"); + + if (!(target = ast_calloc(1, sizeof(*target)))) { + ao2_t_cleanup(peer, "drop peer"); + + if (rtp) { + ast_rtp_instance_destroy(rtp); + } + + goto cleanup; + } + + if (offhook) { + ao2_lock(peer); + peer->offhook += 1; + 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 (multicast) { + ast_sockaddr_set_port(&our_address, port); + + if (!(multicast_rtp = ast_rtp_instance_new("multicast", sip_sched_context, &sip_config.bind_address, "basic"))) { + goto cleanup; + } + + ast_rtp_instance_set_write_format(multicast_rtp, ast_channel_readformat(channel)); + ast_rtp_instance_set_remote_address(multicast_rtp, &our_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)) { + if (frame) { + 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, FALSE, SIP_METHOD_REFER, NULL, 0))) { + ast_log(LOG_ERROR, "Unable to build sip dialog data for refer (memory/socket error)\n"); + } else { + if (sip_dialog_build_from_peer(dialog, target->peer)) { + sip_dialog_unlink(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + dialog = NULL; + } + } + + if (dialog) { + ast_str_reset(content); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + sip_send_refer_with_content(dialog, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(dialog, "drop dialog"); + } + + if (offhook) { + ao2_lock(target->peer); + target->peer->offhook -= 1; + ao2_unlock(target->peer); + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", target->peer->name); + } + + ao2_t_cleanup(target->peer, "drop peer"); + + if (target->rtp) { + ast_rtp_instance_destroy(target->rtp); + } + + ast_free(target); + } + + if (multicast_rtp) { + ast_rtp_instance_destroy(multicast_rtp); + } + + return res; +} diff -durN asterisk-22.3.0.orig/channels/sip/dialplan_funcs.c asterisk-22.3.0/channels/sip/dialplan_funcs.c --- asterisk-22.3.0.orig/channels/sip/dialplan_funcs.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/dialplan_funcs.c 2025-04-17 11:11:22.796703675 +1200 @@ -0,0 +1,883 @@ +/* + * 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 from the Contact: header. + + + R/O Get the Request-URI from the INVITE header. + + + R/O Get the useragent. + + + R/O Get the name of the peer. + + + R/O 1 if T38 is offered or enabled in this channel, + otherwise 0 + + + 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) + + + 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 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. + + + + + + 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. + + + + + 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. + + + + + + + (default) 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). + + + Call limit (call-limit). + + + Configured call level for signalling busy. + + + Current amount of calls. Only available if call-limit is set. + + + 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 + + + A channel variable configured with setvar 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 Cisco USECALLMANAGER peer + + + The line index of the Cisco USECALLMANAGER peer + + + + + + + ***/ + +#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/netsock.h" +#include "include/route.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/domain.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/config.h" +#include "include/chan_tech.h" +#include "include/dialplan_funcs.h" + +int sip_func_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_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")) { + ast_copy_string(buf, + ast_sockaddr_isnull(&dialog->address) ? "" : ast_sockaddr_stringify_addr(&dialog->address), buf_len); + } else if (!strcasecmp(args.option, "recvip")) { + ast_copy_string(buf, + ast_sockaddr_isnull(&dialog->received_address) ? "" : ast_sockaddr_stringify_addr(&dialog->received_address), + buf_len); + } else if (!strcasecmp(args.option, "recvport")) { + ast_copy_string(buf, + ast_sockaddr_isnull(&dialog->received_address) ? "" : ast_sockaddr_stringify_port(&dialog->received_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")) { + if (dialog->initial_request.uri) { + ast_copy_string(buf, dialog->initial_request.uri, buf_len); + } else { + return -1; + } + } 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")) { + ast_copy_string(buf, dialog->t38_state == SIP_T38_DISABLED ? "0" : "1", buf_len); + } else if (!strcasecmp(args.option, "rtpdest")) { + struct ast_sockaddr remote_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_remote_address(rtp, &remote_address); + snprintf(buf, buf_len, "%s", ast_sockaddr_stringify(&remote_address)); + } 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, "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 { + 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 { + res = -1; + } + + return res; +} + +struct ast_custom_function sip_func_checkdomain = { + .name = "CHECKSIPDOMAIN", + .read2 = sip_func_checkdomain_read, +}; + +/* Dial plan function to check if domain is local */ +int sip_func_checkdomain_read(struct ast_channel *channel, const char *function, char *data, struct ast_str **buf, ssize_t max_len) +{ + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "CHECKSIPDOMAIN requires a domain name\n"); + return -1; + } + + ast_str_set(buf, max_len, "%s", sip_domain_check(data, NULL, 0) ? data : ""); + return 0; +} + +struct ast_custom_function sip_func_header = { + .name = "SIP_HEADER", + .read2 = sip_func_header_read, +}; + +/* Read SIP header (dialplan function) */ +int sip_func_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_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_request_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_func_headers = { + .name = "SIP_HEADERS", + .read2 = sip_func_headers_read, +}; + +/* Read unique list of SIP headers (dialplan function) */ +int sip_func_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_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.header[i].name; + + if (!strncasecmp(name, args.pattern, pattern_len)) { + const char *headers; + int name_len; + + name_len = strlen(name); + headers = ast_str_buffer(*buf); + + /* Has the same header been already added? */ + while ((headers = strstr(headers, name)) != NULL) { + /* Found suffix, but is it the full token? */ + if ((headers == ast_str_buffer(*buf) || headers[-1] == ',') && headers[name_len] == ',') { + break; + } + + /* Only suffix matched, go on with the search after the comma. */ + headers += name_len + 1; + } + + /* headers is null if not broken from the loop, hence header not yet added. */ + if (headers == NULL) { + ast_str_append(buf, max_len, "%s,", name); + } + } + } + + ast_str_truncate(*buf, -1); /* Trim the last comma. Safe if empty. */ + ast_channel_unlock(channel); + + return 0; +} + +struct ast_custom_function sip_func_peer = { + .name = "SIPPEER", + .read2 = sip_func_peer_read, + .write = sip_func_peer_write +}; + +/* ${SIPPEER()} Dialplan function - reads peer data */ +int sip_func_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_strdupa(data); + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(peer_name); + AST_APP_ARG(option); + ); + + AST_STANDARD_APP_ARGS(args, parse); + + if (!(peer = sip_peer_find(args.peer_name, TRUE, FALSE))) { + return -1; + } + + if (!strcasecmp(args.option, "ip")) { + 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")) { + sip_peer_get_status(peer, buf); + } else if (!strcasecmp(args.option, "language")) { + ast_str_set(buf, max_len, "%s", peer->language); + } else if (!strcasecmp(args.option, "limit")) { + ast_str_set(buf, max_len, "%d", peer->call_limit); + } else if (!strcasecmp(args.option, "busylevel")) { + ast_str_set(buf, max_len, "%d", peer->busy_level); + } else if (!strcasecmp(args.option, "curcalls")) { + ast_str_set(buf, max_len, "%d", peer->inuse); + } 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")) { + ast_str_set(buf, max_len, "%d", peer->register_expire_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")) { + 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", ast_test_flag(&peer->flags[0], SIP_USE_SRTP) ? "yes" : "no"); + } else if (!strncasecmp(args.option, "chanvar[", 8)) { + char *name; + const char *value; + + name = args.option + 8; + 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 = args.option + 6; /* move past the '[' */ + codec = strsep(&codec, "]"); /* trim trailing ']' if any */ + + 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")) { + 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")) { + ast_str_set(buf, max_len, "%s", peer->register_call_id); + } else if (!strcasecmp(args.option, "ciscodevicename")) { + ast_str_set(buf, max_len, "%s", peer->cisco_device_name); + } else if (!strcasecmp(args.option, "ciscolineindex")) { + ast_str_set(buf, max_len, "%d", peer->cisco_line_index); + } else { + ast_log(LOG_WARNING, "Unknown option '%s'\n", args.option); + } + + ao2_t_cleanup(peer, "drop peer"); + + return 0; +} + +int sip_func_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_t_cleanup(peer, "drop peer"); + + return 0; +} diff -durN asterisk-22.3.0.orig/channels/sip/domain.c asterisk-22.3.0/channels/sip/domain.c --- asterisk-22.3.0.orig/channels/sip/domain.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/domain.c 2025-04-17 11:11:22.796703675 +1200 @@ -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/domain.h" + +/* The SIP domain list */ +struct sip_domains_head sip_domains; + +/* Print domain mode to cli */ +const char *sip_domain_mode2str(int mode) +{ + switch (mode) { + case SIP_DOMAIN_AUTO: + return "(Automatic)"; + case SIP_DOMAIN_CONFIG: + return "(Configured)"; + } + + return ""; +} + +/* Add SIP domain to list of domains we are responsible for */ +int sip_domain_build(const char *config, int lineno, int mode) +{ + struct sip_domain *domain; + char *name, *context; + + if (ast_strlen_zero(config)) { + ast_log(LOG_WARNING, "Invalid domain at line %d\n", lineno); + return -1; + } + + name = ast_strdupa(config); + + if ((context = strchr(name, '@'))) { + *context++ = '\0'; + } + + if (!(domain = ast_calloc(1, sizeof(*domain)))) { + return -1; + } + + ast_copy_string(domain->name, name, sizeof(domain->name)); + + if (!ast_strlen_zero(context)) { + ast_copy_string(domain->context, context, sizeof(domain->context)); + } + + domain->mode = mode; + + AST_LIST_LOCK(&sip_domains); + AST_LIST_INSERT_TAIL(&sip_domains, domain, next); + AST_LIST_UNLOCK(&sip_domains); + + ast_debug(1, "Added local SIP domain '%s'\n", name); + + return 0; +} + +/* Clear our domain list (at reload) */ +void sip_domain_destroy_all(void) +{ + struct sip_domain *domain; + + AST_LIST_LOCK(&sip_domains); + + while ((domain = AST_LIST_REMOVE_HEAD(&sip_domains, next))) { + ast_free(domain); + } + + AST_LIST_UNLOCK(&sip_domains); +} + +/* 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; + int found; + + found = FALSE; + AST_LIST_LOCK(&sip_domains); + + AST_LIST_TRAVERSE(&sip_domains, domain, next) { + if (strcasecmp(domain->name, name)) { + continue; + } + + if (context && context_len && !ast_strlen_zero(domain->context)) { + ast_copy_string(context, domain->context, context_len); + } + + found = TRUE; + break; + } + + AST_LIST_UNLOCK(&sip_domains); + + return found; +} diff -durN asterisk-22.3.0.orig/channels/sip/events.c asterisk-22.3.0/channels/sip/events.c --- asterisk-22.3.0.orig/channels/sip/events.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/events.c 2025-04-17 11:11:22.797703649 +1200 @@ -0,0 +1,149 @@ +/* + * 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/proxy.h" +#include "include/registry.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 id for network change events */ +static struct stasis_subscription *sip_acl_change_subscription; /* subscription id 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_registry_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; + } + + ast_verb(1, "SIP, got a network change message, renewing all SIP registrations\n"); + + if (sip_network_change_sched_id == -1) { + 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_log(LOG_NOTICE, "Reloading chan_sip in response to ACL change event\n"); + ast_mutex_lock(&sip_reload_lock); + + if (sip_reloading) { + ast_verb(3, "Previous SIP reload not yet done\n"); + } else { + sip_reloading = TRUE; + sip_reload_reason = CHANNEL_ACL_RELOAD; + } + + ast_mutex_unlock(&sip_reload_lock); + sip_monitor_restart(); +} + +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 -durN asterisk-22.3.0.orig/channels/sip/fax.c asterisk-22.3.0/channels/sip/fax.c --- asterisk-22.3.0.orig/channels/sip/fax.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/fax.c 2025-04-17 11:11:22.797703649 +1200 @@ -0,0 +1,336 @@ +/* + * 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/netsock.h" +#include "include/route.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/fax.h" + +static int sip_dialog_t38_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 (!ast_test_flag(&dialog->flags[1], SIP_T38_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.bind_address))) { + ast_log(AST_LOG_WARNING, "UDPTL creation failed, disabling T38 for this dialog\n"); + ast_clear_flag(&dialog->flags[1], SIP_T38_SUPPORT); + + return -1; + } + + if (ast_test_flag(&dialog->flags[1], SIP_T38_SUPPORT) == SIP_T38_SUPPORT_UDPTL_REDUNDANCY) { + ast_udptl_set_error_correction_scheme(dialog->udptl, UDPTL_ERROR_CORRECTION_REDUNDANCY); + } else if (ast_test_flag(&dialog->flags[1], SIP_T38_SUPPORT) == SIP_T38_SUPPORT_UDPTL_FEC) { + ast_udptl_set_error_correction_scheme(dialog->udptl, UDPTL_ERROR_CORRECTION_FEC); + } else if (ast_test_flag(&dialog->flags[1], SIP_T38_SUPPORT) == SIP_T38_SUPPORT_UDPTL) { + ast_udptl_set_error_correction_scheme(dialog->udptl, UDPTL_ERROR_CORRECTION_NONE); + } + + 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); + dialog->t38_max_datagram = dialog->peer ? dialog->peer->t38_max_datagram : -1; + + ast_debug(1, "Setting NAT on UDPTL to %s\n", ast_test_flag(&dialog->flags[1], SIP_SYMMETRIC_RTP) ? "on" : "off"); + + ast_udptl_setnat(dialog->udptl, !!ast_test_flag(&dialog->flags[1], SIP_SYMMETRIC_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) +{ + struct ast_control_t38_parameters remote_parameters; + int res; + + if (!ast_test_flag(&dialog->flags[1], SIP_T38_SUPPORT) || !dialog->udptl) { + return -1; + } + + res = 0; + + 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->t38_state == SIP_T38_PEER_REINVITE) { + sip_fax_stop_abort(dialog); + sip_send_response_reliable(dialog, "488 Not Acceptable Here", &dialog->initial_request); + } + + sip_fax_set_state(dialog, SIP_T38_REJECTED); + break; + } else if (dialog->t38_state == SIP_T38_PEER_REINVITE) { + sip_fax_stop_abort(dialog); + dialog->local_t38_parameters = *parameters; + + /* modify our parameters to conform to the peer's parameters, based on the rules in the ITU T.38 + * recommendation. */ + if (!dialog->remote_t38_parameters.fill_bit_removal) { + dialog->local_t38_parameters.fill_bit_removal = FALSE; + } + + if (!dialog->remote_t38_parameters.transcoding_mmr) { + dialog->local_t38_parameters.transcoding_mmr = FALSE; + } + + if (!dialog->remote_t38_parameters.transcoding_jbig) { + dialog->local_t38_parameters.transcoding_jbig = FALSE; + } + + dialog->local_t38_parameters.version = MIN(dialog->local_t38_parameters.version, + dialog->remote_t38_parameters.version); + dialog->local_t38_parameters.rate_management = dialog->remote_t38_parameters.rate_management; + + ast_udptl_set_local_max_ifp(dialog->udptl, dialog->local_t38_parameters.max_ifp); + sip_fax_set_state(dialog, SIP_T38_ENABLED); + + sip_send_response_with_sdp(dialog, "200 OK", &dialog->initial_request, SIP_SEND_CRITICAL, FALSE, FALSE); + } else if (dialog->t38_state != SIP_T38_ENABLED || + (dialog->t38_state == SIP_T38_ENABLED && parameters->request_response == AST_T38_REQUEST_NEGOTIATE)) { + dialog->local_t38_parameters = *parameters; + ast_udptl_set_local_max_ifp(dialog->udptl, dialog->local_t38_parameters.max_ifp); + + sip_fax_set_state(dialog, SIP_T38_LOCAL_REINVITE); + + if (!dialog->pending_invite_cseq) { + sip_send_reinvite_with_sdp(dialog, FALSE, TRUE); + } else if (!ast_test_flag(&dialog->flags[0], SIP_PENDING_BYE)) { + ast_set_flag(&dialog->flags[0], SIP_NEED_REINVITE); + } + } + + break; + case AST_T38_TERMINATED: + case AST_T38_REFUSED: + case AST_T38_REQUEST_TERMINATE: /* Shutdown T38 */ + if (dialog->t38_state == SIP_T38_PEER_REINVITE) { + sip_fax_stop_abort(dialog); + sip_fax_set_state(dialog, SIP_T38_REJECTED); + + sip_send_response_reliable(dialog, "488 Not Acceptable Here", &dialog->initial_request); + } else if (dialog->t38_state == SIP_T38_ENABLED) { + sip_fax_set_state(dialog, SIP_T38_DISABLED); + + sip_send_reinvite_with_sdp(dialog, FALSE, FALSE); + } + + break; + case AST_T38_REQUEST_PARMS: /* Application wants remote's parameters re-sent */ + remote_parameters = dialog->remote_t38_parameters; + + if (dialog->t38_state == SIP_T38_PEER_REINVITE) { + sip_fax_stop_abort(dialog); + + remote_parameters.max_ifp = ast_udptl_get_far_max_ifp(dialog->udptl); + remote_parameters.request_response = AST_T38_REQUEST_NEGOTIATE; + + if (dialog->channel) { + ast_queue_control_data(dialog->channel, AST_CONTROL_T38_PARAMETERS, + &remote_parameters, sizeof(remote_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. */ + res = AST_T38_REQUEST_PARMS; + } + + break; + default: + res = -1; + break; + } + + return res; +} + +/* Change the T38 state on a SIP dialog */ +void sip_fax_set_state(struct sip_dialog *dialog, int t38_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->t38_state == t38_state || !dialog->channel) { + return; + } + + ast_debug(2, "T38 state changed to %u on channel %s\n", + t38_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 (t38_state) { + case SIP_T38_PEER_REINVITE: + parameters = dialog->remote_t38_parameters; + parameters.max_ifp = ast_udptl_get_far_max_ifp(dialog->udptl); + parameters.request_response = AST_T38_REQUEST_NEGOTIATE; + + ast_udptl_set_tag(dialog->udptl, "%s", ast_channel_name(dialog->channel)); + break; + case SIP_T38_ENABLED: + parameters = dialog->remote_t38_parameters; + parameters.max_ifp = ast_udptl_get_far_max_ifp(dialog->udptl); + parameters.request_response = AST_T38_NEGOTIATED; + + ast_udptl_set_tag(dialog->udptl, "%s", ast_channel_name(dialog->channel)); + break; + case SIP_T38_REJECTED: + case SIP_T38_DISABLED: + if (dialog->t38_state == SIP_T38_ENABLED) { + parameters.request_response = AST_T38_TERMINATED; + } else if (dialog->t38_state == SIP_T38_LOCAL_REINVITE) { + parameters.request_response = AST_T38_REFUSED; + + } + + break; + case SIP_T38_LOCAL_REINVITE: + /* wait until we get a peer response before responding to local reinvite */ + break; + } + + dialog->t38_state = t38_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_dialog_t38_abort(const void *data) +{ + struct sip_dialog *dialog; + struct ast_channel *channel; + + dialog = (struct sip_dialog *) data; + dialog->t38_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->t38_state == SIP_T38_PEER_REINVITE) { + /* Still waiting for a response on timeout so reject the offer. */ + sip_fax_set_state(dialog, SIP_T38_REJECTED); + sip_send_response_reliable(dialog, "488 Not Acceptable Here", &dialog->initial_request); + } + + if (channel) { + ast_channel_unlock(channel); + ast_channel_unref(channel); + } + + ao2_unlock(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + 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->t38_sched_id, ao2_t_cleanup(dialog, "drop dialog")); + ao2_t_cleanup(dialog, "drop dialog"); + + return 0; +} + +static void sip_fax_stop_abort(struct sip_dialog *dialog) +{ + ao2_t_bump(dialog, "bump dialog"); + + if (ast_sched_add(sip_sched_context, 0, __sip_fax_stop_abort, dialog) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(dialog, "drop dialog"); + } +} + +/* 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->t38_sched_id, ao2_t_cleanup(dialog, "drop dialog")); + ao2_t_bump(dialog, "bump dialog"); + + if ((dialog->t38_sched_id = ast_sched_add(sip_sched_context, 5000, sip_dialog_t38_abort, dialog) == -1)) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(dialog, "drop dialog"); + } + + ao2_t_cleanup(dialog, "drop dialog"); + + return 0; +} + +void sip_fax_start_abort(struct sip_dialog *dialog) +{ + ao2_t_bump(dialog, "bump dialog"); + + if (ast_sched_add(sip_sched_context, 0, __sip_fax_start_abort, dialog) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(dialog, "drop dialog"); + } +} diff -durN asterisk-22.3.0.orig/channels/sip/handlers.c asterisk-22.3.0/channels/sip/handlers.c --- asterisk-22.3.0.orig/channels/sip/handlers.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/handlers.c 2025-04-17 11:11:45.996085420 +1200 @@ -0,0 +1,5593 @@ +/* + * 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/netsock.h" +#include "include/route.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/domain.h" +#include "include/registry.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/chan_tech.h" +#include "include/handlers.h" +#include "include/security_events.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" + +/* The results from handling an invite request */ +enum { + SIP_INVITE_SUCCESS = 0, + SIP_INVITE_FAILURE, + SIP_INVITE_ERROR, +}; + +/* 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 */ +}; + +static int sip_handle_request(struct sip_dialog *dialog, struct sip_request *request, int *recount, int *no_unlock); +static int sip_handle_request_invite(struct sip_dialog *dialog, struct sip_request *request, int *recount, int *no_unlock); +static int sip_handle_request_ack(struct sip_dialog *dialog, struct sip_request *request); +static int sip_handle_request_update(struct sip_dialog *dialog, struct sip_request *request); +static int sip_handle_request_cancel(struct sip_dialog *dialog, struct sip_request *request); +static int sip_handle_request_bye(struct sip_dialog *dialog, struct sip_request *request); +static int sip_handle_request_subscribe(struct sip_dialog *dialog, struct sip_request *request); +static int sip_handle_request_notify(struct sip_dialog *dialog, struct sip_request *request); +static int sip_handle_request_refer(struct sip_dialog *dialog, struct sip_request *request, int *no_unlock); +static int sip_handle_request_register(struct sip_dialog *dialog, struct sip_request *request); +static int sip_handle_request_options(struct sip_dialog *dialog, struct sip_request *request); +static int sip_handle_request_publish(struct sip_dialog *dialog, struct sip_request *request); +static int sip_handle_request_info(struct sip_dialog *dialog, struct sip_request *request); +static int sip_handle_request_message(struct sip_dialog *dialog, struct sip_request *request); + +static void sip_transfer_blind(struct ast_channel *channel, struct transfer_channel_data *chan_data, + enum ast_transfer_type transfer_type); +static int sip_transfer_attended(struct sip_dialog *dialog, struct ast_channel *channel, uint32_t cseq, int *no_unlock); + +static int sip_handle_invite_replaces(struct sip_dialog *dialog, struct sip_request *request, int *no_unlock, + struct sip_dialog *transfer_dialog, struct ast_channel *transfer_channel); +static int sip_handle_subscribe_feature_event(struct sip_peer *peer, struct sip_request *request, int *feature); +static int sip_handle_notify_dialog(struct sip_dialog *dialog, struct sip_request *request); +static int sip_handle_publish_presence(struct sip_dialog *dialog, struct sip_request *request); +static int sip_handle_refer_remotecc(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer); + +static int sip_remotecc_idivert(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data); +static int sip_remotecc_hlog(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data); +static int sip_remotecc_startrecording(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data); +static int sip_remotecc_stoprecording(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data); +static int sip_remotecc_qrt(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data); +static int sip_remotecc_mcid(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data); + +static void sip_handle_response(struct sip_dialog *dialog, struct sip_request *response); +static void sip_handle_response_invite(struct sip_dialog *dialog, struct sip_request *response); +static void sip_handle_response_update(struct sip_dialog *dialog, struct sip_request *response); +static void sip_handle_response_subscribe(struct sip_dialog *dialog, struct sip_request *response); +static void sip_handle_response_notify(struct sip_dialog *dialog, struct sip_request *response); +static void sip_handle_response_refer(struct sip_dialog *dialog, struct sip_request *response); +static void sip_handle_response_register(struct sip_dialog *dialog, struct sip_request *response); +static void sip_handle_response_options(struct sip_dialog *dialog, struct sip_request *response); +static void sip_handle_response_info(struct sip_dialog *dialog, struct sip_request *response); +static void sip_handle_response_message(struct sip_dialog *dialog, struct sip_request *response); + +/* Handle incoming SIP message - request or response. This is used for all transports (udp, tcp and tcp/tls) */ +int sip_handle_incoming(struct sip_socket *socket, char *data) +{ + struct sip_dialog *dialog; + struct ast_channel *channel; + struct sip_request request; + 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(&request, 0, sizeof(request)); + + 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 */ + request.debug = TRUE; + } + + if (request.debug) { + ast_verbose("\n<--- SIP read from %s://%s --->\n%s\n<------------->\n", + ast_transport2str(socket->transport), ast_sockaddr_stringify(&socket->address), data); + } + + if (sip_config.pedantic_checking) { + sip_compress_headers(data); /* Fix multiline headers */ + } + + if (sip_request_parse(&request, data)) { /* Bad packet, can't parse */ + return FALSE; + } + + sip_parse_via(&request); + 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_request_find_dialog(&request, socket))) { /* returns dialog with a reference only. _NOT_ locked*/ + sip_request_destroy(&request); + ast_mutex_unlock(&sip_netsock_lock); + + return FALSE; + } + + sip_socket_copy(&dialog->socket, socket); + ast_sockaddr_copy(&dialog->received_address, &socket->address); + + if (dialog->logger_callid) { + ast_callid_threadassoc_add(dialog->logger_callid); + } + + /* if we channel an owner, then this request has been authenticated */ + if (dialog->channel) { + request.authenticated = TRUE; + } + + if (dialog->record_history) { /* This is a request or response, note what it was for */ + if (request.response) { + sip_history_append(dialog, "Rx", "%s", request.status_line); + } else { + sip_history_append(dialog, "Rx", "%s %s", sip_methods[request.method].name, request.uri); + } + } + + /* Save useragent of the client */ + useragent = sip_request_get_header(&request, "User-Agent"); + + if (!ast_strlen_zero(useragent) && strcmp(dialog->useragent, useragent)) { + ast_string_field_set(dialog, useragent, useragent); + } + + /* Lock both the dialog and the channel if a channel is present. This will not fail. */ + channel = sip_dialog_lock_with_channel(dialog); + + authenticated = FALSE; + recount = FALSE; + no_unlock = FALSE; + + if (request.response) { + sip_handle_response(dialog, &request); + } else { + if (sip_handle_request(dialog, &request, &recount, &no_unlock)) { + /* Request failed */ + ast_debug(1, "SIP message could not be handled, bad request\n"); + } + + authenticated = request.authenticated; + } + + sip_request_destroy(&request); + 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_t_ref(dialog, -1, "drop dialog"); /* 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_request *request, int *recount, int *no_unlock) +{ + int res; + + /* New SIP request coming in */ + ast_debug(4, "Received '%s', outgoing method is '%s'\n", sip_methods[request->method].name, sip_methods[dialog->method].name); + + 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 || request->method == SIP_METHOD_CANCEL)) { + ast_debug(2, "Got CANCEL or ACK on INVITE with transactions in between\n"); + } else { + ast_debug(1, "Ignoring too old SIP packet cseq %u (expecting cseq >= %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_send_response_with_retry_after(dialog, + "500 Internal Server Error", 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_send_response(dialog, "500 Internal Server Error", request); + } + + return -1; + } + } else if (dialog->incoming_cseq && dialog->incoming_cseq == request->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 SIP message because of retransmit (method %s, cseq %u, our cseq %u)\n", + sip_methods[request->method].name, 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_send_response(dialog, "481 Call/Transaction Does Not Exist", request); + return 0; + } + + 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)) { + struct ast_str *tag = ast_str_alloca(128); + + sip_get_tag(request, "From", &tag); + ast_string_field_set(dialog, remote_tag, ast_str_buffer(tag)); + } + + ast_string_field_build(dialog, last_message, "Rx: %s", sip_methods[request->method].name); + + if (sip_config.pedantic_checking) { + /* 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 (!dialog->initial_request.uri && request->has_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. */ + struct ast_str *tag = ast_str_alloca(128); + + sip_get_tag(request, "To", &tag); + ast_string_field_set(dialog, local_tag, ast_str_buffer(tag)); + + dialog->pending_invite_cseq = dialog->incoming_cseq; + sip_send_response_reliable(dialog, "481 Call/Transaction Does Not Exist", request); + + /* Will cease to exist after ACK */ + return 0; + } else if (request->method != SIP_METHOD_ACK) { + sip_send_response(dialog, "481 Call/Transaction Does Not Exist", request); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return 0; + } + + /* Otherwise, this is an ACK. It will always have a to-tag */ + } + } + + res = 0; + + /* 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: + if (sip_handle_request_invite(dialog, request, recount, no_unlock) == SIP_INVITE_ERROR) { + res = -1; + } else { + res = 0; + } + + 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); + sip_report_security_event(dialog, request, res); + 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: + sip_send_response_with_accept(dialog, "501 Method Not Implemented", request); + + ast_debug(1, "Unsupported SIP method %s from '%s'\n", + sip_methods[request->method].name, ast_sockaddr_stringify(&dialog->address)); + + /* If this is some new method, and we don't have a call, destroy it now */ + if (!dialog->initial_request.uri) { + sip_dialog_set_need_destroy(dialog, "unimplemented method"); + } + + 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_request *request, int *recount, int *no_unlock) +{ + int reinvite; + unsigned int required_options; + struct ast_channel *channel; /* New channel */ + char *replaces, *call_id, *decoded_exten, pickup_exten[AST_MAX_EXTENSION], pickup_context[AST_MAX_CONTEXT]; + struct ast_str *unsupported; + RAII_VAR(struct sip_peer *, auth_peer, NULL, ao2_cleanup); /* Matching Peer */ + RAII_VAR(struct sip_dialog *, transfer_dialog, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, transfer_channel, NULL, ao2_cleanup); + + /* Find out what they support */ + if (!dialog->options) { + dialog->options |= sip_parse_options(request, "Supported", NULL, 0); + } + + /* Find out what they require */ + unsupported = ast_str_alloca(512); + required_options = sip_parse_options(request, "Require", &unsupported, ast_str_size(unsupported)); + + /* If there are any options required that we do not support, then send a 420 with only those unsupported options listed */ + if (ast_str_strlen(unsupported)) { + ast_log(LOG_WARNING, "Received INVITE with unsupported required extension: %s\n", ast_str_buffer(unsupported)); + + sip_send_response_with_unsupported(dialog, "420 Bad Extension", request, ast_str_buffer(unsupported)); + dialog->invite_state = SIP_INVITE_COMPLETED; + + if (!dialog->last_invite_cseq) { + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + } + + return SIP_INVITE_ERROR; + } + + /* The option tags may be present in Supported: or Require: headers Include the Require: option tags for further + * processing as well */ + dialog->options |= required_options; + dialog->required_options = required_options; + + /* Check if this is a loop */ + if (ast_test_flag(&dialog->flags[0], SIP_OUTGOING) && dialog->channel && + (dialog->invite_state != SIP_INVITE_TERMINATED && dialog->invite_state != SIP_INVITE_CONFIRMED) && + 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. */ + int match; + + if (sip_config.pedantic_checking) { + match = !sip_cmp_uri(dialog->initial_request.uri, request->uri); + } else { + match = !strcmp(dialog->initial_request.uri, request->uri); + } + + if (match) { + sip_send_response(dialog, "482 Loop Detected", request); + + dialog->invite_state = SIP_INVITE_COMPLETED; + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return SIP_INVITE_FAILURE; + } 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 *exten; + + ast_debug(2, "Potential spiral detected, original RURI was %s, new RURI is %s\n", + dialog->initial_request.uri, request->uri); + + sip_send_response(dialog, "100 Trying", request); + + exten = ast_strdupa(request->uri); + exten = strsep(&exten, "@"); + + /* Parse out "sip:" */ + if (!strncmp(exten, "sip:", 4)) { + exten += 4; + } else if (!strncmp(exten, "sips:", 5)) { + exten += 5; + } else { + ast_log(LOG_WARNING, "Not a sip: or sips: URI: %s\n", exten); + exten = 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, exten); + ast_queue_control(dialog->channel, AST_CONTROL_BUSY); + + return SIP_INVITE_FAILURE; + } + } + + if (!request->ignore && dialog->pending_invite_cseq) { + if (!ast_test_flag(&dialog->flags[0], SIP_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 the + * 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_send_response_reliable(dialog, "491 Request Pending", request); + + sip_dialog_check_via(dialog, request); + + ast_debug(1, "Got INVITE on '%s' where we already have pending INVITE, deferring that\n", + dialog->call_id); + + /* Don't destroy dialog here */ + return SIP_INVITE_FAILURE; + } + } + + replaces = ast_strdupa(sip_request_get_header(request, "Replaces")); + call_id = NULL; + + pickup_exten[0] = '\0'; + pickup_context[0] = '\0'; + + if (!ast_strlen_zero(replaces)) { + /* We have a replaces header */ + 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); + + sip_send_response_reliable(dialog, "400 Bad Request", request); /* The best way to not accept the transfer */ + sip_dialog_check_via(dialog, request); + sip_request_copy(&dialog->initial_request, request); + + /* Do not destroy existing call */ + return SIP_INVITE_ERROR; + } + + ast_debug(3, "INVITE part of call transfer. Replaces %s\n", replaces); + + call_id = strsep(&replaces, ";"); + + ast_uri_decode(call_id, ast_uri_sip_user); + + to_tag = strstr(replaces, "to-tag="); + from_tag = strstr(replaces, "from-tag="); + + if (to_tag) { + to_tag += 7; + to_tag = strsep(&to_tag, "&;"); + } + + if (from_tag) { + from_tag += 9; + from_tag = strsep(&from_tag, "&;"); + } + + ast_debug(4, "Invite/replaces: Will use Call-ID: '%s' From: tag='%s' To: tag='%s'\n", + call_id, from_tag ? from_tag : "", to_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 *, subscribe_dialog, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, subscribe_channel, NULL, ao2_cleanup); + + call_id += 7; /* Worst case we are looking at \0 */ + + if (!(subscribe_dialog = sip_dialog_find_with_channel(call_id, to_tag, from_tag, &subscribe_channel))) { + ast_log(LOG_NOTICE, "Unable to find subscription '%s'\n", call_id); + sip_send_response_reliable(dialog, "481 Call/Transaction Does Not Exist", request); + + error = TRUE; + } else { + SCOPED_LOCK(lock, subscribe_dialog, ao2_lock, ao2_unlock); + + ast_log(LOG_NOTICE, "Trying to pick up %s@%s\n", subscribe_dialog->exten, subscribe_dialog->context); + + ast_copy_string(pickup_exten, subscribe_dialog->exten, sizeof(pickup_exten)); + ast_copy_string(pickup_context, subscribe_dialog->context, sizeof(pickup_context)); + } + } + + if (!error && ast_strlen_zero(pickup_exten) && + !(transfer_dialog = sip_dialog_find_with_channel(call_id, to_tag, from_tag, &transfer_channel))) { + ast_log(LOG_NOTICE, "Supervised transfer attempted on '%s' tried to replace non-existent call\n", call_id); + + sip_send_response_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_log(LOG_NOTICE, "INVITE with Replaces: into it's own Call-ID: '%s'\n", dialog->call_id); + + sip_send_response_reliable(dialog, "400 Bad Request", request); /* The best way to not accept the transfer */ + error = TRUE; + } + + if (!error && ast_strlen_zero(pickup_exten) && !transfer_channel) { + /* Oops, someting wrong anyway, no owner, no call */ + ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existing Call-ID: '%s'\n", call_id); + + /* Check for better return code */ + sip_send_response_reliable(dialog, "481 Call/Transaction Does Not Exist", request); + error = TRUE; + } + + 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 && + /* 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 */ + ast_channel_state(transfer_channel) != AST_STATE_DOWN) { + ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-ringing or active Call-ID: '%s'\n", call_id); + + sip_send_response_reliable(dialog, "603 Decline", request); + error = TRUE; + } + + if (error) { /* Give up this dialog */ + sip_history_append(dialog, "Xfer", "INVITE/Replace Failed."); + + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + dialog->invite_state = SIP_INVITE_COMPLETED; + + sip_dialog_check_via(dialog, request); + sip_request_copy(&dialog->initial_request, request); + + return SIP_INVITE_ERROR; + } + } + + /* Check if this is an INVITE that sets up a new dialog or a re-invite in an existing dialog */ + if (!request->ignore) { + int reinvite = dialog->initial_request.uri != NULL; + + /* 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_request_copy(&dialog->initial_request, request); /* Save this INVITE as the transaction basis */ + sip_parse_ok_contact(dialog, request); /* Parse new contact both for existing (re-invite) and new calls. */ + + if (!dialog->channel) { /* Not a re-invite */ + if (request->debug) { + ast_verb(3, "Using INVITE request as basis request for '%s'\n", dialog->call_id); + } + + if (reinvite) { + sip_history_append(dialog, "Invite", "New call: %s", dialog->call_id); + } + } else { /* Re-invite on existing call */ + ast_clear_flag(&dialog->flags[0], SIP_OUTGOING); /* This is now an inbound dialog */ + + if (sip_parse_remote_party_id(dialog, request)) { + sip_dialog_queue_connected_line_update(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_T38_INITIATE, TRUE)) { + if (!ast_strlen_zero(sip_request_get_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_send_response_reliable(dialog, "415 Unsupported Media Type", request); + } else { + sip_send_response_reliable(dialog, "488 Not Acceptable Here", request); + } + + if (!dialog->last_invite_cseq) { + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + } + + return SIP_INVITE_ERROR; + } + + 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); + + ast_debug(1, "No sdp\n"); + + /* 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 os on remote + * hold, we go back to off hold */ + if (ast_test_flag(&dialog->flags[1], SIP_ONHOLD)) { + ast_queue_unhold(dialog->channel); + /* Activate a re-invite */ + ast_queue_frame(dialog->channel, &ast_null_frame); + sip_dialog_change_onhold(dialog, request, FALSE, FALSE); + } + } + + if (dialog->record_history) { /* This is a response, note what it was for */ + sip_history_append(dialog, "ReInv", "Re-invite received"); + } + } + } else if (request->debug) { + ast_verb(3, "Ignoring this INVITE request\n"); + } + + channel = NULL; + reinvite = FALSE; + + if (!dialog->last_invite_cseq && !request->ignore && !dialog->channel) { + char *contact, *uri; + int res; + + /* This is a new invite. Handle authentication if this is our first invite */ + sip_dialog_set_allowed_methods(dialog, request); + res = sip_dialog_check_peer_authorization(dialog, request, &auth_peer, SIP_SEND_RELIABLE); + + if (res == SIP_AUTHORIZATION_CHALLENGE_SENT) { + dialog->invite_state = SIP_INVITE_COMPLETED; /* Needs to restart in another INVITE transaction */ + return SIP_INVITE_SUCCESS; + } + + if (res != SIP_AUTHORIZATION_SUCCESSFUL) { /* Something failed in authentication */ + sip_report_security_event(dialog, request, res); + + sip_send_response_with_authorization_failure(dialog, request, res, SIP_SEND_RELIABLE); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + dialog->invite_state = SIP_INVITE_COMPLETED; + + return SIP_INVITE_FAILURE; + } + + /* Successful authentication and peer matching so record the peer related to this dialog (for easy + * access to peer settings) */ + if (dialog->peer) { + ao2_t_cleanup(dialog->peer, "drop peer"); + dialog->peer = NULL; + } + + if (auth_peer) { + dialog->peer = ao2_t_bump(auth_peer, "bump peer"); + } + + request->authenticated = TRUE; + + /* 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_T38_INITIATE, TRUE)) { + /* 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_request_get_header(request, "Content-Encoding"))) { + sip_send_response_reliable(dialog, "415 Unsupported Media Type", request); + } else { + /* Unacceptable codecs */ + sip_send_response_reliable(dialog, "488 Not Acceptable Here", request); + } + + ast_debug(1, "No compatible codecs for this SIP call\n"); + + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + dialog->invite_state = SIP_INVITE_COMPLETED; + + return SIP_INVITE_FAILURE; + } + } 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"); + } + + /* Initialize the context if it hasn't been already */ + if (ast_strlen_zero(dialog->context)) { + ast_string_field_set(dialog, context, sip_config.context); + } + + /* Check number of concurrent calls -vs- incoming limit HERE */ + ast_debug(1, "Checking SIP call limits for device %s\n", dialog->username); + + if (sip_dialog_update_call_counter(dialog, SIP_INC_CALL_LIMIT)) { + ast_log(LOG_NOTICE, "Failed to place call for device %s, too many calls\n", dialog->username); + sip_send_response_reliable(dialog, "480 Temporarily Unavailable", request); + + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + dialog->invite_state = SIP_INVITE_COMPLETED; + + return SIP_AUTHORIZATION_SESSION_LIMIT; + } + + contact = ast_strdupa(sip_request_get_header(request, "Contact")); + uri = sip_remove_uri_parameters(sip_get_in_brackets(contact)); + + if (!ast_strlen_zero(uri)) { + ast_string_field_set(dialog, uri, uri); + } + + sip_dialog_build_contact(dialog, request); /* Build our contact header */ + + if (dialog->audio_rtp) { + ast_rtp_instance_set_prop(dialog->audio_rtp, AST_RTP_PROPERTY_DTMF, + ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833); + ast_rtp_instance_set_prop(dialog->audio_rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, + ast_test_flag(&dialog->flags[0], SIP_RFC2833_COMPENSATE)); + } + + res = sip_dialog_get_destination(dialog, &dialog->initial_request); /* Get destination right away */ + + if (!call_id && (res != SIP_DESTINATION_EXTEN_FOUND)) { /* No matching extension found */ + switch (res) { + case SIP_DESTINATION_INVALID_URI: + sip_send_response_reliable(dialog, "416 Unsupported URI Scheme", request); + break; + case SIP_DESTINATION_EXTEN_MATCH_MORE: + if (ast_test_flag(&dialog->flags[1], SIP_ALLOW_OVERLAP) == SIP_ALLOW_OVERLAP_YES) { + sip_send_response_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_send_response_reliable(dialog, "404 Not Found", request); + + decoded_exten = ast_strdupa(dialog->exten); + ast_uri_decode(decoded_exten, ast_uri_sip_user); + + ast_log(LOG_NOTICE, "Call from '%s' (%s) to extension '%s' rejected because extension not found in context '%s'\n", + S_OR(dialog->username, dialog->peer_name), + ast_sockaddr_stringify(&dialog->received_address), decoded_exten, dialog->context); + + sip_report_failed_acl(dialog, "no_extension_match"); + break; + case SIP_DESTINATION_REFUSED: + default: + sip_send_response_reliable(dialog, "403 Forbidden", request); + } + + sip_dialog_update_call_counter(dialog, SIP_DEC_CALL_LIMIT); + + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + dialog->invite_state = SIP_INVITE_COMPLETED; + + return SIP_INVITE_FAILURE; + } else { + /* If no extension was specified, use the s one Basically for calling to IP/Host name only */ + if (ast_strlen_zero(dialog->exten)) { + ast_string_field_set(dialog, exten, "s"); + } + + /* Initialize our tag */ + sip_dialog_build_local_tag(dialog); + + if (sip_stimer_handle_invite(dialog, request, reinvite)) { + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + dialog->invite_state = SIP_INVITE_COMPLETED; + + return SIP_INVITE_ERROR; + } + + /* First invitation - create the channel. Allocation failures are handled below. */ + channel = 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 (channel) { + sip_parse_diversion(dialog, request, FALSE); + sip_set_redirecting(dialog); + } + } + } else { + ast_debug(2, "Got a SIP re-transmit of INVITE for '%s'\n", dialog->call_id); + + if (!request->ignore) { + reinvite = TRUE; + } + + if (sip_stimer_handle_invite(dialog, request, reinvite)) { + dialog->invite_state = SIP_INVITE_COMPLETED; + + if (!dialog->last_invite_cseq) { + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + } + + return SIP_INVITE_ERROR; + } + + sip_parse_diversion(dialog, request, FALSE); + + if ((channel = dialog->channel)) { + sip_set_redirecting(dialog); + } + } + + /* Check if OLI/ANI-II is present in From: */ + sip_parse_oli(dialog, request); + + if (reinvite && dialog->stimer) { + sip_stimer_restart(dialog); + } + + if (!request->ignore && dialog) { + dialog->last_invite_cseq = request->cseq; + } + + if (channel && call_id) { /* Attended transfer or call pickup - we're the target */ + if (!ast_strlen_zero(pickup_exten)) { + sip_history_append(dialog, "Xfer", "INVITE/Replace received"); + + /* Let the caller know we're giving it a shot */ + sip_send_response(dialog, "100 Trying", request); + + dialog->invite_state = SIP_INVITE_PROCEEDING; + ast_setstate(channel, AST_STATE_RING); + + /* Do the pickup itself */ + ast_channel_unlock(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(channel, pickup_exten, pickup_context); + + /* Now we're either masqueraded or we failed to pickup, in either case we... */ + ast_hangup(channel); + ao2_lock(dialog); /* dialog is expected to remain locked on return, so re-lock it */ + + return SIP_INVITE_FAILURE; + } else { + /* Go and take over the target call */ + ast_debug(4, "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 (channel) { /* We have a call either a new call or an old one (RE-INVITE) */ + enum ast_channel_state state; + RAII_VAR(struct ast_features_pickup_config *, pickup_config, NULL, ao2_cleanup); + + if (!(pickup_config = ast_get_chan_features_pickup_config(channel))) { + ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n"); + } + + state = ast_channel_state(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, "New call %s is still down, sending trying\n", ast_channel_name(channel)); + + sip_send_response_provisional(dialog, "100 Trying", request, FALSE); + + dialog->invite_state = SIP_INVITE_PROCEEDING; + ast_setstate(channel, AST_STATE_RING); + + /* Call to extension start pbx on this call */ + if (!pickup_config || strcmp(dialog->exten, pickup_config->pickupexten)) { + enum ast_pbx_result pbx_res = ast_pbx_start(channel); + + switch (pbx_res) { + case AST_PBX_FAILED: + ast_log(LOG_WARNING, "Failed to start PBX\n"); + + sip_dialog_set_already_gone(dialog); + dialog->invite_state = SIP_INVITE_COMPLETED; + + sip_send_response_reliable(dialog, "503 Service Unavailable", request); + + return SIP_INVITE_FAILURE; + + case AST_PBX_CALL_LIMIT: + ast_log(LOG_WARNING, "Failed to start PBX (call limit reached)\n"); + + sip_dialog_set_already_gone(dialog); + dialog->invite_state = SIP_INVITE_COMPLETED; + + sip_send_response_reliable(dialog, "480 Temporarily Unavailable", request); + + return SIP_INVITE_FAILURE; + + case AST_PBX_SUCCESS: + /* nothing to do */ + break; + } + + if (pbx_res != AST_PBX_SUCCESS) { + /* Unlock locks so ast_hangup can do its magic */ + ast_channel_unlock(channel); + *no_unlock = TRUE; + + ao2_unlock(dialog); + ast_hangup(channel); + ao2_lock(dialog); + + channel = NULL; + } + } else { /* Pickup call in call group */ + if (sip_pickup_call(channel)) { + ast_log(LOG_WARNING, "Failed to start group pickup by %s\n", ast_channel_name(channel)); + sip_send_response_reliable(dialog, "480 Temporarily Unavailable", request); + + sip_dialog_set_already_gone(dialog); + ast_channel_hangupcause_set(channel, AST_CAUSE_FAILURE); + + /* Unlock locks so ast_hangup can do its magic */ + ast_channel_unlock(channel); + *no_unlock = TRUE; + + dialog->invite_state = SIP_INVITE_COMPLETED; + + ao2_unlock(dialog); + ast_hangup(channel); + ao2_lock(dialog); + + channel = NULL; + } + } + + break; + case AST_STATE_RING: + sip_send_response_provisional(dialog, "100 Trying", request, FALSE); + dialog->invite_state = SIP_INVITE_PROCEEDING; + + break; + case AST_STATE_RINGING: + sip_send_response_provisional(dialog, "180 Ringing", request, FALSE); + dialog->invite_state = SIP_INVITE_PROCEEDING; + + break; + case AST_STATE_UP: + ast_debug(2, "Call is up: %s\n", ast_channel_name(channel)); + sip_send_response(dialog, "100 Trying", request); + + if (dialog->t38_state == SIP_T38_PEER_REINVITE) { + sip_fax_start_abort(dialog); + } else if (dialog->t38_state == SIP_T38_ENABLED) { + ast_set_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED); + + sip_send_response_with_sdp(dialog, "200 OK", request, + (reinvite ? SIP_SEND_RELIABLE : (request->ignore ? SIP_SEND_UNRELIABLE : SIP_SEND_CRITICAL)), + FALSE, FALSE); + } else if (dialog->t38_state == SIP_T38_DISABLED || dialog->t38_state == SIP_T38_REJECTED) { + /* 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_log(LOG_WARNING, "Target does not support required crypto\n"); + sip_send_response_reliable(dialog, "488 Not Acceptable Here", request); + } else { + ast_set_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED); + + sip_send_response_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); + } + } + + dialog->invite_state = SIP_INVITE_TERMINATED; + break; + default: + ast_log(LOG_WARNING, "Don't know how to handle INVITE in state %u\n", ast_channel_state(channel)); + sip_send_response(dialog, "100 Trying", request); + + break; + } + } else { + if (!request->ignore && dialog && dialog->auto_destruct_sched_id == -1) { + if (!ast_format_cap_count(dialog->joint_format_cap)) { + sip_send_response_reliable(dialog, "488 Not Acceptable Here", request); + } else { + ast_log(LOG_NOTICE, "Unable to create/find SIP channel for this INVITE\n"); + sip_send_response_reliable(dialog, "503 Service Unavailable", request); + } + + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + dialog->invite_state = SIP_INVITE_COMPLETED; + } + } + + return SIP_INVITE_SUCCESS; +} + +static int sip_handle_request_ack(struct sip_dialog *dialog, struct sip_request *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); /* is a response */ + + if (dialog->channel && sip_sdp_find(request)) { + if (sip_sdp_parse(dialog, request, SIP_SDP_T38_NONE, FALSE)) { + return -1; + } + + if (ast_test_flag(&dialog->flags[0], SIP_DIRECT_MEDIA)) { + ast_queue_control(dialog->channel, AST_CONTROL_UPDATE_RTP_PEER); + } + } + + sip_dialog_sched_check_pending(dialog); + } else if (dialog->glare_invite_cseq == request->cseq) { + /* handle ack for the 491 pending sent for glareinvite */ + 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 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_request *request) +{ + if (!dialog->channel) { + sip_send_response(dialog, "481 Call/Transaction Does Not Exist", request); + return 0; + } + + if (sip_parse_remote_party_id(dialog, request)) { + sip_dialog_queue_connected_line_update(dialog, AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER); + } + + sip_send_response(dialog, "200 OK", request); + + return 0; +} + +/* Handle incoming CANCEL request */ +static int sip_handle_request_cancel(struct sip_dialog *dialog, struct sip_request *request) +{ + 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_send_response(dialog, "200 OK", request); + ast_debug(1, "Got 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 (ast_test_flag(&dialog->flags[0], SIP_CALL_COUNTER_INUSE) || ast_test_flag(&dialog->flags[1], SIP_ONHOLD)) { + sip_dialog_update_call_counter(dialog, SIP_DEC_CALL_LIMIT); + } + + 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, SIP_DEFAULT_TIMEOUT); + } + + if (dialog->initial_request.uri) { + struct sip_packet *packet; + + /* 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_packet_cancel_resend(packet); + ao2_t_ref(packet, -1, "drop packet"); + + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + /* Cisco phones fail to include the To tag in the ACK response */ + if (ast_test_flag(&dialog->flags[1], SIP_CISCO_USECALLMANAGER)) { + sip_send_response(dialog, "487 Request Terminated", &dialog->initial_request); + } else { + sip_send_response_reliable(dialog, "487 Request Terminated", &dialog->initial_request); + } + + sip_send_response(dialog, "200 OK", request); + + return 1; + } else { + sip_send_response(dialog, "481 Call Leg Does Not Exist", request); + + return 0; + } +} + +/* Handle incoming BYE request */ +static int sip_handle_request_bye(struct sip_dialog *dialog, struct sip_request *request) +{ + char field[AST_MAX_USER_FIELD], *quality; + struct ast_str *unsupported; + RAII_VAR(struct ast_channel *, bridge_channel, NULL, ast_channel_cleanup); + + /* If we have an INCOMING invite that we haven't answered, terminate that transaction */ + if (dialog->pending_invite_cseq && !ast_test_flag(&dialog->flags[0], SIP_OUTGOING) && !request->ignore) { + sip_send_response_reliable(dialog, "487 Request Terminated", &dialog->initial_request); + } + + sip_packet_pretend_ack(dialog); + dialog->invite_state = SIP_INVITE_TERMINATED; + + sip_request_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); + + if (dialog->channel) { + RAII_VAR(struct ast_channel *, channel_relock, 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); + + bridge_channel = ast_channel_bridge_peer(channel); + channel_relock = sip_dialog_lock_with_channel(dialog); + + if (!channel_relock) { + ast_debug(3, "Unable to reaquire owner channel lock, channel is gone\n"); + return 0; + } + } + + /* Get RTCP quality before end of call */ + if (dialog->audio_rtp) { + if (dialog->record_history) { + if ((quality = ast_rtp_instance_get_quality(dialog->audio_rtp, + AST_RTP_INSTANCE_STAT_FIELD_QUALITY, field, sizeof(field)))) { + sip_history_append(dialog, "RTCPaudio", "Quality:%s", quality); + } + + if ((quality = ast_rtp_instance_get_quality(dialog->audio_rtp, + AST_RTP_INSTANCE_STAT_FIELD_QUALITY_JITTER, field, sizeof(field)))) { + sip_history_append(dialog, "RTCPaudioJitter", "Quality:%s", quality); + } + + if ((quality = ast_rtp_instance_get_quality(dialog->audio_rtp, + AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS, field, sizeof(field)))) { + sip_history_append(dialog, "RTCPaudioLoss", "Quality:%s", quality); + } + + if ((quality = ast_rtp_instance_get_quality(dialog->audio_rtp, + AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT, field, sizeof(field)))) { + sip_history_append(dialog, "RTCPaudioRTT", "Quality:%s", quality); + } + } + + if (dialog->channel) { + struct ast_rtp_instance *rtp; + RAII_VAR(struct ast_channel *, channel_relock, 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); + + rtp = dialog->audio_rtp; + ao2_ref(rtp, +1); + + /* 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, rtp); + ao2_ref(rtp, -1); + + if (bridge_channel) { + ast_channel_lock(bridge_channel); + + if (ast_channel_tech(bridge_channel) == &sip_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); + } + + channel_relock = sip_dialog_lock_with_channel(dialog); + + if (!channel_relock) { + ast_debug(3, "Unable to reacquire owner channel lock, channel is gone\n"); + return 0; + } + } + } + + if (dialog->video_rtp && (quality = ast_rtp_instance_get_quality(dialog->video_rtp, + AST_RTP_INSTANCE_STAT_FIELD_QUALITY, field, sizeof(field)))) { + if (dialog->record_history) { + sip_history_append(dialog, "RTCPvideo", "Quality:%s", quality); + } + + if (dialog->channel) { + 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)))) { + if (dialog->record_history) { + sip_history_append(dialog, "RTCPtext", "Quality:%s", quality); + } + + if (dialog->channel) { + pbx_builtin_setvar_helper(dialog->channel, "RTPTEXTQOS", quality); + } + } + + sip_dialog_stop_rtp(dialog); /* Immediately stop RTP, VRTP and UDPTL as applicable */ + + if (dialog->stimer) { + sip_stimer_stop(dialog); /* Stop Session-Timer */ + } + + sip_parse_reason(dialog, request); + + if (dialog->channel) { + sip_queue_hangup_cause(dialog, ast_channel_hangupcause(dialog->channel)); + ast_debug(3, "Received bye, issuing owner hangup\n"); + } else { + ast_debug(3, "Received bye, no owner, selfdestruct soon\n"); + } + + if (!dialog->final_destruction_scheduled) { + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + dialog->final_destruction_scheduled = TRUE; + } + + ast_clear_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED); + + /* Find out what they require */ + unsupported = ast_str_alloca(512); + sip_parse_options(request, "Require", &unsupported, ast_str_size(unsupported)); + + /* If there are any options required that we do not support, then send a 420 with only those unsupported options listed */ + if (ast_str_strlen(unsupported)) { + sip_send_response_with_unsupported(dialog, "420 Bad Extension", request, ast_str_buffer(unsupported)); + + ast_log(LOG_WARNING, "Received SIP BYE with unsupported required extension: '%s'\n", ast_str_buffer(unsupported)); + } else { + sip_send_response(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 1; +} + +/* Handle incoming SUBSCRIBE request */ +static int sip_handle_request_subscribe(struct sip_dialog *dialog, struct sip_request *request) +{ + const char *event, *accept; + int resubscribe, feature; + RAII_VAR(struct sip_peer *, auth_peer, NULL, ao2_cleanup); + + resubscribe = dialog->subscribe_events != SIP_SUBSCRIBE_NONE && !request->ignore; + + if (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_send_response(dialog, "403 Forbidden", request); + + /* Do not destroy session, since we will break the call if we do */ + ast_debug(1, "Got a subscription request 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, "Got a re-subscribe on existing subscription '%s'\n", dialog->call_id); + } else { + ast_debug(1, "Got a new subscription '%s' (possibly with auth) or retransmission\n", + dialog->call_id); + } + } + } + + if (!request->ignore && !resubscribe) { /* Set up dialog, new subscription */ + const char *to = sip_request_get_header(request, "To"); + struct ast_str *tag; + + sip_dialog_set_allowed_methods(dialog, request); + + tag = ast_str_alloca(128); + + sip_get_tag(request, "To", &tag); + + /* 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(to) && ast_str_strlen(tag)) { + if (request->debug) { + ast_verb(3,"Received resubscription for a dialog we no longer know about. Telling remote side to subscribe again\n"); + } + + sip_send_response(dialog, "481 Subscription Does Not Exist", request); + sip_dialog_set_need_destroy(dialog, "subscription does not exist"); + + return 0; + } + + /* Use this as the basis */ + if (request->debug) { + ast_verb(3, "Creating new subscription\n"); + } + + sip_request_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_request_get_header(request, "Event"); /* Get Event package name */ + + /* Find parameters to Event: header value and remove them for now */ + if (ast_strlen_zero(event)) { + sip_send_response(dialog, "489 Bad Event", request); + + ast_debug(2, "Received SIP subscribe for unknown event package: \n"); + sip_dialog_set_need_destroy(dialog, "unknown event package in subscribe"); + + return 0; + } + + /* 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_events == SIP_SUBSCRIBE_NONE || resubscribe) { + int res = sip_dialog_check_peer_authorization(dialog, request, &auth_peer, SIP_SEND_UNRELIABLE); + + /* if an authentication response was sent, we are done here */ + if (res == SIP_AUTHORIZATION_CHALLENGE_SENT) {/* auth_peer = NULL here */ + return 0; + } + + if (res != SIP_AUTHORIZATION_SUCCESSFUL) { + sip_send_response_with_authorization_failure(dialog, request, res, SIP_SEND_UNRELIABLE); + sip_dialog_set_need_destroy(dialog, "authentication failed"); + + return 0; + } + } + + /* At this point, we hold a reference to auth_peer (if not NULL). It must be released when done. */ + /* Check if this device is allowed to subscribe at all */ + if (!ast_test_flag(&dialog->flags[1], SIP_ALLOW_SUBSCRIBE)) { + sip_send_response(dialog, "403 Forbidden", request); + sip_dialog_set_need_destroy(dialog, "subscription not allowed"); + + return 0; + } + + /* Get full contact header - this needs to be used as a request URI in NOTIFY's */ + sip_parse_ok_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); + } + + accept = sip_request_get_header(request, "Accept"); + feature = SIP_FEATURE_NONE; + + if (!strcmp(event, "presence") || !strcmp(event, "dialog")) { /* Presence, RFC 3842 */ + int res, subscribe_events; + + /* 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_send_response(dialog, "416 Unsupported URI Scheme", request); + } else { + sip_send_response(dialog, "404 Not Found", request); + } + + sip_dialog_set_need_destroy(dialog, "subscription target not found"); + return 0; + } + + subscribe_events = SIP_SUBSCRIBE_NONE; + + if (!ast_strlen_zero(accept)) { + if (!strcmp(event, "dialog")) { + if (strstr(accept, "application/dialog-info+xml")) { + subscribe_events = SIP_SUBSCRIBE_DIALOG_INFO_XML; + } + } else if (!strcmp(event, "presence")) { + if (strstr(accept, "application/pidf+xml") || strstr(accept, "application/cpim-pidf+xml")) { + subscribe_events = SIP_SUBSCRIBE_PIDF_XML; + } + } + } + + if (ast_strlen_zero(accept)) { + /* If the subscribe_events field is not already set, and there is no accept header */ + if (dialog->subscribe_events == SIP_SUBSCRIBE_NONE) { + ast_log(LOG_WARNING,"No Accept header\n"); + + sip_send_response(dialog, "489 Bad Event", request); + sip_dialog_set_need_destroy(dialog, "no Accept header"); + + return 0; + } + + /* If dialog->subscribe_events 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 */ + } else if (subscribe_events == SIP_SUBSCRIBE_NONE) { + /* Can't find a format for events that we know about */ + sip_send_response(dialog, "489 Bad Event", request); + sip_dialog_set_need_destroy(dialog, "unrecognized format"); + + ast_log(LOG_WARNING, "Unrecognized format for %s: %s\n", event, accept); + return 0; + } else { + dialog->subscribe_events = subscribe_events; + } + } else if (!strcmp(event, "message-summary")) { + if (!strstr(accept, "application/simple-message-summary")) { + /* Format requested that we do not support */ + sip_send_response(dialog, "406 Not Acceptable", request); + + ast_debug(2, "Received SIP mailbox subscription for unknown format\n"); + sip_dialog_set_need_destroy(dialog, "unknown format"); + + return 0; + } + + /* Looks like they actually want a mailbox status. This version of Asterisk supports mailbox subscriptions + * The subscribed URI needs to exist in the dial plan. In most devices, this is configurable to the + * voicemailmain extension you use. */ + if (!auth_peer || AST_LIST_EMPTY(&auth_peer->mailboxes)) { + if (!auth_peer) { + sip_send_response(dialog, "404 Not Found", request); + } else { + ast_log(LOG_NOTICE, "Received MWI subscribe for peer without mailbox: %s\n", S_OR(auth_peer->name, "")); + sip_send_response(dialog, "404 Not Found", request); + } + + sip_dialog_set_need_destroy(dialog, "received 404 response"); + + return 0; + } + + dialog->subscribe_events = SIP_SUBSCRIBE_MESSAGE_SUMMARY; + + if (ast_test_flag(&auth_peer->flags[1], SIP_SUBSCRIBE_MWI_ONLY)) { + ao2_unlock(dialog); + sip_peer_update_mailboxes(auth_peer); + ao2_lock(dialog); + } + + if (auth_peer->mwi_dialog != dialog) { /* Destroy old PVT if this is a new one */ + /* We only allow one subscription per peer */ + if (auth_peer->mwi_dialog) { + sip_dialog_unlink(auth_peer->mwi_dialog); + ao2_t_cleanup(auth_peer->mwi_dialog, "drop dialog"); + } + + auth_peer->mwi_dialog = ao2_t_bump(dialog, "bump dialog"); + } + + if (dialog->peer != auth_peer) { + if (dialog->peer) { + ao2_t_cleanup(dialog->peer, "drop peer"); + } + + dialog->peer = ao2_t_bump(auth_peer, "bump peer"); + } + } else if (!strcmp(event, "as-feature-event")) { + if (!auth_peer || sip_handle_subscribe_feature_event(auth_peer, request, &feature)) { + sip_send_response(dialog, "489 Bad Event", request); + sip_dialog_set_need_destroy(dialog, "unknown format"); + + return 0; + } + + dialog->subscribe_events = SIP_SUBSCRIBE_FEATURE_EVENTS; + + if (auth_peer->feature_events_dialog != dialog) { /* Destroy old PVT if this is a new one */ + /* We only allow one subscription per peer */ + if (auth_peer->feature_events_dialog) { + sip_dialog_unlink(auth_peer->feature_events_dialog); + ao2_t_cleanup(auth_peer->feature_events_dialog, "drop dialog"); + } + + auth_peer->feature_events_dialog = ao2_t_bump(dialog, "bump dialog"); + } + + if (dialog->peer != auth_peer) { + if (dialog->peer) { + ao2_t_cleanup(dialog->peer, "drop peer"); + } + + dialog->peer = ao2_t_bump(auth_peer, "bump peer"); + } + } else { /* At this point, Asterisk does not understand the specified event */ + ast_debug(2, "Received SIP subscribe for unknown event package: %s\n", event); + + sip_send_response(dialog, "489 Bad Event", request); + sip_dialog_set_need_destroy(dialog, "unknown event package"); + + return 0; + } + + if (!request->ignore) { + dialog->last_invite_cseq = request->cseq; + } + + if (!dialog->need_destroy) { + const char *expires = sip_request_get_header(request, "Expires"); + + if (ast_strlen_zero(expires)) { + dialog->expiry = sip_config.default_expiry; + } else { + dialog->expiry = atoi(expires); + } + + /* check if the requested expiry-time is within the approved limits from sip.conf */ + if (dialog->expiry > sip_config.max_subscribe_expiry) { + dialog->expiry = sip_config.max_subscribe_expiry; + } else if (dialog->expiry < sip_config.min_subscribe_expiry && dialog->expiry > 0) { + ast_log(LOG_WARNING, + "Received subscription for Expire header less than 'subminexpire' limit: %d\n", dialog->expiry); + + sip_send_response_with_min_expires(dialog, "423 Interval Too Small", request, sip_config.min_subscribe_expiry); + sip_dialog_set_need_destroy(dialog, "Expires is less that the min expires allowed."); + + return 0; + } + + if (dialog->subscribe_events == SIP_SUBSCRIBE_DIALOG_INFO_XML || dialog->subscribe_events == SIP_SUBSCRIBE_PIDF_XML) { + ast_debug(2, "%s extension state subscription %s@%s for %s\n", + dialog->expiry ? "Adding" : "Removing", dialog->exten, dialog->context, dialog->username); + } else if (dialog->subscribe_events == SIP_SUBSCRIBE_MESSAGE_SUMMARY) { + ast_debug(2, "%s mwi event subscription for %s\n", dialog->expiry ? "Adding" : "Removing", dialog->peer->name); + } else if (dialog->subscribe_events == SIP_SUBSCRIBE_FEATURE_EVENTS) { + ast_debug(2, "%s feature event subscription for %s\n", dialog->expiry ? "Adding" : "Removing", dialog->username); + } + + /* Remove subscription expiry for renewals */ + sip_dialog_cancel_destroy(dialog); + + if (dialog->expiry) { + /* Set timer for destruction of call at expiration */ + sip_dialog_sched_destroy(dialog, (dialog->expiry + 10) * 1000); + } + + if (dialog->subscribe_events == SIP_SUBSCRIBE_DIALOG_INFO_XML || dialog->subscribe_events == SIP_SUBSCRIBE_PIDF_XML) { + 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 (dialog->expiry > 0 && !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); + } + + ao2_t_bump(dialog, "bump dialog"); + dialog->extension_state_id = ast_extension_state_add_destroy_extended(dialog->context, dialog->exten, + sip_extension_state_event, sip_extension_state_destroy, dialog); + + if (dialog->extension_state_id == -1) { + ao2_t_cleanup(dialog, "drop dialog"); + } + } + + ao2_unlock(dialog); + + state_info.reason = 0; + state_info.exten_state = ast_extension_state_extended(NULL, dialog->context, dialog->exten, &device_state_info); + state_info.device_state_info = device_state_info; + + state_info.presence_state = ast_hint_presence_state(NULL, dialog->context, dialog->exten, &subtype, &message); + state_info.presence_subtype = subtype; + state_info.presence_message = message; + + ao2_lock(dialog); + + if (state_info.exten_state < 0) { + if (dialog->expiry > 0) { + ast_log(LOG_NOTICE, "Got subscribe for extension %s@%s from %s, but there is no hint for that extension\n", + dialog->exten, dialog->context, ast_sockaddr_stringify(&dialog->address)); + } + + sip_send_response(dialog, "404 Not Found", request); + sip_dialog_set_need_destroy(dialog, "no hint for subscription"); + + return 0; + } + + ast_set_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED); + sip_send_response(dialog, "200 OK", request); + + 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_string_field_build(dialog, subscribe_uri, "%s@%s", dialog->exten, dialog->context); + + ast_set_flag(&dialog->flags[0], SIP_FORCE_STATE_CHANGE); + sip_extension_state_event(dialog->context, dialog->exten, &state_info, dialog); + + sip_history_append(dialog, "Subscribestatus", "%s", ast_extension_state2str(state_info.exten_state)); + } else if (dialog->subscribe_events == SIP_SUBSCRIBE_MESSAGE_SUMMARY) { + ast_set_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED); + sip_send_response(dialog, "200 OK", request); + + if (dialog->peer) { /* Send first notification */ + struct sip_peer *peer = dialog->peer; + + ao2_t_bump(peer, "bump peer"); + ao2_unlock(dialog); + + sip_peer_send_mwi(peer, FALSE); + ao2_lock(dialog); + + ao2_t_cleanup(peer, "drop peer"); + } + } else if (dialog->subscribe_events == SIP_SUBSCRIBE_FEATURE_EVENTS) { + ast_set_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED); + + if (feature != SIP_FEATURE_BULK_UPDATE) { + sip_send_response_with_feature_event(dialog, feature, request); + } + + if (dialog->peer) { + struct sip_peer *peer = dialog->peer; + + ao2_t_bump(peer, "bump peer"); + + if (feature == SIP_FEATURE_BULK_UPDATE) { + sip_peer_send_bulk_update(peer); + } else if (feature == SIP_FEATURE_DO_NOT_DISTURB) { + sip_peer_send_do_not_disturb(peer); + } else if (feature == SIP_FEATURE_CALL_FORWARD) { + sip_peer_send_call_forward(peer); + } + + ao2_t_cleanup(peer, "drop peer"); + } + } + + if (!dialog->expiry) { + sip_dialog_set_need_destroy(dialog, "forcing expiration"); + } + } + + return 1; +} + +/* 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_request *request) +{ + int res; + char *event; + + event = ast_strdupa(sip_request_get_header(request, "Event")); + event = strsep(&event, ";"); + + ast_debug(2, "Got NOTIFY event: %s\n", event); + + res = 0; + + if (!strcmp(event, "refer")) { + /* Handle REFER notifications */ + const char *content_type; + char *content, *sipfrag; + int code; + + /* id= for each transfer... EventID is basically the REFER cseq We are getting notifications on + * a call that we transferred We should hangup when we are getting a 200 OK in a sipfrag Check if we + * have an owner of this event */ + content_type = sip_request_find_content_type(request); + + /* Check the content type */ + if (strncasecmp(content_type, "message/sipfrag", 15)) { + if (!strcasecmp(content_type, "application/x-cisco-remotecc-response+xml")) { + sip_send_response(dialog, "200 OK", request); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return 0; + } + + /* We need a sipfrag */ + sip_send_response(dialog, "400 Bad Request", request); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return -1; + } + + /* Get the text of the attachment */ + if (ast_strlen_zero(content = sip_request_get_content(request, 0, request->line_count))) { + sip_send_response(dialog, "400 Bad Request", request); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return -1; + } + + /* Fragment should be "SIP/2.0 " */ + sipfrag = ast_skip_blanks(content); + code = 0; + + if (!strncmp(sipfrag, "SIP/2.0", 6)) { + sipfrag = ast_skip_blanks(sipfrag + 6); + + /* 200 OK, 301 Moved Permanently, 302 Moved Temporarily: Transfer succeeded */ + /* 503 Service Unavailable, 603 Decline: Transfer failed */ + if (sscanf(sipfrag, "%30d", &code) != 1) { + code = 0; + } + } + + if (!code) { + ast_log(LOG_NOTICE, "Error parsing sipfrag in NOTIFY in response to REFER\n"); + } + + /* 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_log(LOG_NOTICE, "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_send_response(dialog, "200 OK", request); + } else if (!strcmp(event, "dialog")) { + /* Cisco USECALLMANAGER on/off-hook notifications */ + if (sip_handle_notify_dialog(dialog, request)) { + sip_send_response(dialog, "489 Bad Event", request); + res = -1; + } + } else if (!strcmp(event, "message-summary")) { + char *mailbox, *context, *messages; + int old_messages, new_messages, i; + + mailbox = NULL; + + if (!dialog->mwi) { + struct sip_peer *peer; + + if ((peer = sip_peer_find_by_address(&dialog->received_address, dialog->socket.transport, TRUE, FALSE))) { + mailbox = ast_strdupa(peer->unsolicited_mailbox); + + if ((context = strchr(mailbox, '@'))) { + *context++ = '\0'; + } else { + context = "default"; + } + + ao2_t_cleanup(peer, "drop peer"); + } + } else { + mailbox = ast_strdupa(dialog->mwi->mailbox); + context = ast_strdupa(dialog->mwi->context); + } + + messages = NULL; + + for (i = 0; i < request->line_count; i++) { + if (!strncmp(request->line[i], "Voice-Message:", 14)) { + messages = ast_strdupa(request->line[i]); + break; + } + } + + if (!ast_strlen_zero(mailbox) && !ast_strlen_zero(messages) && + sscanf(messages, "%30d/%30d", &new_messages, &old_messages) == 2) { + ast_publish_mwi_state(mailbox, context, new_messages, old_messages); + sip_send_response(dialog, "200 OK", request); + } else { + sip_send_response(dialog, "489 Bad Event", request); + res = -1; + } + } else if (!strcmp(event, "keep-alive")) { + /* Used by Sipura/Linksys for NAT pinhole, just confirm that we received the packet. */ + sip_send_response(dialog, "200 OK", request); + } else { + /* We don't understand this event. */ + sip_send_response(dialog, "489 Bad Event", request); + res = -1; + } + + if (!dialog->last_invite_cseq) { + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + } + + return res; +} + +/* Handle incoming REFER request */ +static int sip_handle_request_refer(struct sip_dialog *dialog, struct sip_request *request, int *no_unlock) +{ + char *refer_to, *refer_to_context; + struct sip_transfer_blind_data transfer_data; + enum ast_transfer_result transfer_res; + RAII_VAR(struct ast_channel *, channel, NULL, ast_channel_cleanup); + RAII_VAR(struct ast_str *, replaces, NULL, ast_free_ptr); + const char *content_type; + int res; + + content_type = sip_request_find_content_type(request); + + /* Cisco USECALLMANAGER remotecc and failover */ + if (!strcasecmp(content_type, "application/x-cisco-alarm+xml") || + !strcasecmp(content_type, "application/x-cisco-remotecc-response+xml")) { + sip_send_response(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 (!strcasecmp(content_type, "application/x-cisco-remotecc-request+xml")) { + RAII_VAR(struct sip_peer *, auth_peer, NULL, ao2_cleanup); + + res = sip_dialog_check_peer_authorization(dialog, request, &auth_peer, SIP_SEND_UNRELIABLE); + + /* if an authentication response was sent, we are done here */ + if (res == SIP_AUTHORIZATION_CHALLENGE_SENT) { + return 0; + } + + if (res != SIP_AUTHORIZATION_SUCCESSFUL) { + ast_log(LOG_NOTICE, "Failed to authenticate device %s for REFER\n", sip_request_get_header(request, "From")); + sip_send_response_reliable(dialog, "403 Forbidden", request); + } else if (sip_handle_refer_remotecc(dialog, request, auth_peer)) { + sip_send_response(dialog, "603 Decline", request); + } + + if (!dialog->channel) { + sip_dialog_set_already_gone(dialog); + sip_dialog_set_need_destroy(dialog, "remotecc request"); + } + + return 0; + } else if (!strcasecmp(sip_request_get_header(request, "Refer-To"), "")) { + sip_send_response(dialog, "202 Accepted", request); + + sip_dialog_set_already_gone(dialog); + sip_dialog_set_need_destroy(dialog, "token registration"); + + return 0; + } + + if (request->debug) { + ast_verb(3, "Call '%s' got a transfer REFER from %s\n", + dialog->call_id, ast_test_flag(&dialog->flags[0], SIP_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_send_response(dialog, "603 Decline", request); + + if (!request->ignore) { + sip_history_append(dialog, "Xfer", "Refer failed. Outside of dialog."); + + sip_dialog_set_already_gone(dialog); + sip_dialog_set_need_destroy(dialog, "outside of dialog"); + + } + return 0; + } + + /* Check if transfer is allowed from this device */ + if (!ast_test_flag(&dialog->flags[1], SIP_ALLOW_TRANSFER)) { + /* Transfer not allowed, decline */ + sip_send_response(dialog, "603 Decline", request); + sip_history_append(dialog, "Xfer", "Refer failed. Allow transfer disabled."); + + /* Do not destroy SIP session */ + return 0; + } + + if (!request->ignore && ast_test_flag(&dialog->flags[0], SIP_GOT_REFER)) { + /* Already have a pending REFER */ + sip_send_response(dialog, "491 Request Pending", request); + sip_history_append(dialog, "Xfer", "Refer failed. Request pending."); + + return 0; + } + + dialog->refer_status = SIP_REFER_SENT; + res = sip_parse_refer_to(dialog, request); /* Extract headers */ + + if (res != SIP_PARSE_REFER_SUCCESS) { + switch (res) { + case SIP_PARSE_REFER_MISSING_HEADER: /* Syntax error */ + sip_send_response(dialog, "400 Bad Request", request); + sip_history_append(dialog, "Xfer", "Refer failed. Refer-to missing."); + + if (request->debug) { + ast_debug(1, "Transfer to black hole can't be handled (no refer-to: )\n"); + } + + break; + case SIP_PARSE_REFER_BAD_URI: + sip_send_response(dialog, "603 Decline", request); + sip_history_append(dialog, "Xfer", "Refer failed. Non SIP uri"); + + if (request->debug) { + ast_debug(1, "Transfer to non-SIP uri denied\n"); + } + + break; + case SIP_PARSE_REFER_BAD_EXTENSION: + default: + /* Refer-to extension not found, fake a failed transfer */ + sip_send_response(dialog, "202 Accepted", request); + sip_history_append(dialog, "Xfer", "Refer failed. Bad extension."); + + sip_send_notify_with_sipfrag(dialog, request->cseq, "404 Not Found", TRUE); + ast_clear_flag(&dialog->flags[0], SIP_GOT_REFER); + + if (request->debug) { + ast_debug(1, "Transfer to bad extension: %s\n", dialog->refer_to); + } + + break; + } + + return 0; + } + + if (ast_strlen_zero(dialog->context)) { + ast_string_field_set(dialog, context, sip_config.context); + } + + /* If we do not support SIP domains, all transfers are local */ + if (sip_config.allow_external_domains && sip_domain_check(dialog->refer_to_domain, NULL, 0)) { + dialog->local_transfer = TRUE; + + ast_debug(3, "This transfer is local: %s\n", dialog->refer_to_domain); + } else if (AST_LIST_EMPTY(&sip_domains) || sip_domain_check(dialog->refer_to_domain, NULL, 0)) { + /* This PBX doesn't bother with SIP domains or domain is local, so this transfer is local */ + dialog->local_transfer = TRUE; + } else { + ast_debug(3, "This transfer is to a remote SIP extension (remote domain %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); + + ast_debug(3, "%s transfer: Transferer channel %s\n", dialog->attended_transfer ? "Attended" : "Blind", ast_channel_name(channel)); + + ast_set_flag(&dialog->flags[0], SIP_GOT_REFER); + + /* From here on failures will be indicated with NOTIFY requests */ + sip_send_response(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 */ + if (sip_transfer_attended(dialog, channel, request->cseq, no_unlock)) { + ast_clear_flag(&dialog->flags[0], SIP_GOT_REFER); + return -1; + } + + /* 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"); + } + + /* Copy data we can not safely access after letting the dialog lock go. */ + refer_to = ast_strdupa(dialog->refer_to); + refer_to_context = ast_strdupa(dialog->refer_to_context); + + 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)) { + replaces = ast_str_create(128); + + if (!replaces) { + ast_log(LOG_NOTICE, "Unable to create Replaces string for remote attended transfer. Transfer failed\n"); + ast_clear_flag(&dialog->flags[0], SIP_GOT_REFER); + + return -1; + } + + ast_str_append(&replaces, 0, "%s%s%s%s%s", dialog->replaces_call_id, + !ast_strlen_zero(dialog->replaces_to_tag) ? ";to-tag=" : "", S_OR(dialog->replaces_to_tag, ""), + !ast_strlen_zero(dialog->replaces_from_tag) ? ";from-tag=" : "", S_OR(dialog->replaces_from_tag, "")); + + transfer_data.replaces = ast_str_buffer(replaces); + } else { + transfer_data.replaces = NULL; + } + + if (!*no_unlock) { + ast_channel_unlock(dialog->channel); + *no_unlock = TRUE; + } + + ast_set_flag(&dialog->flags[0], SIP_DEFER_BYE_ON_TRANSFER); + + ao2_unlock(dialog); + transfer_res = ast_bridge_transfer_blind(TRUE, channel, refer_to, refer_to_context, sip_transfer_blind, &transfer_data); + + ao2_lock(dialog); + + switch (transfer_res) { + case AST_BRIDGE_TRANSFER_INVALID: + dialog->refer_status = SIP_REFER_FAILED; + + sip_send_notify_with_sipfrag(dialog, request->cseq, "503 Service Unavailable", TRUE); + sip_history_append(dialog, "Xfer", "Refer failed (only bridged calls)."); + + ast_clear_flag(&dialog->flags[0], SIP_DEFER_BYE_ON_TRANSFER); + res = -1; + + break; + case AST_BRIDGE_TRANSFER_NOT_PERMITTED: + dialog->refer_status = SIP_REFER_FAILED; + + sip_send_notify_with_sipfrag(dialog, request->cseq, "403 Forbidden", TRUE); + sip_history_append(dialog, "Xfer", "Refer failed (bridge does not permit transfers)"); + + ast_clear_flag(&dialog->flags[0], SIP_DEFER_BYE_ON_TRANSFER); + res = -1; + + break; + case AST_BRIDGE_TRANSFER_FAIL: + dialog->refer_status = SIP_REFER_FAILED; + + sip_send_notify_with_sipfrag(dialog, request->cseq, "500 Internal Server Error", TRUE); + sip_history_append(dialog, "Xfer", "Refer failed (internal error)"); + + ast_clear_flag(&dialog->flags[0], SIP_DEFER_BYE_ON_TRANSFER); + res = -1; + + break; + case AST_BRIDGE_TRANSFER_SUCCESS: + dialog->refer_status = SIP_REFER_200_OK; + + sip_send_notify_with_sipfrag(dialog, request->cseq, "200 OK", TRUE); + sip_history_append(dialog, "Xfer", "Refer succeeded."); + + res = 0; + + break; + default: + break; + } + + ast_clear_flag(&dialog->flags[0], SIP_GOT_REFER); + + return res; +} + +/* Handle incoming REGISTER request */ +static int sip_handle_request_register(struct sip_dialog *dialog, struct sip_request *request) +{ + int res; + + /* If this is not the intial request, and the initial request isn't a register, something screwy happened, so bail */ + if (dialog->initial_request.uri && dialog->initial_request.method != SIP_METHOD_REGISTER) { + ast_log(LOG_WARNING, "Ignoring spurious REGISTER for '%s'\n", dialog->call_id); + return -1; + } + + /* Use this as the basis */ + sip_request_copy(&dialog->initial_request, request); + + ast_debug(4, "Set initital SUBSCRIBE request for '%s'\n", dialog->call_id); + + sip_dialog_check_via(dialog, request); + + if ((res = sip_dialog_check_register_authorization(dialog, request)) == -1) { + const char *reason; + + switch (res) { + case SIP_AUTHORIZATION_SECRET_FAILED: + reason = "Wrong password"; + break; + case SIP_AUTHORIZATION_USERNAME_MISMATCH: + reason = "Username/auth name 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_BAD_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_log(LOG_NOTICE, "Registration from '%s' failed for '%s': %s\n", + sip_request_get_header(request, "To"), ast_sockaddr_stringify(&dialog->received_address), reason); + sip_history_append(dialog, "RegRequest", "Failed Account %s: %s", sip_request_get_header(request, "To"), reason); + } else { + request->authenticated = TRUE; + sip_history_append(dialog, "RegRequest", "Succeeded Account %s", sip_request_get_header(request, "To")); + } + + 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, SIP_DEFAULT_TIMEOUT); + } + + return res; +} + +/* 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_request *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_send_response_with_accept(dialog, "200 OK", request); + return 0; + } + + if (sip_config.auth_options_requests) { + /* Do authentication if this OPTIONS request began the dialog */ + sip_request_copy(&dialog->initial_request, request); + sip_dialog_set_allowed_methods(dialog, request); + + res = sip_dialog_check_peer_authorization(dialog, request, NULL, SIP_SEND_UNRELIABLE); + + if (res == SIP_AUTHORIZATION_CHALLENGE_SENT) { + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + return 0; + } + + if (res != SIP_AUTHORIZATION_SUCCESSFUL) { /* Something failed in authentication */ + sip_send_response_with_authorization_failure(dialog, request, res, SIP_SEND_UNRELIABLE); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + return 0; + } + } + + /* must go through authentication before getting here */ + res = sip_dialog_get_destination(dialog, request); + sip_dialog_build_contact(dialog, request); + + if (ast_strlen_zero(dialog->context)) { + ast_string_field_set(dialog, context, sip_config.context); + } + + 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_send_response_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. */ + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return 0; +} + +static int sip_handle_request_publish(struct sip_dialog *dialog, struct sip_request *request) +{ + static unsigned int next_etag = 0; + const char *event, *expires; + struct sip_request resp; + int res, expiry; + + event = sip_request_get_header(request, "Event"); + + if (ast_strlen_zero(event)) { + sip_send_response(dialog, "489 Bad Event", request); + sip_dialog_set_need_destroy(dialog, "missing Event: header"); + + return -1; + } + + res = sip_dialog_check_peer_authorization(dialog, request, NULL, SIP_SEND_UNRELIABLE); + + if (res == SIP_AUTHORIZATION_CHALLENGE_SENT) { + dialog->last_invite_cseq = request->cseq; + return 0; + } else if (res != SIP_AUTHORIZATION_SUCCESSFUL) { + sip_send_response_with_authorization_failure(dialog, request, res, SIP_SEND_UNRELIABLE); + + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + ast_string_field_set(dialog, remote_tag, NULL); + + return 0; + } else if (res == SIP_AUTHORIZATION_SUCCESSFUL && 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_request_get_header(request, "Expires"); + + if (ast_strlen_zero(expires)) { + expiry = sip_config.default_expiry; + } else { + expiry = atoi(expires); + + if (expiry > sip_config.max_expiry) { + expiry = sip_config.max_expiry; + } else if (expiry < sip_config.min_expiry && expiry > 0) { + sip_send_response_with_min_expires(dialog, "423 Interval Too Small", request, sip_config.min_expiry); + sip_dialog_set_need_destroy(dialog, "Expires is less that the min expires allowed."); + + return 0; + } + } + + dialog->expiry = expiry; + + if (!strcmp(event, "presence")) { + res = sip_handle_publish_presence(dialog, request); + } else { + sip_send_response(dialog, "400 Unknown Event", request); + res = -1; + } + + if (!res) { + const char *etag = sip_request_get_header(request, "SIP-If-Match"); + + sip_response_prepare(&resp, dialog, "200 OK", request); + + if (!ast_strlen_zero(etag)) { + sip_request_add_header(&resp, "SIP-ETag", etag); + } else { + ast_atomic_fetchadd_int((int *) &next_etag, +1); + sip_request_build_header(&resp, "SIP-ETag", "%d", next_etag); + } + + res = sip_response_send(dialog, &resp, SIP_SEND_UNRELIABLE, 0); + } + + sip_dialog_set_need_destroy(dialog, "forcing expiration"); + + return res; +} + +/* Receive SIP INFO Message */ +static int sip_handle_request_info(struct sip_dialog *dialog, struct sip_request *request) +{ + const char *content_type; + + if (request->ignore) { + sip_send_response(dialog, "200 OK", request); + return 0; + } + + content_type = sip_request_get_header(request, "Content-Type"); + + /* Need to check the media/type */ + if (!strcasecmp(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_send_response(dialog, "200 OK", request); + return 0; + } else if (!atoi(sip_request_get_header(request, "Content-Length"))) { + /* This is probably just a packet making sure the signalling is still up, just send back a 200 OK */ + sip_send_response(dialog, "200 OK", request); + return 0; + } + + /* Other type of INFO message, not really understood by Asterisk */ + ast_log(LOG_WARNING, "Unable to parse INFO message from '%s'\n", dialog->call_id); + sip_send_response(dialog, "415 Unsupported Media Type", request); + + return 0; +} + +/* Handle incoming MESSAGE request. We only handle messages within current calls currently */ +static int sip_handle_request_message(struct sip_dialog *dialog, struct sip_request *request) +{ + char *content, *from, *to, *contact; + const char *content_type; + struct ast_str *from_name; + struct ast_msg *msg; + int i, res; + + if (request->ignore) { + sip_send_response(dialog, "202 Accepted", request); + return 0; + } + + content_type = sip_request_get_header(request, "Content-Type"); + + if (strncmp(content_type, "text/plain", 10)) { /* No text/plain attachment */ + sip_send_response(dialog, "415 Unsupported Media Type", request); /* Good enough, or? */ + + if (!dialog->channel) { + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + } + + return 0; + } + + if (!(content = sip_request_get_content(request, 0, request->line_count))) { + ast_log(LOG_WARNING, "No content body on MESSAGE for '%s'\n", dialog->call_id); + sip_send_response(dialog, "500 Internal Server Error", request); + + if (!dialog->channel) { + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + } + + return 0; + } + + /* Strip trailing line feeds from message body. (sip_request_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_send_response(dialog, "202 Accepted", request); /* We respond 202 accepted, since we relay the message */ + + return 0; + } + + /* At this point MESSAGE is outside of a call. */ + if (!sip_config.accept_outofcall_message) { + /* Message outside of a call, we do not support that */ + ast_debug(1, "MESSAGE outside of a call administratively disabled\n"); + + sip_send_response(dialog, "405 Method Not Allowed", request); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return 0; + } + + sip_request_copy(&dialog->initial_request, request); + + if (sip_config.auth_message_requests) { + sip_dialog_set_allowed_methods(dialog, request); + res = sip_dialog_check_peer_authorization(dialog, request, NULL, SIP_SEND_UNRELIABLE); + + if (res == SIP_AUTHORIZATION_CHALLENGE_SENT) { + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + return 0; + } + + if (res != SIP_AUTHORIZATION_SUCCESSFUL) { /* Something failed in authentication */ + sip_send_response_with_authorization_failure(dialog, request, res, SIP_SEND_UNRELIABLE); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return 0; + } + /* Auth was successful. Proceed. */ + } else { + struct sip_peer *peer; + + /* MESSAGE outside of a call, not authenticating it. Check to see if we match a peer anyway so that we can direct + * it to the right context. */ + if ((peer = sip_peer_find_by_address(&dialog->received_address, dialog->socket.transport, TRUE, FALSE))) { + /* Only if no auth is required. */ + if (ast_strlen_zero(peer->secret) && ast_strlen_zero(peer->md5secret)) { + ast_string_field_set(dialog, context, peer->context); + } + + if (!ast_strlen_zero(peer->message_context)) { + ast_string_field_set(dialog, message_context, peer->message_context); + } + + ast_string_field_set(dialog, peer_name, peer->name); + ao2_t_cleanup(peer, "drop peer"); + } + } + + /* Override the context with the message context _BEFORE_ getting the destination. This way we can guarantee the correct + * extension is used in the message context when it is present. */ + if (!ast_strlen_zero(dialog->message_context)) { + ast_string_field_set(dialog, context, dialog->message_context); + } else if (!ast_strlen_zero(sip_config.message_context)) { + ast_string_field_set(dialog, context, sip_config.message_context); + } + + switch (sip_dialog_get_destination(dialog, request)) { + case SIP_DESTINATION_REFUSED: + /* Okay to send 403 since this is after auth processing */ + sip_send_response(dialog, "403 Forbidden", request); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return 0; + case SIP_DESTINATION_INVALID_URI: + sip_send_response(dialog, "416 Unsupported URI Scheme", request); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return 0; + 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_send_response(dialog, "500 Internal Server Error", request); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return -1; + } + + to = ast_strdupa(request->uri); + res = ast_msg_set_to(msg, "%s", to); + + from = ast_strdupa(sip_request_get_header(request, "From")); + + /* Build "display" for from string. */ + from_name = ast_str_alloca(128); + sip_get_name(from, &from_name); + + from = sip_get_in_brackets(from); + + if (ast_str_strlen(from_name)) { + char quoted_name[128]; + + ast_escape_quoted(ast_str_buffer(from_name), quoted_name, sizeof(quoted_name)); + res |= ast_msg_set_from(msg, "\"%s\" <%s>", quoted_name, from); + } else { + res |= ast_msg_set_from(msg, "<%s>", from); + } + + ast_free(from_name); + + res = 0; + + res |= ast_msg_set_body(msg, "%s", content); + res |= ast_msg_set_context(msg, "%s", dialog->context); + + res |= ast_msg_set_var(msg, "SIP_RECVADDR", ast_sockaddr_stringify(&dialog->received_address)); + res |= ast_msg_set_tech(msg, "%s", "SIP"); + + 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_request_get_header(request, "Contact")); + res |= ast_msg_set_var(msg, "SIP_FULLCONTACT", sip_get_in_brackets(contact)); + + res |= ast_msg_set_exten(msg, "%s", dialog->exten); + + for (i = 0; i < request->header_count; i++) { + res |= ast_msg_set_var(msg, request->header[i].name, request->header[i].value); + + if (res) { + break; + } + } + + if (res) { + ast_msg_destroy(msg); + + sip_send_response(dialog, "500 Internal Server Error", request); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return -1; + } + + if (ast_msg_has_destination(msg)) { + ast_msg_queue(msg); + + sip_send_response(dialog, "202 Accepted", request); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return 0; + } + + ast_msg_destroy(msg); + + sip_send_response(dialog, "404 Not Found", request); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return 0; +} + +/* 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. */ +static 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 = channel_data->data; + struct ast_party_redirecting redirecting; + struct ast_set_party_redirecting update; + + pbx_builtin_setvar_helper(channel, "SIPTRANSFER", "yes"); + pbx_builtin_setvar_helper(channel, "SIPTRANSFER_REPLACES", transfer_data->replaces); + pbx_builtin_setvar_helper(channel, "SIPDOMAIN", 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, sip_dialog transferer and current.chan1 (the dialog's owner). Additional + * locks are held at the beginning of the function, target_dialog, and target_dialog's owner channel (which is stored in + * target.chan1). These 2 locks _MUST_ be let go by the end of the function. Do not be confused into thinking a dialog's owner + * 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 transferer dialog lock will remain on return. Setting no_unlock indicates to + * sip_handle_incoming() that the dialog's owner it locked does not require an unlock. */ +static 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 */ + transfer_dialog = sip_dialog_find_with_channel(dialog->replaces_call_id, + dialog->replaces_to_tag, dialog->replaces_from_tag, &transfer_channel); + + if (!transfer_dialog) { + 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_send_notify_with_sipfrag(dialog, cseq, "481 Call Leg/Transaction Does Not Exist", TRUE); + sip_history_append(dialog, "Xfer", "Refer failed"); + + ast_clear_flag(&dialog->flags[0], SIP_GOT_REFER); + dialog->refer_status = 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"); + + /* Cancel transfer */ + sip_send_notify_with_sipfrag(dialog, cseq, "503 Service Unavailable", TRUE); + sip_history_append(dialog, "Xfer", "Refer failed"); + + ast_clear_flag(&dialog->flags[0], SIP_GOT_REFER); + dialog->refer_status = SIP_REFER_FAILED; + + return -1; + } + + ast_set_flag(&dialog->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Delay hangup */ + + /* Cisco phones need a different response code */ + if (ast_test_flag(&transfer_dialog->flags[1], SIP_CISCO_USECALLMANAGER)) { + ast_set_flag(&transfer_dialog->flags[1], SIP_TRANSFER_RESPONSE); + } + + 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_status = SIP_REFER_200_OK; + + sip_send_notify_with_sipfrag(dialog, cseq, "200 OK", TRUE); + sip_history_append(dialog, "Xfer", "Refer succeeded"); + + return 1; + case AST_BRIDGE_TRANSFER_FAIL: + dialog->refer_status = SIP_REFER_FAILED; + sip_send_notify_with_sipfrag(dialog, cseq, "500 Internal Server Error", TRUE); + + sip_history_append(dialog, "Xfer", "Refer failed (internal error)"); + ast_clear_flag(&dialog->flags[0], SIP_DEFER_BYE_ON_TRANSFER); + + return -1; + case AST_BRIDGE_TRANSFER_INVALID: + dialog->refer_status = SIP_REFER_FAILED; + sip_send_notify_with_sipfrag(dialog, cseq, "503 Service Unavailable", TRUE); + + sip_history_append(dialog, "Xfer", "Refer failed (invalid bridge state)"); + ast_clear_flag(&dialog->flags[0], SIP_DEFER_BYE_ON_TRANSFER); + + return -1; + case AST_BRIDGE_TRANSFER_NOT_PERMITTED: + dialog->refer_status = SIP_REFER_FAILED; + sip_send_notify_with_sipfrag(dialog, cseq, "403 Forbidden", TRUE); + + sip_history_append(dialog, "Xfer", "Refer failed (operation not permitted)"); + ast_clear_flag(&dialog->flags[0], SIP_DEFER_BYE_ON_TRANSFER); + + return -1; + default: + break; + } + + return 1; +} + +/* 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_request *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; + } + + if (!dialog->channel) { + /* What to do if no channel ??? */ + ast_log(LOG_ERROR, "Unable to create new channel. Invite/replace failed\n"); + + sip_send_response_reliable(dialog, "503 Service Unavailable", request); + sip_history_append(dialog, "Xfer", "INVITE/Replace Failed. No new channel."); + + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + return 1; + } + + sip_history_append(dialog, "Xfer", "INVITE/Replace received"); + + /* Get a ref to ensure the channel cannot go away on us. */ + channel = ast_channel_ref(dialog->channel); + + /* Fake call progress */ + sip_send_response(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_peer *peer, struct sip_request *request, int *feature) +{ +#ifdef HAVE_LIBXML2 + const char *content_type; + char *content; + RAII_VAR(struct ast_xml_doc *, xml_doc, NULL, ast_xml_close); + struct ast_xml_node *root; + + if (!atoi(sip_request_get_header(request, "Content-Length"))) { + /* Peer is subscribing to the current DoNotDisturb and CallForward state */ + *feature = SIP_FEATURE_BULK_UPDATE; + return 0; + } + + content_type = sip_request_get_header(request, "Content-Type"); + + if (strcasecmp(content_type, "application/x-as-feature-event+xml")) { + ast_log(LOG_WARNING, "Content type is not x-as-feature-event+xml\n"); + return -1; + } + + if (!(content = sip_request_get_content(request, 0, request->line_count - 1))) { + ast_log(LOG_WARNING, "Unable to get feature event body\n"); + return -1; + } + + if (!(xml_doc = ast_xml_read_memory(content, strlen(content)))) { + ast_log(LOG_WARNING, "Unable to open XML as-feature-event document. Is it malformed?\n"); + return -1; + } + + if (!(root = ast_xml_get_root(xml_doc))) { + ast_log(LOG_WARNING, "Unable to get root node\n"); + return -1; + } + + if (!strcmp(ast_xml_node_get_name(root), "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 = root; + + if (!(set_do_not_disturb = ast_xml_node_get_children(set_do_not_disturb))) { + ast_log(LOG_WARNING, "No child nodes within SetDoNotDisturb node"); + return -1; + } + + if (!(do_not_disturb_on = ast_xml_find_element(set_do_not_disturb, "doNotDisturbOn", NULL, NULL))) { + ast_log(LOG_WARNING, "Missing doNotDisturbOn node"); + 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_log(LOG_WARNING, "Invalid doNotDisturbOn node %s\n", text); + return -1; + } + + if (peer->do_not_disturb != do_not_disturb) { + peer->do_not_disturb = do_not_disturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", 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); + } + } + + *feature = SIP_FEATURE_DO_NOT_DISTURB; + } else if (!strcmp(ast_xml_node_get_name(root), "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 = root; + + if (!(set_forwarding = ast_xml_node_get_children(set_forwarding))) { + ast_log(LOG_WARNING, "No child nodes within SetForwarding node"); + return -1; + } + + if (!(forwarding_type = ast_xml_find_element(set_forwarding, "forwardingType", NULL, NULL))) { + ast_log(LOG_WARNING, "Missing forwardingType node\n"); + return -1; + } + + text = ast_xml_get_text(forwarding_type); + + if (strcmp(text, "forwardImmediate")) { + ast_log(LOG_WARNING, "forwardingType not supported: %s\n", text); + return -1; + } + + if (!(activate_forward = ast_xml_find_element(set_forwarding, "activateForward", NULL, NULL))) { + ast_log(LOG_WARNING, "Missing activateForward node"); + return -1; + } + + ast_xml_free_text(text); + + text = ast_xml_get_text(activate_forward); + + if (!strcmp(text, "true")) { + RAII_VAR(const char *, dn_text, NULL, ast_xml_free_text); + + if (!(forward_dn = ast_xml_find_element(set_forwarding, "forwardDN", NULL, NULL))) { + ast_log(LOG_WARNING, "Missing forwardDN node\n"); + return -1; + } + + dn_text = ast_xml_get_text(forward_dn); + ast_copy_string(call_forward, dn_text, sizeof(call_forward)); + } else if (!strcmp(text, "false")) { + call_forward[0] = '\0'; + } else { + ast_log(LOG_WARNING, "Invalid activateForward: %s\n", text); + return -1; + } + + if (strcmp(peer->call_forward, 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, 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); + } + } + + *feature = SIP_FEATURE_CALL_FORWARD; + } else { + ast_log(LOG_WARNING, "Missing SetDoNotDisturb or SetForwarding node: %s\n", ast_xml_node_get_name(root)); + return -1; + } + + return 0; +#else + return -1 +#endif +} + +/* Handle dialog notifications */ +static int sip_handle_notify_dialog(struct sip_dialog *dialog, struct sip_request *request) +{ +#ifdef HAVE_LIBXML2 + struct sip_peer *peer; + const char *content_type; + char *content, *uri, *user, *domain; + RAII_VAR(struct ast_xml_doc *, xml_doc, 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_request_get_header(request, "Content-Type"); + + if (strcasecmp(content_type, "application/dialog-info+xml")) { + ast_log(LOG_WARNING, "Content type is not application/dialog-info+xml\n"); + return -1; + } + + if (!(content = sip_request_get_content(request, 0, request->line_count - 1))) { + ast_log(LOG_WARNING, "Unable to get dialog body\n"); + return -1; + } + + if (!(xml_doc = ast_xml_read_memory(content, strlen(content)))) { + ast_log(LOG_WARNING, "Unable to parse XML, is it malformed?\n"); + return -1; + } + + if (!(dialog_info = ast_xml_get_root(xml_doc))) { + ast_log(LOG_WARNING, "Unable to get root node\n"); + ast_xml_close(xml_doc); + return -1; + } + + if (strcasecmp(ast_xml_node_get_name(dialog_info), "dialog-info")) { + ast_log(LOG_WARNING, "Missing dialog-info node\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_log(LOG_WARNING, "Missing entity attribute"); + return -1; + } + + uri = ast_strdupa(entity); + + if (!(dialog_info = ast_xml_node_get_children(dialog_info))) { + ast_log(LOG_WARNING, "No child nodes in dialog-info node\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_log(LOG_WARNING, "Missing dialog node\n"); + return -1; + } + + if (!(dialog = ast_xml_node_get_children(dialog))) { + ast_log(LOG_WARNING, "No child nodes in dialog node\n"); + return -1; + } + + if (!(state = ast_xml_find_element(dialog, "state", NULL, NULL))) { + ast_log(LOG_WARNING, "Missing state node\n"); + return -1; + } + + text = ast_xml_get_text(state); + + if (!strcasecmp(text, "trying")) { + offhook = 1; + } else if (!strcasecmp(text, "terminated")) { + offhook = -1; + } else { + ast_log(LOG_WARNING, "Invalid state node %s\n", text); + return -1; + } + } + + if (sip_parse_uri(uri, "sip:,sips", &user, &domain, NULL)) { + return -1; + } + + sip_pedantic_decode(user); + + if (!(peer = sip_peer_find(user, TRUE, TRUE))) { + ast_log(LOG_WARNING, "Unknown peer '%s'\n", user); + return -1; + } + + if (peer->socket.transport == dialog->socket.transport && !ast_sockaddr_cmp(&peer->address, &dialog->received_address)) { + ao2_lock(peer); + + if ((peer->offhook += offhook) < 0) { + peer->offhook = 0; + } + + ao2_unlock(peer); + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + } + + ao2_t_cleanup(peer, "drop peer"); + sip_send_response(dialog, "200 OK", request); + + return 0; +#else + return -1; +#endif +} + +static int sip_handle_publish_presence(struct sip_dialog *dialog, struct sip_request *request) +{ +#ifdef HAVE_LIBXML2 + const char *content_type; + char *content; + RAII_VAR(struct ast_xml_doc *, xml_doc, NULL, ast_xml_close); + struct ast_xml_node *presence, *person, *activities; + struct sip_peer *peer; + struct sip_alias *alias; + int res, do_not_disturb; + + res = -1; + content_type = sip_request_get_header(request, "Content-Type"); + + if (strcmp(content_type, "application/pidf+xml")) { + const char *etag = sip_request_get_header(request, "SIP-If-Match"); + + if (!ast_strlen_zero(etag)) { + return 0; + } + + ast_log(LOG_WARNING, "Content type is not application/pidf+xml\n"); + goto cleanup; + } + + if (!(content = sip_request_get_content(request, 0, request->line_count))) { + ast_log(LOG_WARNING, "Unable to get PIDF body\n"); + goto cleanup; + } + + if (!(xml_doc = ast_xml_read_memory(content, strlen(content)))) { + ast_log(LOG_WARNING, "Unable to parse xml, is it malformed?\n"); + goto cleanup; + } + + presence = ast_xml_get_root(xml_doc); + + if (strcmp(ast_xml_node_get_name(presence), "presence")) { + ast_log(LOG_WARNING, "Root node of PIDF document is not presence\n"); + goto cleanup; + } + + if (!(presence = ast_xml_node_get_children(presence))) { + ast_log(LOG_WARNING, "No child nodes within presence element\n"); + goto cleanup; + } + + if (!(person = ast_xml_find_element(presence, "person", NULL, NULL))) { + ast_log(LOG_NOTICE, "Missing person node?\n"); + goto cleanup; + } + + if (!(person = ast_xml_node_get_children(person))) { + ast_log(LOG_NOTICE, "No child nodes within person node\n"); + goto cleanup; + } + + if (!(activities = ast_xml_find_element(person, "activities", NULL, NULL))) { + ast_log(LOG_NOTICE, "Missing activities node?\n"); + goto cleanup; + } + + if (!(activities = ast_xml_node_get_children(activities))) { + ast_log(LOG_NOTICE, "No child nodes within activities node\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_log(LOG_NOTICE, "Missing dnd or available node\n"); + goto cleanup; + } + + if (!(peer = sip_peer_find(dialog->peer_name, TRUE, TRUE))) { + ast_log(LOG_NOTICE, "No such peer '%s'\n", dialog->peer_name); + goto cleanup; + } + + if (peer->do_not_disturb != do_not_disturb) { + 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, 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); + } + } + + ao2_t_cleanup(peer, "drop peer"); + res = 0; + +cleanup: + if (res) { + sip_send_response(dialog, "400 Bad Request", request); + } + + return res; +#else + return -1; +#endif /* HAVE_LIBXML2 */ +} + +/* Handle incoming remotecc request */ +static int sip_handle_refer_remotecc(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer) +{ +#ifdef HAVE_LIBXML2 + const char *content_type = sip_request_get_header(request, "Content-Type"); + char *content, *boundary = NULL; + RAII_VAR(struct ast_xml_doc *, xml_doc, NULL, ast_xml_close); + struct ast_xml_node *remotecc_request, *soft_key_event_msg, *data_passthrough_req; + struct sip_remotecc_data remotecc_data; + int iter_start, iter_end, iter, done; + + done = FALSE; + + if (!strncasecmp(content_type, "multipart/mixed", 15)) { + if ((boundary = strcasestr(content_type, ";boundary="))) { + boundary += 10; + } else if ((boundary = strcasestr(content_type, "; boundary="))) { + boundary += 11; + } else { + return -1; + } + + boundary = ast_strdupa(boundary); + boundary = strsep(&boundary, ";"); + + if ((iter_start = sip_request_find_boundary(request, boundary, 0, &done)) == -1) { + return -1; + } + + iter_start += 1; + + if ((iter_end = sip_request_find_boundary(request, boundary, iter_start, &done)) == -1) { + return -1; + } + + content_type = NULL; + + for (iter = iter_start; iter < iter_end; iter++) { + if (!strncasecmp(request->line[iter], "Content-Type:", 13)) { + content_type = ast_skip_blanks(request->line[iter] + 13); + } else if (ast_strlen_zero(request->line[iter])) { + break; + } + } + + if (ast_strlen_zero(content_type)) { + return -1; + } + } else { + iter_start = 0; + iter_end = request->line_count; + } + + if (strcasecmp(content_type, "application/x-cisco-remotecc-request+xml")) { + ast_log(LOG_WARNING, "Content type is not x-cisco-remotecc-request+xml\n"); + return -1; + } + + if (!(content = sip_request_get_content(request, iter_start, iter_end - 1))) { + ast_log(LOG_WARNING, "Unable to get remotecc body\n"); + return -1; + } + + if (!(xml_doc = ast_xml_read_memory(content, strlen(content)))) { + ast_log(LOG_WARNING, "Unable to open XML remotecc document. Is it malformed?\n"); + return -1; + } + + if (!(remotecc_request = ast_xml_get_root(xml_doc))) { + ast_log(LOG_WARNING, "Unable to get root node\n"); + return -1; + } + + if (strcasecmp(ast_xml_node_get_name(remotecc_request), "x-cisco-remotecc-request")) { + ast_log(LOG_WARNING, "Missing x-cisco-remotecc-request node\n"); + return -1; + } + + if (!(remotecc_request = ast_xml_node_get_children(remotecc_request))) { + ast_log(LOG_WARNING, "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); + } + } + } 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_node, *conf_id_node; + + if ((application_id_node = 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_node); + remotecc_data.application_id = atoi(S_OR(text, "")); + } + + if ((conf_id_node = 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_node); + remotecc_data.conference_id = atoi(S_OR(text, "")); + } + } + + if (boundary && !done) { + iter_start = iter_end + 1; + + if ((iter_end = sip_request_find_boundary(request, boundary, iter_start, &done)) == -1) { + ast_log(LOG_WARNING, "Failed to find end boundary\n"); + return -1; + } + + content_type = NULL; + + for (iter = iter_start; iter < iter_end; iter++) { + if (!strncasecmp(request->line[iter], "Content-Type:", 13)) { + content_type = ast_skip_blanks(request->line[iter] + 13); + } else if (ast_strlen_zero(request->line[iter])) { + break; + } + } + + if (ast_strlen_zero(content_type)) { + return -1; + } + + if (!strcasecmp(content_type, "application/x-cisco-remotecc-cm+xml")) { + char *user_call_data; + + if (!(user_call_data = sip_request_get_content(request, iter_start, iter_end - 1))) { + ast_log(LOG_WARNING, "Unable to get user_call_data body\n"); + return -1; + } + + remotecc_data.user_call_data = ast_trim_blanks(ast_strdupa(user_call_data)); + } + } + + if (!ast_strlen_zero(remotecc_data.soft_key_event)) { + if (!strcmp(remotecc_data.soft_key_event, "Park")) { + return sip_remotecc_park(dialog, request, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "ParkMonitor")) { + return sip_remotecc_parkmonitor(dialog, request, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "HLog")) { + return sip_remotecc_hlog(dialog, request, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "Conference")) { + return sip_remotecc_conference(dialog, request, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "ConfList")) { + return sip_remotecc_conflist(dialog, request, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "ConfDetails")) { + return sip_remotecc_conflist(dialog, request, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "RmLastConf")) { + return sip_remotecc_rmlastconf(dialog, request, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "Cancel")) { + sip_send_response(dialog, "202 Accepted", request); + return 0; + } else if (!strcmp(remotecc_data.soft_key_event, "Select")) { + return sip_remotecc_select(dialog, request, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "Unselect")) { + return sip_remotecc_unselect(dialog, request, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "Join")) { + return sip_remotecc_join(dialog, request, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "StartRecording")) { + return sip_remotecc_startrecording(dialog, request, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "StopRecording")) { + return sip_remotecc_stoprecording(dialog, request, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "IDivert")) { + return sip_remotecc_idivert(dialog, request, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "QRT")) { + return sip_remotecc_qrt(dialog, request, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "MCID")) { + return sip_remotecc_mcid(dialog, request, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.soft_key_event, "CallBack")) { + return sip_remotecc_callback(dialog, request, peer, &remotecc_data); + } + + ast_log(LOG_WARNING, "Unsupported softkeyevent: %s\n", remotecc_data.soft_key_event); + } else if (remotecc_data.application_id) { + if (remotecc_data.application_id == SIP_REMOTECC_CONFLIST) { + return sip_remotecc_conflist(dialog, request, peer, &remotecc_data); + } else if (remotecc_data.application_id == SIP_REMOTECC_CALLBACK) { + return sip_remotecc_callback(dialog, request, peer, &remotecc_data); + } + + ast_log(LOG_WARNING, "Unsupported applicationid: %d\n", remotecc_data.application_id); + } else { + ast_log(LOG_WARNING, "Unsupported x-cisco-remotecc-request+xml request\n"); + } +#endif + return -1; +} + +/* Handle idivert request */ +static int sip_remotecc_idivert(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + 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_t_cleanup(target_dialog, "drop dialog"); + + return -1; + } + + ast_channel_ref(channel); + ao2_unlock(target_dialog); + + ao2_t_cleanup(target_dialog, "drop dialog"); + sip_send_response(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", peer->name); + ast_async_goto(bridge_channel, peer->context, "idivert", 1); + + ast_channel_unref(bridge_channel); + } + } + + ast_channel_unref(channel); + + return 0; +} + +/* Handle hlog request */ +static int sip_remotecc_hlog(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data) +{ + struct sip_alias *alias; + + sip_send_response(dialog, "202 Accepted", request); + + peer->hunt_group = !peer->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, 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); + + return 0; +} + +/* Handle remotecc quality reporting tool requests */ +static int sip_remotecc_qrt(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data) +{ + struct sip_dialog *target_dialog, *refer_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; + } + + ast_set_flag(&target_dialog->flags[2], SIP_SEND_QRT_URL); + ao2_t_cleanup(target_dialog, "drop dialog"); + + sip_send_response(dialog, "202 Accepted", request); + + if (!(refer_dialog = sip_dialog_alloc(NULL, &dialog->socket, FALSE, SIP_METHOD_REFER, NULL, 0))) { + return 0; + } + + sip_dialog_copy(refer_dialog, dialog); + + content = ast_str_alloca(4096); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "notify_display\n"); + ast_str_append(&content, 0, "\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"); + ast_str_append(&content, 0, "Quality Reporting Tool is active\n"); + ast_str_append(&content, 0, "5\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "1\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + sip_send_refer_with_content(refer_dialog, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_t_cleanup(refer_dialog, "drop dialog"); + } else { + sip_send_response(dialog, "202 Accepted", request); + sip_peer_send_qrt_url(peer); + } + + return 0; +} + +/* Handle remotecc malicious call requests */ +static int sip_remotecc_mcid(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data) +{ + struct sip_dialog *target_dialog, *refer_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_t_cleanup(target_dialog, "drop dialog"); + + return -1; + } + + ast_channel_ref(channel); + + ao2_unlock(target_dialog); + ao2_t_cleanup(target_dialog, "drop dialog"); + + sip_send_response(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 (!(refer_dialog = sip_dialog_alloc(NULL, &dialog->socket, FALSE, SIP_METHOD_REFER, NULL, 0))) { + return 0; + } + + sip_dialog_copy(refer_dialog, dialog); + + content = ast_str_alloca(4096); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "notify_display\n"); + ast_str_append(&content, 0, "\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"); + ast_str_append(&content, 0, "\200T\n"); + ast_str_append(&content, 0, "10\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "1\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\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"); + ast_str_append(&content, 0, "DtZipZip\n"); + ast_str_append(&content, 0, "all\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + sip_send_refer_with_content(refer_dialog, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(refer_dialog, "drop dialog"); + + return 0; +} + +/* Handle remotecc start recording requests */ +static int sip_remotecc_startrecording(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data) +{ + sip_send_response(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; +} + +/* Handle remotecc stop recording requests */ +static int sip_remotecc_stoprecording(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data) +{ + sip_send_response(dialog, "202 Accepted", request); + sip_recording_stop(remotecc_data->dialog.call_id, remotecc_data->dialog.remote_tag, remotecc_data->dialog.local_tag); + + return 0; +} + +/* Handle SIP response in dialog, only called by sip_handle_request */ +static void sip_handle_response(struct sip_dialog *dialog, struct sip_request *response) +{ + const char *via; + int iter, acked; + + ast_debug(4, "Received response '%s'\n", response->status_line); + + if (response->code <= 0) { + ast_log(LOG_WARNING, "Invalid SIP response code: '%d'\n", response->code); + return; + } + + /* 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. This is not perfect, as it will not catch multiple headers joined with a comma. Fixing that + * would pretty much involve writing a new parser */ + iter = 0; + via = sip_request_next_header(response, "Via", &iter); + + if (strchr(via, ',') || !ast_strlen_zero(sip_request_next_header(response, "Via", &iter))) { + ast_log(LOG_WARNING, "Misrouted SIP 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, *uri; + + contact = ast_strdupa(sip_request_get_header(response, "Contact")); + uri = sip_remove_uri_parameters(sip_get_in_brackets(contact)); + + if (!ast_strlen_zero(uri)) { + 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_send_ack(dialog, response->cseq, response->code < 300); + } + + sip_history_append(dialog, "Ignore", "Ignoring this retransmit\n"); + return; + } + + /* If this is a NOTIFY for a subscription clear the flag that indicates that we have a NOTIFY pending */ + if (!dialog->channel && response->method == SIP_METHOD_NOTIFY && dialog->pending_invite_cseq) { + dialog->pending_invite_cseq = 0; + } + + /* Get their tag if we haven't already */ + if (ast_strlen_zero(dialog->remote_tag) || response->code >= 200) { + struct ast_str *tag = ast_str_alloca(128); + + sip_get_tag(response, "To", &tag); + ast_string_field_set(dialog, remote_tag, ast_str_buffer(tag)); + } else { + /* Store remote_tag to track for changes when 200 responses to invites are received without SDP */ + ast_string_field_set(dialog, remote_provisional_tag, dialog->remote_tag); + } + + /* 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 == 404 || response->code == 408 || response->code == 481) && response->method == SIP_METHOD_BYE) { + sip_dialog_set_need_destroy(dialog, "received 4XX response->codeonse to a BYE"); + return; + } + + if (dialog->peer && response->method == SIP_METHOD_OPTIONS) { + /* 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) { + sip_handle_response_options(dialog, response); + } + } else if (response->method == SIP_METHOD_REFER && response->code >= 200) { + sip_handle_response_refer(dialog, response); + } else if (response->method == SIP_METHOD_INFO) { + /* More good gravy! */ + sip_handle_response_info(dialog, response); + } else if (response->method == SIP_METHOD_MESSAGE) { + /* More good gravy! */ + sip_handle_response_message(dialog, response); + } else if (response->method == SIP_METHOD_NOTIFY) { + /* The gravy train continues to roll */ + sip_handle_response_notify(dialog, response); + } else if (ast_test_flag(&dialog->flags[0], SIP_OUTGOING)) { + switch (response->code) { + case 100: /* 100 Trying */ + case 101: /* 101 Dialog establishment */ + case 183: /* 183 Session Progress */ + case 180: /* 180 Ringing */ + case 182: /* 182 Queued */ + case 181: /* 181 Call Is Being Forwarded */ + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } + + break; + case 200: /* 200 OK */ + dialog->authentication_attempts = 0; /* Reset authentication counter */ + + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } else if (response->method == SIP_METHOD_REGISTER) { + sip_handle_response_register(dialog, response); + } else if (response->method == SIP_METHOD_SUBSCRIBE) { + ast_set_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED); + sip_handle_response_subscribe(dialog, response); + } else if (response->method == SIP_METHOD_BYE) { /* Ok, we're ready to go */ + sip_dialog_set_need_destroy(dialog, "received 200 response"); + ast_clear_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED); + } + + break; + case 401: /* Not www-authorized on SIP method */ + case 407: /* Proxy auth required */ + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } else if (response->method == SIP_METHOD_SUBSCRIBE) { + sip_handle_response_subscribe(dialog, response); + } else if (dialog->registry && response->method == SIP_METHOD_REGISTER) { + sip_handle_response_register(dialog, response); + } else if (response->method == SIP_METHOD_UPDATE) { + sip_handle_response_update(dialog, response); + } else if (response->method == SIP_METHOD_BYE) { + dialog->authorization_code = response->code; + + if (ast_strlen_zero(dialog->auth_name)) { + ast_log(LOG_WARNING, "Asked to authenticate BYE, to %s but we have no matching peer!\n", + ast_sockaddr_stringify(&dialog->received_address)); + sip_dialog_set_need_destroy(dialog, "unable to authenticate BYE"); + } else if (sip_dialog_handle_authentication(dialog, response, response->method, SIP_INIT_NONE)) { + ast_log(LOG_NOTICE, "Failed to authenticate on BYE to '%s'\n", + sip_request_get_header(&dialog->initial_request, "From")); + + sip_dialog_set_need_destroy(dialog, "failed to authenticate BYE"); + } + } else { + ast_log(LOG_WARNING, "Got authentication request (%d) on %s to '%s'\n", + response->code, sip_methods[response->method].name, sip_request_get_header(response, "To")); + + sip_dialog_set_need_destroy(dialog, "received 407 response"); + } + + break; + case 403: /* Forbidden - we failed authentication */ + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } else if (response->method == SIP_METHOD_SUBSCRIBE) { + sip_handle_response_subscribe(dialog, response); + } else if (dialog->registry && response->method == SIP_METHOD_REGISTER) { + sip_handle_response_register(dialog, response); + } else { + ast_log(LOG_WARNING, "Forbidden, maybe wrong password on authentication for %s\n", + sip_methods[response->method].name); + + sip_dialog_set_need_destroy(dialog, "received 403 response"); + } + + break; + case 400: /* Bad Request */ + case 414: /* Request URI too long */ + case 493: /* Undecipherable */ + case 404: /* Not found */ + if (dialog->registry && response->method == SIP_METHOD_REGISTER) { + sip_handle_response_register(dialog, response); + } else if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } else if (response->method == SIP_METHOD_SUBSCRIBE) { + sip_handle_response_subscribe(dialog, response); + } else if (dialog->channel) { + ast_queue_control(dialog->channel, AST_CONTROL_CONGESTION); + } + + break; + case 423: /* Interval too brief */ + if (response->method == SIP_METHOD_REGISTER) { + sip_handle_response_register(dialog, response); + } + + break; + case 408: /* Request timeout - terminate dialog */ + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } else if (response->method == SIP_METHOD_REGISTER) { + sip_handle_response_register(dialog, response); + } else if (response->method == SIP_METHOD_BYE) { + sip_dialog_set_need_destroy(dialog, "received 408 response"); + ast_debug(4, "Got timeout on bye. Thanks for the answer\n"); + } else { + if (dialog->channel) { + ast_queue_control(dialog->channel, AST_CONTROL_CONGESTION); + } + + sip_dialog_set_need_destroy(dialog, "received 408 response"); + } + + break; + case 428: + case 422: /* Session-Timers: Session Interval Too Small */ + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } + + break; + case 480: + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } else if (response->method == SIP_METHOD_SUBSCRIBE) { + sip_handle_response_subscribe(dialog, response); + } else if (dialog->channel) { + /* No specific handler. Default to congestion */ + ast_queue_control(dialog->channel, AST_CONTROL_CONGESTION); + } + + break; + case 481: /* Call leg does not exist */ + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } else if (response->method == SIP_METHOD_SUBSCRIBE) { + sip_handle_response_subscribe(dialog, response); + } else { + ast_log(LOG_WARNING, "Remote host can't match request %s for '%s', giving up\n", + sip_methods[response->method].name, dialog->call_id); + /* Guessing that this is not an important request */ + } + + break; + case 487: + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } + + break; + case 415: /* Unsupported media type */ + case 488: /* Not acceptable here - codec error */ + case 606: /* Not Acceptable */ + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } + + break; + case 491: /* Pending */ + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } else { + ast_debug(1, "Got 491 response on %s unsupported for '%s'\n", + sip_methods[response->method].name, dialog->call_id); + + sip_dialog_set_need_destroy(dialog, "received 491 response"); + } + + break; + case 405: /* Method not allowed */ + case 501: /* Not Implemented */ + sip_set_method_allowed(&dialog->allowed_methods, response->method, FALSE); + + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } else { + ast_log(LOG_WARNING, "Host '%s' does not implement '%s'\n", + ast_sockaddr_stringify(&dialog->address), sip_methods[response->method].name); + } + + break; + default: + if (response->code >= 200 && response->code < 300) { /* on any 2XX response->codeonse do the following */ + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } + } else if (response->code >= 300 && response->code < 700) { + /* Fatal response */ + if (response->code != 487) { + ast_verb(3, "Got SIP %s response '%s' back from %s\n", + sip_methods[response->method].name, response->status_line, + ast_sockaddr_stringify(&dialog->address)); + } + + if (response->method == SIP_METHOD_INVITE) { + sip_dialog_stop_rtp(dialog); /* Immediately stop RTP, VRTP and UDPTL as applicable */ + } + + /* XXX Locking issues? */ + switch (response->code) { + 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); + } + /* Fall through */ + case 486: /* Busy here */ + case 600: /* Busy everywhere */ + case 603: /* Decline */ + if (dialog->channel) { + ast_queue_control(dialog->channel, AST_CONTROL_BUSY); + } + + break; + case 482: /* Loop Detected */ + case 404: /* Not Found */ + case 410: /* Gone */ + case 400: /* Bad Request */ + case 500: /* Server error */ + if (response->method == SIP_METHOD_SUBSCRIBE) { + sip_handle_response_subscribe(dialog, response); + break; + } + /* Fall through */ + case 502: /* Bad gateway */ + case 503: /* Service Unavailable */ + case 504: /* Server Timeout */ + if (dialog->channel) { + ast_queue_control(dialog->channel, AST_CONTROL_CONGESTION); + } + + break; + case 484: /* Address Incomplete */ + if (dialog->channel && response->method != SIP_METHOD_BYE) { + if (ast_test_flag(&dialog->flags[1], SIP_ALLOW_OVERLAP) == SIP_ALLOW_OVERLAP_YES) { + ast_queue_hangup_with_cause(dialog->channel, sip_hangup2cause(response->code)); + } else { + ast_queue_hangup_with_cause(dialog->channel, sip_hangup2cause(404)); + } + } + + break; + default: + /* Send hangup */ + if (dialog->channel && response->method != SIP_METHOD_BYE) { + ast_queue_hangup_with_cause(dialog->channel, sip_hangup2cause(response->code)); + } + + break; + } + + /* ACK on invite */ + if (response->method == SIP_METHOD_INVITE) { + sip_send_ack(dialog, response->cseq, FALSE); + } + + sip_dialog_set_already_gone(dialog); + + if (!dialog->channel) { + sip_dialog_set_need_destroy(dialog, "transaction completed"); + } + } else if (response->code >= 100 && response->code < 200) { + if (response->method == SIP_METHOD_INVITE) { + if (!response->ignore) { + sip_dialog_cancel_destroy(dialog); + } + + if (sip_sdp_find(response)) { + sip_sdp_parse(dialog, response, SIP_SDP_T38_NONE, FALSE); + } + + if (dialog->channel) { + /* Queue a progress frame */ + ast_queue_control(dialog->channel, AST_CONTROL_PROGRESS); + } + } + } else { + ast_log(LOG_NOTICE, "Don't know how to handle a '%s' response from %s\n", + response->status_line, + dialog->channel ? ast_channel_name(dialog->channel) : ast_sockaddr_stringify(&dialog->address)); + } + } + } else { + /* Responses to OUTGOING SIP requests on INCOMING calls get handled here. As well as out-of-call message responses */ + if (response->debug) { + ast_verb(3, "SIP Response message for INCOMING dialog %s arrived\n", sip_methods[response->method].name); + } + + if (response->method == SIP_METHOD_INVITE && response->code == 200) { + /* Tags in early session is replaced by the tag in 200 OK, which is the final reply to our INVITE */ + struct ast_str *tag = ast_str_alloca(128); + + sip_get_tag(response, "To", &tag); + ast_string_field_set(dialog, remote_tag, ast_str_buffer(tag)); + } + + switch (response->code) { + case 200: /* OK */ + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } else if (response->method == SIP_METHOD_CANCEL) { + ast_debug(1, "Got 200 OK on CANCEL\n"); + + /* Wait for 487, then destroy */ + } else if (response->method == SIP_METHOD_BYE) { + sip_parse_rtp_stats(dialog, response); + sip_dialog_set_need_destroy(dialog, "transaction completed"); + } + + break; + case 401: /* Unauthorized */ + case 407: /* Proxy Authentication Required */ + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } else if (response->method == SIP_METHOD_BYE) { + if (sip_dialog_handle_authentication(dialog, response, response->method, SIP_INIT_NONE)) { + ast_log(LOG_NOTICE, "Failed to authenticate on BYE to '%s'\n", + sip_request_get_header(&dialog->initial_request, "From")); + + sip_dialog_set_need_destroy(dialog, "failed to authenticate BYE"); + } + } + + break; + case 481: /* Call Leg Does Not Exist */ + if (response->method == SIP_METHOD_INVITE) { + /* Re-invite failed */ + sip_handle_response_invite(dialog, response); + } else if (response->method == SIP_METHOD_BYE) { + sip_dialog_set_need_destroy(dialog, "received 481 response"); + } else { + ast_debug(1, "Remote host can't match %s to '%s', giving up\n", + sip_methods[response->method].name, dialog->call_id); + } + + break; + case 501: /* Not Implemented */ + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } + + break; + default: /* Errors without handlers */ + if (response->code >= 100 && response->code < 200) { + if (response->method == SIP_METHOD_INVITE) { /* re-invite */ + if (!response->ignore) { + sip_dialog_cancel_destroy(dialog); + } + } + } else if (response->code >= 200 && response->code < 300) { /* on any unrecognized 2XX code do the following */ + if (response->method == SIP_METHOD_INVITE) { + sip_handle_response_invite(dialog, response); + } + } else if (response->code >= 300 && response->code < 700) { + if (response->code != 487) { + ast_verb(3, "Got SIP %s response '%s' back from %s\n", + sip_methods[response->method].name, response->status_line, + ast_sockaddr_stringify(&dialog->address)); + } + + switch (response->code) { + case 415: /* Unsupported Media Type */ + case 488: /* Not Acceptable Here */ + case 603: /* Declined */ + case 500: /* Internal Server Error */ + case 502: /* Bad Gateway */ + case 503: /* Service Unavailable */ + case 504: /* Server Timeout */ + /* re-invite failed */ + if (response->method == SIP_METHOD_INVITE) { + sip_dialog_cancel_destroy(dialog); + } + + break; + } + } + + break; + } + } +} + +/* Handle SIP response to INVITE dialogue */ +static void sip_handle_response_invite(struct sip_dialog *dialog, struct sip_request *response) +{ + int res, outgoing, reinvite; + + reinvite = !!ast_test_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED); + outgoing = !!ast_test_flag(&dialog->flags[0], SIP_OUTGOING); + + if (reinvite) { + ast_debug(4, "Response %d to RE-invite on %s '%s'\n", + response->code, outgoing ? "outgoing" : "incoming", dialog->call_id); + } else { + ast_debug(4, "SIP response %d to standard invite\n", response->code); + } + + if (dialog->already_gone) { /* This call is already gone */ + ast_debug(1, "Got 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_t_cleanup(dialog, "drop 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; + } + + /* For INVITE, treat all 2XX responses as we would a 200 response */ + if (response->code >= 200 && response->code < 300) { + 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; + } + + /* Final response, not 200 ? */ + 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; + } + + if (response->code >= 200 && reinvite) { + 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; + } + + /* If this is a response to our initial INVITE, we need to set what we can use for this peer. */ + if (!reinvite) { + sip_dialog_set_allowed_methods(dialog, response); + } + + 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_parse_ok_contact(dialog, response); + + if (!reinvite) { + sip_dialog_build_route(dialog, response, TRUE); + } + + if (!response->ignore && dialog->channel) { + if (sip_parse_remote_party_id(dialog, response)) { + sip_dialog_queue_connected_line_update(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); + + if (dialog->peer) { + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_NOT_CACHABLE, "SIP/%s", dialog->peer->name); + } + } + + if (ast_test_flag(&dialog->flags[1], SIP_CISCO_USECALLMANAGER)) { + 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, FALSE, 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"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\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"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + ao2_lock(answer_dialog); + sip_send_refer_with_content(answer_dialog, "application/x-cisco-remotecc-request+xml", + ast_str_buffer(content)); + ao2_unlock(answer_dialog); + + ao2_t_cleanup(answer_dialog, "drop dialog"); + } + } + } + } + + 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_T38_NONE, 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 */ + ast_set_flag(&dialog->flags[0], SIP_PROGRESS_SENT); + } + + 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_parse_ok_contact(dialog, response); + + if (!reinvite) { + 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_parse_ok_contact(dialog, response); + + if (!reinvite) { + sip_dialog_build_route(dialog, response, TRUE); + } + + if (!response->ignore && dialog->channel) { + if (sip_parse_remote_party_id(dialog, response)) { + sip_dialog_queue_connected_line_update(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_T38_NONE, FALSE); + + if (!response->ignore && dialog->channel) { + /* Queue a progress frame */ + ast_queue_control(dialog->channel, AST_CONTROL_PROGRESS); + /* We have not sent progress, but we have been sent progress so enable early media */ + ast_set_flag(&dialog->flags[0], SIP_PROGRESS_SENT); + } + + 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); + } + + dialog->authentication_attempts = 0; + + if (sip_sdp_find(response)) { + if (sip_sdp_parse(dialog, response, SIP_SDP_T38_ACCEPT, FALSE) && !response->ignore) { + if (!reinvite) { + /* This 200 OK's SDP is not acceptable, so we need to ack, then hangup. For + * re-invites, we try to recover */ + ast_set_flag(&dialog->flags[0], SIP_PENDING_BYE); + 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 (!reinvite) { + 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->remote_provisional_tag) && + strcmp(dialog->remote_tag, dialog->remote_provisional_tag))) { + ast_log(LOG_WARNING, "Received response: \"200 OK\" from '%s' without SDP\n", dialog->peer->name); + + ast_set_flag(&dialog->flags[0], SIP_PENDING_BYE); + ast_rtp_instance_activate(dialog->audio_rtp); + } + } + + if (!response->ignore && dialog->channel) { + if (sip_parse_remote_party_id(dialog, response) || !reinvite) { + sip_dialog_queue_connected_line_update(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 (outgoing) { + sip_dialog_update_call_counter(dialog, SIP_DEC_CALL_RINGING); + sip_parse_ok_contact(dialog, response); + + /* Save Record-Route for any later requests we make on this dialogue */ + if (!reinvite) { + sip_dialog_build_route(dialog, response, TRUE); + } + + if (ast_test_flag(&dialog->flags[0], SIP_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->received_address); + } else if (sip_get_address(dialog->full_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) { + ast_set_flag(&dialog->flags[0], SIP_PENDING_BYE); + } + } + } + + if (!response->ignore && dialog->channel) { + if (!reinvite && !res) { + ast_queue_control(dialog->channel, AST_CONTROL_ANSWER); + } else { /* RE-invite */ + if (dialog->t38_state == SIP_T38_DISABLED || dialog->t38_state == SIP_T38_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) { + ast_set_flag(&dialog->flags[0], SIP_PENDING_BYE); + } + } + + /* Check for Session-Timers related headers */ + if (sip_stimer_get_mode(dialog, FALSE) != SIP_STIMER_MODE_REFUSE) { + sip_stimer_alloc(dialog); + + /* UAS supports Session-Timers */ + if (!ast_strlen_zero(sip_request_get_header(response, "Session-Expires"))) { + if (sip_parse_session_expires(response, &dialog->stimer->interval, &dialog->stimer->refresher)) { + ast_set_flag(&dialog->flags[0], SIP_PENDING_BYE); + } else if (dialog->stimer->interval < sip_stimer_get_expiry(dialog, FALSE)) { + ast_log(LOG_WARNING, "Got Session-Expires less than local Min-SE in 200 OK, tearing down call\n"); + ast_set_flag(&dialog->flags[0], SIP_PENDING_BYE); + } + + dialog->stimer->active = TRUE; + dialog->stimer->remote_active = TRUE; + + sip_stimer_start(dialog); + } else if (sip_stimer_get_mode(dialog, FALSE) == SIP_STIMER_MODE_ORIGINATE) { + /* UAS doesn't support Session-Timers */ + dialog->stimer->refresher = SIP_STIMER_REFRESHER_UAC; + dialog->stimer->remote_active = FALSE; + + sip_stimer_start(dialog); + } + } + + /* If I understand this right, the branch is different for a non-200 ACK only */ + dialog->invite_state = SIP_INVITE_TERMINATED; + ast_set_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED); + + res = sip_send_ack(dialog, response->cseq, TRUE); + sip_dialog_sched_check_pending(dialog); + + if (ast_test_flag(&dialog->flags[2], SIP_RELAY_NEAREND) || ast_test_flag(&dialog->flags[2], SIP_RELAY_FAREND)) { + if (ast_test_flag(&dialog->flags[2], SIP_RELAY_NEAREND)) { + ast_clear_flag(&dialog->flags[2], SIP_RELAY_NEAREND); + sip_recording_start(dialog->join_call_id, dialog->join_local_tag, dialog->join_remote_tag, FALSE); + } else { + ast_clear_flag(&dialog->flags[2], SIP_RELAY_FAREND); + } + + ast_set_flag(&dialog->flags[2], SIP_SDP_ACK); + sip_send_invite(dialog, FALSE, SIP_INIT_BRANCH, NULL); + } else if (ast_test_flag(&dialog->flags[2], SIP_SDP_ACK)) { + ast_clear_flag(&dialog->flags[2], SIP_SDP_ACK); + ast_set_flag(&dialog->flags[2], SIP_CISCO_RECORDING); + } + + break; + case 401: /* Unauthorized */ + case 407: /* Proxy Authentication Required */ + /* First we ACK */ + res = sip_send_ack(dialog, response->cseq, FALSE); + dialog->authorization_code = response->code; + + /* Then we AUTH */ + /* Forget their old tag, so we don't match tags when getting response */ + ast_string_field_set(dialog, remote_tag, NULL); + + if (!response->ignore) { + if (dialog->authentication_attempts < SIP_MAX_AUTHENTICATION_ATTEMPTS) { + dialog->invite_state = SIP_INVITE_CALLING; + } + + if (sip_dialog_handle_authentication(dialog, response, SIP_METHOD_INVITE, SIP_INIT_BRANCH)) { + ast_log(LOG_NOTICE, "Failed to authenticate on INVITE to '%s'\n", + sip_request_get_header(&dialog->initial_request, "From")); + + sip_dialog_set_need_destroy(dialog, "failed to authenticate on INVITE"); + sip_dialog_set_already_gone(dialog); + + if (dialog->channel) { + ast_queue_control(dialog->channel, AST_CONTROL_CONGESTION); + } + } + } + + break; + case 403: /* Forbidden */ + /* First we ACK */ + res = sip_send_ack(dialog, response->cseq, FALSE); + + if (!response->ignore && dialog->channel) { + sip_queue_hangup_cause(dialog, sip_hangup2cause(response->code)); + } + + ast_log(LOG_WARNING, "Received response: \"Forbidden\" from '%s'\n", + sip_request_get_header(&dialog->initial_request, "From")); + break; + case 400: /* Bad Request */ + case 404: /* Not Found */ + case 414: /* Bad Request URI */ + case 493: /* Undecipherable */ + res = sip_send_ack(dialog, response->cseq, FALSE); + + if (dialog->channel && !response->ignore) { + sip_queue_hangup_cause(dialog, sip_hangup2cause(response->code)); + } + + break; + case 481: /* Call Leg Does Not Exist */ + /* Could be REFER caused INVITE with replaces */ + res = sip_send_ack(dialog, response->cseq, FALSE); + + if (dialog->channel) { + ast_queue_hangup_with_cause(dialog->channel, sip_hangup2cause(response->code)); + } + + ast_log(LOG_WARNING, "Re-invite on '%s' which doesn't exist on other end, giving up\n", dialog->call_id); + break; + case 422: /* Session-Timers: Session interval too small */ + res = sip_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(response, &dialog->stimer->cached_min_se)) { + dialog->stimer->cached_min_se = sip_config.stimer_min_se; + } + + if (dialog->stimer->interval < dialog->stimer->cached_min_se) { + dialog->stimer->interval = dialog->stimer->cached_min_se; + } + + sip_send_invite(dialog, TRUE, SIP_INIT_REQUEST, NULL); + break; + case 428: /* Use Identity Header - RFC 4474 - not supported by Asterisk yet */ + res = sip_send_ack(dialog, response->cseq, FALSE); + + if (dialog->channel && !response->ignore) { + ast_queue_hangup_with_cause(dialog->channel, sip_hangup2cause(response->code)); + } + + sip_history_append(dialog, "Identity", "SIP identity is required. Not supported by Asterisk."); + ast_log(LOG_WARNING, "SIP identity required by proxy for '%s', giving up\n", dialog->call_id); + + 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_send_ack(dialog, response->cseq, FALSE); + + if (dialog->channel && !response->ignore) { + if ((dialog->redirecting_code = ast_redirecting_reason_parse(ast_skip_blanks(response->status_line + 3))) == -1) { + ast_string_field_build(dialog, redirecting_reason, "\"%s\"", ast_skip_blanks(response->status_line)); + 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); + } + + sip_history_append(dialog, "TempUnavailable", "Endpoint is temporarily unavailable."); + break; + case 487: /* Transaction Cancelled */ + /* We have sent CANCEL on an outbound INVITE. This transaction is already scheduled to be killed by sip_hangup(). */ + res = sip_send_ack(dialog, response->cseq, FALSE); + + if (dialog->channel && !response->ignore) { + ast_queue_hangup_with_cause(dialog->channel, AST_CAUSE_NORMAL_CLEARING); + + sip_history_append(dialog, "Hangup", "Got 487 on CANCEL request from us. Queued AST hangup request"); + } else if (!response->ignore) { + sip_dialog_update_call_counter(dialog, SIP_DEC_CALL_LIMIT); + + sip_history_append(dialog, + "Hangup", "Got 487 on CANCEL request from us on call without owner. Killing this dialog."); + } + + sip_dialog_sched_check_pending(dialog); + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + break; + case 415: /* Unsupported Media Type */ + case 488: /* Not Acceptable Here */ + case 606: /* Not Acceptable */ + res = sip_send_ack(dialog, response->cseq, FALSE); + + if (dialog->udptl && dialog->t38_state == SIP_T38_LOCAL_REINVITE) { + sip_fax_set_state(dialog, SIP_T38_REJECTED); + /* Try to reset RTP timers. Trigger a reinvite back to audio */ + sip_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 491: /* Pending */ + res = sip_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 { + 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->outgoing_call) { + when = (2100 + ast_random()) % 2000; + } else { + when = ast_random() % 2000; + } + + ao2_t_bump(dialog, "bump dialog"); + + if ((dialog->need_reinvite_sched_id = ast_sched_add(sip_sched_context, when, + sip_dialog_start_need_reinvite, dialog) == -1)) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(dialog, "drop dialog"); + } + + ast_debug(2, "Reinvite race. Scheduled reinvite retry for '%s' in %dms'\n", dialog->call_id, when); + } + } + + break; + case 408: /* Request Timeout */ + case 405: /* Not Allowed */ + case 501: /* Not Implemented */ + res = sip_send_ack(dialog, response->cseq, FALSE); + + if (dialog->channel) { + ast_queue_hangup_with_cause(dialog->channel, sip_hangup2cause(response->code)); + } + + break; + } + + if (res == -1) { + ast_log(LOG_WARNING, "Could not send INVITE on '%s'\n", dialog->call_id); + } +} + +/* Handle authentication challenge for SIP UPDATE. This function is only called upon the receipt of a 401/407 response to an UPDATE. */ +static void sip_handle_response_update(struct sip_dialog *dialog, struct sip_request *response) +{ + dialog->authorization_code = response->code; + + if (sip_dialog_handle_authentication(dialog, response, SIP_METHOD_UPDATE, SIP_INIT_BRANCH)) { + ast_log(LOG_NOTICE, "Failed to authenticate on UPDATE to '%s'\n", + sip_request_get_header(&dialog->initial_request, "From")); + } +} + +/* Handle SIP response in SUBSCRIBE transaction */ +static void sip_handle_response_subscribe(struct sip_dialog *dialog, struct sip_request *response) +{ + if (dialog->subscribe_events != SIP_SUBSCRIBE_MESSAGE_SUMMARY || !dialog->mwi) { + return; + } + + switch (response->code) { + case 200: /* Subscription accepted */ + ast_debug(3, "Got 200 OK on subscription for MWI\n"); + sip_dialog_set_allowed_methods(dialog, response); + + dialog->mwi->subscribed = TRUE; + sip_mwi_subscription_start(dialog->mwi, sip_config.mwi_expiry * 1000); + + break; + case 401: + case 407: + ast_string_field_set(dialog, remote_tag, NULL); + + if (dialog->authentication_attempts > 1 || + sip_dialog_handle_authentication(dialog, response, SIP_METHOD_SUBSCRIBE, SIP_INIT_NONE)) { + ast_log(LOG_NOTICE, "Failed to authenticate on SUBSCRIBE to '%s'\n", + sip_request_get_header(&dialog->initial_request, "From")); + + dialog->mwi->dialog = NULL; + ao2_t_ref(dialog->mwi, -1, "drop mwi"); + + sip_dialog_set_need_destroy(dialog, "failed to authenticate SUBSCRIBE"); + } + + break; + case 403: + ast_log(LOG_WARNING, "Authentication failed while trying to subscribe for MWI\n"); + sip_send_response_with_date(dialog, "200 OK", response); + + dialog->mwi->dialog = NULL; + ao2_t_ref(dialog->mwi, -1, "dop mwi"); + + sip_dialog_set_already_gone(dialog); + sip_dialog_set_need_destroy(dialog, "received 403 response"); + break; + case 404: + ast_log(LOG_WARNING, "Subscription failed for MWI. The remote side said that a mailbox may not have been configured\n"); + + dialog->mwi->dialog = NULL; + ao2_t_ref(dialog->mwi, -1, "drop mwi"); + + sip_dialog_set_need_destroy(dialog, "received 404 response"); + break; + case 481: + ast_log(LOG_WARNING, "Subscription failed for MWI. The remote side said that our dialog did not exist\n"); + + dialog->mwi->dialog = NULL; + ao2_t_ref(dialog->mwi, -1, "drop mwi"); + + sip_dialog_set_need_destroy(dialog, "received 481 response"); + break; + case 400: /* Bad Request */ + case 414: /* Request URI too long */ + case 493: /* Undecipherable */ + case 500: + case 501: + ast_log(LOG_WARNING, "Subscription failed for MWI.\n"); + + dialog->mwi->dialog = NULL; + ao2_t_ref(dialog->mwi, -1, "drop mwi"); + + sip_dialog_set_need_destroy(dialog, "received serious error (500/501/493/414/400) 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_request *response) +{ + switch (response->code) { + case 200: /* Notify accepted */ + /* They got the notify, this is the end */ + if (dialog->channel) { + if (dialog->refer_status) { + ast_log(LOG_NOTICE, "Got OK on REFER notify message\n"); + } else { + ast_log(LOG_WARNING, "Notify answer on an owned channel? - %s\n", ast_channel_name(dialog->channel)); + } + } else { + if (dialog->subscribe_events == SIP_SUBSCRIBE_NONE && dialog->refer_status) { + ast_debug(4, "Got 200 accepted on NOTIFY for '%s'\n", dialog->call_id); + sip_dialog_set_need_destroy(dialog, "received 200 response"); + } + + if (ast_test_flag(&dialog->flags[1], SIP_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 */ + ast_set_flag(&dialog->flags[0], SIP_FORCE_STATE_CHANGE); + ast_clear_flag(&dialog->flags[1], SIP_QUEUED_STATE_CHANGE); + + sip_extension_state_event(dialog->context, dialog->exten, &state_info, dialog); + } + } + + break; + case 401: /* Not www-authorized on SIP method */ + case 407: /* Proxy auth */ + if (!dialog->notify_headers) { + break; /* Only device notify can use NOTIFY auth */ + } + + ast_string_field_set(dialog, remote_tag, NULL); + + if (ast_strlen_zero(dialog->auth_name)) { + ast_log(LOG_WARNING, "Asked to authenticate NOTIFY to %s but we have no matching peer or realm auth!\n", + ast_sockaddr_stringify(&dialog->received_address)); + + sip_dialog_set_need_destroy(dialog, "unable to authenticate NOTIFY"); + } + + if (dialog->authentication_attempts > 1 || + sip_dialog_handle_authentication(dialog, response, SIP_METHOD_NOTIFY, SIP_INIT_NONE)) { + ast_log(LOG_NOTICE, "Failed to authenticate on NOTIFY to '%s'\n", + sip_request_get_header(&dialog->initial_request, "From")); + + sip_dialog_set_need_destroy(dialog, "failed to authenticate NOTIFY"); + } + + break; + case 481: /* Call leg does not exist */ + sip_dialog_set_need_destroy(dialog, "Received 481 response for NOTIFY"); + 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_request *response) +{ + enum ast_control_transfer transfer = AST_TRANSFER_FAILED; + + /* If no refer structure exists, then do nothing */ + if (dialog->refer_status) { + return; + } + + switch (response->code) { + case 200: /* Out of Dialog REFER */ + case 202: /* Transfer accepted */ + /* We need to do something here. The transferee is now sending INVITE to target We should hang along, + * waiting for NOTIFY's here */ + dialog->refer_status = SIP_REFER_ACCEPTED; + /* Now wait for next transfer */ + ast_debug(3, "Got 202 accepted on transfer\n"); + + break; + case 401: /* Not www-authorized on SIP method */ + case 407: /* Proxy auth */ + if (ast_strlen_zero(dialog->auth_name)) { + ast_log(LOG_WARNING, "Asked to authenticate REFER to %s but we have no matching peer or realm auth!\n", + ast_sockaddr_stringify(&dialog->received_address)); + + if (dialog->channel) { + ast_queue_control_data(dialog->channel, AST_CONTROL_TRANSFER, &transfer, sizeof(transfer)); + } + + sip_dialog_set_need_destroy(dialog, "unable to authenticate REFER"); + } + + if (dialog->authentication_attempts > 1 || + sip_dialog_handle_authentication(dialog, response, SIP_METHOD_REFER, SIP_INIT_NONE)) { + ast_log(LOG_NOTICE, "Failed to authenticate on REFER to '%s'\n", + sip_request_get_header(&dialog->initial_request, "From")); + + dialog->refer_status = SIP_REFER_NO_AUTH; + + 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_log(LOG_NOTICE, "SIP transfer to %s failed, REFER not allowed\n", dialog->refer_to); + + sip_dialog_set_need_destroy(dialog, "received 405 response"); + dialog->refer_status = SIP_REFER_FAILED; + + if (dialog->channel) { + ast_queue_control_data(dialog->channel, AST_CONTROL_TRANSFER, &transfer, sizeof(transfer)); + } + + break; + case 481: /* Call leg does not exist */ + /* A transfer with Replaces did not work */ + ast_log(LOG_WARNING, "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: /* Server error */ + case 501: /* Method not implemented */ + /* Return to the current call onhold. Status flag needed to be reset */ + ast_log(LOG_NOTICE, "Transfer to %s failed, call miserably fails\n", dialog->refer_to); + + sip_dialog_set_need_destroy(dialog, "received 500/501 response"); + dialog->refer_status = SIP_REFER_FAILED; + + if (dialog->channel) { + ast_queue_control_data(dialog->channel, AST_CONTROL_TRANSFER, &transfer, sizeof(transfer)); + } + + break; + case 603: /* Transfer declined */ + ast_log(LOG_NOTICE, "Transfer to %s declined, call miserably fails\n", dialog->refer_to); + + dialog->refer_status = 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 4-6xx is + * theoretically possible. */ + if (response->code < 299) { /* 1xx cases don't get here */ + ast_log(LOG_WARNING, "Transfer to %s had unexpected 2xx response (%d), confusion is possible\n", + dialog->refer_to, response->code); + } else { + ast_log(LOG_WARNING, "Transfer to %s with response %d\n", dialog->refer_to, response->code); + } + + dialog->refer_status = 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_request *response) +{ + struct sip_registry *registry; + int expiry; + + registry = dialog->registry; + + switch (response->code) { + case 401: /* Unauthorized */ + if (sip_registry_handle_authentication(dialog, response)) { + ast_log(LOG_NOTICE, "Failed to authenticate on REGISTER to '%s@%s' (Tries %d)\n", + registry->username, registry->hostname, dialog->authentication_attempts); + + sip_dialog_set_need_destroy(dialog, "failed to authenticate REGISTER"); + } + + break; + case 403: /* Forbidden */ + if (sip_config.register_retry_on_403) { + ast_log(LOG_NOTICE, "Treating 403 response to REGISTER as non-fatal for %s@%s\n", + registry->username, registry->hostname); + + ast_string_field_set(registry, nonce, ""); + ast_string_field_set(dialog, nonce, ""); + + break; + } + + ast_log(LOG_WARNING, "Forbidden - wrong password on authentication for REGISTER for '%s' to '%s'\n", + registry->username, registry->hostname); + + registry->state = SIP_REGISTRY_AUTHORIZATION_FAILED; + sip_registry_stop_timeout(registry); + + ast_system_publish_registry("SIP", registry->username, registry->hostname, sip_registry_state2str(registry->state), NULL); + sip_dialog_set_need_destroy(dialog, "received 403 response"); + + break; + case 404: /* Not found */ + ast_log(LOG_WARNING, "Got 404 Not found on SIP register to service %s@%s, giving up\n", + registry->username, registry->hostname); + + registry->state = SIP_REGISTRY_REJECTED; + sip_registry_stop_timeout(registry); + + ast_system_publish_registry("SIP", registry->username, registry->hostname, sip_registry_state2str(registry->state), NULL); + sip_dialog_set_need_destroy(dialog, "received 404 response"); + + if (registry->dialog) { + ao2_t_cleanup(registry->dialog, "drop dialog"); + registry->dialog = NULL; + } + + break; + case 407: /* Proxy auth */ + if (sip_registry_handle_authentication(dialog, response)) { + ast_log(LOG_NOTICE, "Failed to authenticate on REGISTER to '%s' (attempts '%d')\n", + sip_request_get_header(&dialog->initial_request, "From"), dialog->authentication_attempts); + sip_dialog_set_need_destroy(dialog, "failed to authenticate REGISTER"); + } + + break; + case 408: /* Request timeout */ + /* Got a timeout response, so reset the counter of failed responses */ + if (registry) { + registry->attempts = 0; + } else { + ast_log(LOG_WARNING, + "Got a 408 response to our REGISTER on '%s' after we had destroyed the registry entry\n", + dialog->call_id); + } + + break; + case 423: /* Interval too brief */ + ast_log(LOG_WARNING, "Got '423 Interval Too Brief' for service %s@%s, minimum is %ds\n", + registry->username, registry->hostname, registry->expiry); + + registry->expiry = atoi(sip_request_get_header(response, "Min-Expires")); + + if (registry->expiry > sip_config.max_expiry) { + ast_log(LOG_WARNING, "Required expiration time from %s@%s is too high, giving up\n", + registry->username, registry->hostname); + + registry->expiry = registry->default_expiry; + registry->state = SIP_REGISTRY_REJECTED; + + sip_registry_stop_timeout(registry); + } else { + registry->state = SIP_REGISTRY_UNREGISTERED; + sip_send_register(registry, FALSE); + } + + ast_system_publish_registry("SIP", registry->username, registry->hostname, sip_registry_state2str(registry->state), NULL); + + if (registry->dialog) { + ao2_t_cleanup(registry->dialog, "drop dialog"); + registry->dialog = NULL; + + sip_dialog_set_need_destroy(dialog, "received 423 response"); + } + + break; + case 400: /* Bad request */ + case 414: /* Request URI too long */ + case 493: /* Undecipherable */ + case 479: /* Kamailio/OpenSIPS: Not able to process the URI - address is wrong in register*/ + ast_log(LOG_WARNING, "Got error %d on register to %s@%s, giving up (check config)\n", + response->code, registry->username, registry->hostname); + + registry->state = SIP_REGISTRY_REJECTED; + sip_registry_stop_timeout(registry); + + ast_system_publish_registry("SIP", registry->username, registry->hostname, sip_registry_state2str(registry->state), NULL); + sip_dialog_set_need_destroy(dialog, "received 4xx response"); + + if (registry->dialog) { + ao2_t_cleanup(registry->dialog, "drop dialog"); + registry->dialog = NULL; + } + + break; + case 200: /* 200 OK */ + if (!registry) { + ast_log(LOG_WARNING, + "Got 200 OK on REGISTER, but there isn't a registry entry for '%s' (we probably already got the OK)\n", + S_OR(dialog->peer_name, dialog->username)); + + sip_dialog_set_need_destroy(dialog, "received erroneous 200 response"); + return; + } + + ast_debug(1, "Registration successful\n"); + + if (registry->timeout_sched_id != -1) { + ast_debug(1, "Cancelling timeout\n"); + } + + sip_registry_stop_timeout(registry); + + registry->state = SIP_REGISTRY_REGISTERED; + registry->time = ast_tvnow(); /* Reset time of last successful registration */ + registry->attempts = 0; + + ast_system_publish_registry("SIP", registry->username, registry->hostname, sip_registry_state2str(registry->state), NULL); + /* destroy dialog now to avoid interference with next register */ + sip_dialog_set_need_destroy(dialog, "Registration successfull"); + + /* 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 */ + /* XXX Try to save the extra call */ + expiry = 0; + + if (!ast_strlen_zero(sip_request_get_header(response, "Contact"))) { + int iter = 0; + + for (;;) { + const char *contact, *expires; + + contact = sip_request_next_header(response, "Contact", &iter); + + /* this loop ensures we get a contact header about our register request */ + if (ast_strlen_zero(contact)) { + break; + } + + if (!strstr(contact, dialog->our_contact)) { + continue; + } + + if ((expires = strcasestr(contact, ";expires="))) { + expiry = atoi(expires + 9); + } + + break; + } + + } + + if (!expiry) { + if (!(expiry = atoi(sip_request_get_header(response, "Expires")))) { + expiry = sip_config.default_expiry; + } + } + + /* Refresh 30s before expiry but wih a minimum of 500ms */ + registry->outgoing_expiry = MAX((expiry * 1000) - 3000, 500); + + ast_debug(1, "Outbound registration expiry for %s is %ds (re-registration in %ds)\n", + registry->hostname, expiry, registry->outgoing_expiry / 1000); + + /* Schedule re-registration before we expire */ + sip_registry_expires(registry, registry->outgoing_expiry); + + if (registry->dialog) { + ao2_t_cleanup(registry->dialog, "drop dialog"); + registry->dialog = NULL; + } + + ao2_t_replace(registry, NULL, "drop registry"); + } +} + +/* Handle qualification responses (OPTIONS) */ +static void sip_handle_response_options(struct sip_dialog *dialog, struct sip_request *response) +{ + struct sip_peer *peer = dialog->peer; + int state_changed, is_reachable, was_reachable, old_last_qualify; + const char *peer_status; + char last_qualify[20]; + struct sip_alias *alias; + + peer = dialog->peer; /* hope this is already refcounted! */ + + old_last_qualify = peer->last_qualify; + peer->last_qualify = ast_tvdiff_ms(ast_tvnow(), peer->qualify_start); + + /* Compute the response time to a ping (goes in peer->last_qualify.) -1 means did not respond, 0 means unknown, + * 1..maxms is a valid response, >maxms means late response. */ + if (peer->last_qualify < 1) { /* zero = unknown, so round up to 1 */ + peer->last_qualify = 1; + } + + if (!peer->qualify_max) { /* this should never happens */ + sip_dialog_set_need_destroy(dialog, "got OPTIONS response but qualify is not enabled"); + return; + } + + /* Now determine new state and whether it has changed. Use some helper variables to simplify the writing of the expressions. */ + was_reachable = old_last_qualify > 0 && old_last_qualify <= peer->qualify_max; + is_reachable = peer->last_qualify <= peer->qualify_max; + + state_changed = old_last_qualify == 0 /* yes, unknown before */ || was_reachable != is_reachable; + peer_status = is_reachable ? "reachable" : "lagged"; + + snprintf(last_qualify, sizeof(last_qualify), "%d", peer->last_qualify); + + ao2_t_cleanup(peer->qualify_dialog, "drop dialog"); + peer->qualify_dialog = NULL; + + if (state_changed) { + ast_verb(3, "SIP peer '%s' is now %s (%dms / %dms)\n", peer->name, peer_status, peer->last_qualify, peer->qualify_max); + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + + if (sip_config.realtime_update_peer) { + ast_update_realtime("sippeers", "name", peer->name, "lastms", last_qualify, SENTINEL); + } + + 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: i}", "peer_status", peer_status, "time", peer->last_qualify); + ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); + } + } + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (!alias->peer) { + continue; + } + + alias->peer->last_qualify = peer->last_qualify; + + if (state_changed) { + ast_verb(3, "SIP peer alias '%s' is now %s (%dms / %dms)\n", + alias->peer->name, peer_status, alias->peer->last_qualify, alias->peer->qualify_max); + + 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, "lastms", last_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", peer_status, "time", peer->last_qualify); + ast_endpoint_blob_publish(alias->peer->endpoint, ast_endpoint_state_type(), blob); + } + } + } + + sip_dialog_set_need_destroy(dialog, "got OPTIONS response"); + + /* Try again eventually */ + AST_SCHED_REPLACE_UNREF(peer->qualify_expire_sched_id, sip_sched_context, + is_reachable ? peer->qualify_freq : SIP_QUALIFY_FREQ_NOT_OK, sip_peer_qualify, peer, + ao2_t_cleanup(_data, "drop peer"), + ao2_t_cleanup(peer, "drop peer"), + ao2_t_bump(peer, "bump peer")); +} + +/* 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_request *response) +{ + switch (response->code) { + case 401: /* Not www-authorized on SIP method */ + case 407: /* Proxy auth required */ + ast_log(LOG_WARNING, "Host '%s' requests authentication (%d) for '%s'\n", + ast_sockaddr_stringify(&dialog->address), response->code, sip_methods[SIP_METHOD_INFO].name); + break; + case 405: /* Method not allowed */ + case 501: /* Not Implemented */ + sip_set_method_allowed(&dialog->allowed_methods, SIP_METHOD_INFO, FALSE); + + ast_log(LOG_WARNING, "Host '%s' does not implement '%s'\n", + ast_sockaddr_stringify(&dialog->address), sip_methods[SIP_METHOD_INFO].name); + break; + default: + if (response->code >= 300 && response->code < 700) { + ast_verb(3, "Got SIP %s response '%s' back from %s\n", + sip_methods[SIP_METHOD_INFO].name, response->status_line, ast_sockaddr_stringify(&dialog->address)); + } + + 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_request *response) +{ + switch (response->code) { + case 401: /* Not www-authorized on SIP method */ + case 407: /* Proxy auth required */ + if (sip_dialog_handle_authentication(dialog, response, SIP_METHOD_MESSAGE, SIP_INIT_NONE) && + !ast_test_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED)) { + sip_dialog_set_need_destroy(dialog, "MESSAGE authentication failed"); + } + + break; + case 405: /* Method not allowed */ + case 501: /* Not Implemented */ + sip_set_method_allowed(&dialog->allowed_methods, SIP_METHOD_MESSAGE, FALSE); + + ast_log(LOG_WARNING, "Host '%s' does not implement '%s'\n", + ast_sockaddr_stringify(&dialog->address), sip_methods[SIP_METHOD_MESSAGE].name); + + if (!ast_test_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED)) { + sip_dialog_set_need_destroy(dialog, "MESSAGE not implemented or allowed"); + } + + break; + default: + if (response->code >= 100 && response->code < 200) { + /* Must allow provisional responses for out-of-dialog requests. */ + } else if (response->code >= 200 && response->code < 300) { + dialog->authentication_attempts = 0; /* Reset authentication counter */ + + if (!ast_test_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED)) { + sip_dialog_set_need_destroy(dialog, "MESSAGE delivery accepted"); + } + } else if (response->code >= 300 && response->code < 700) { + ast_verb(3, "Got SIP %s response '%s' back from %s\n", + sip_methods[SIP_METHOD_MESSAGE].name, response->status_line, ast_sockaddr_stringify(&dialog->address)); + + if (!ast_test_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED)) { + sip_dialog_set_need_destroy(dialog, + response->code >= 300 && response->code < 600 ? "MESSAGE delivery failed" : "MESSAGE delivery refused"); + } + } + + break; + } +} diff -durN asterisk-22.3.0.orig/channels/sip/include/auth_realms.h asterisk-22.3.0/channels/sip/include/auth_realms.h --- asterisk-22.3.0.orig/channels/sip/include/auth_realms.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/auth_realms.h 2025-04-17 11:11:22.802703516 +1200 @@ -0,0 +1,48 @@ +/* + * 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_auth_realm { + AST_LIST_ENTRY(sip_auth_realm) next; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(realm); /* Realm in which these credentials are valid */ + AST_STRING_FIELD(username); /* Username */ + AST_STRING_FIELD(secret); /* Secret */ + AST_STRING_FIELD(md5secret); /* MD5Secret */ + ); +}; + +/* Container of SIP authentication credentials. */ +AST_LIST_HEAD_NOLOCK(sip_auth_realm_head, sip_auth_realm); + +extern struct sip_auth_realm_head *sip_auth_realms; +extern ast_mutex_t sip_auth_realm_lock; + +void sip_auth_realm_build(struct sip_auth_realm_head **auth_realms, const char *config, int lineno); +void sip_auth_realm_destroy_all(void); +struct sip_auth_realm *sip_auth_realm_find(struct sip_auth_realm_head *auth_realms, const char *realm); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/callback.h asterisk-22.3.0/channels/sip/include/callback.h --- asterisk-22.3.0.orig/channels/sip/include/callback.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/callback.h 2025-04-17 11:11:22.802703516 +1200 @@ -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_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; +}; + +void sip_callback_destroy(struct sip_peer *peer); + +int sip_remotecc_callback(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/chan_tech.h asterisk-22.3.0/channels/sip/include/chan_tech.h --- asterisk-22.3.0.orig/channels/sip/include/chan_tech.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/chan_tech.h 2025-04-17 11:11:22.802703516 +1200 @@ -0,0 +1,59 @@ +/* + * 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_CHAN_TECH_H +#define _SIP_CHAN_TECH_H + +/* Forward declarations */ +struct sip_dialog; + +extern struct ast_channel_tech sip_tech; +extern struct ast_sip_api_tech chan_sip_api_tech; +extern const struct ast_msg_tech sip_msg_tech; + +struct ast_channel *sip_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_devicestate(const char *data); +int sip_presencestate(const char *data, char **subtype, char **message); +int sip_send_text(struct ast_channel *channel, const char *text); +int sip_call(struct ast_channel *channel, const char *destination, int timeout); +int sip_send_html(struct ast_channel *channel, int subclass, const char *data, int data_len); +int sip_hangup(struct ast_channel *chan); +int sip_answer(struct ast_channel *chan); +struct ast_frame *sip_read(struct ast_channel *chan); +int sip_write(struct ast_channel *channel, struct ast_frame *frame); +int sip_indicate(struct ast_channel *channel, int condition, const void *data, size_t data_len); +int sip_transfer(struct ast_channel *channel, const char *destintation); +int sip_fixup(struct ast_channel *oldchannel, struct ast_channel *newchan); +int sip_send_digit_begin(struct ast_channel *channel, char digit); +int sip_send_digit_end(struct ast_channel *channel, char digit, unsigned int duration); +int sip_setoption(struct ast_channel *channel, int option, void *data, int data_len); +int sip_queryoption(struct ast_channel *channel, int option, void *data, int *data_len); +const char *sip_get_pvt_uniqueid(struct ast_channel *chan); + +int sip_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 -durN asterisk-22.3.0.orig/channels/sip/include/cli.h asterisk-22.3.0/channels/sip/include/cli.h --- asterisk-22.3.0.orig/channels/sip/include/cli.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/cli.h 2025-04-17 11:11:22.802703516 +1200 @@ -0,0 +1,55 @@ +/* + * 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_H +#define _SIP_CLI_H + +char *sip_cli_reload(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_show_settings(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_show_channel(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_show_peers(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_notify(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_do_not_disturb(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_call_forward(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_hunt_group(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_show_history(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_set_history(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_set_debug(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_prune_realtime(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_show_sched(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_show_tcp(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_qualify_peer(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_show_peers(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_show_peer(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_show_mwi(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_show_registry(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_unregister(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_show_channels(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_show_channel_stats(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_show_inuse(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_show_domains(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_show_objects(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); + +extern struct ast_cli_entry sip_cli_commands[]; + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/conference.h asterisk-22.3.0/channels/sip/include/conference.h --- asterisk-22.3.0.orig/channels/sip/include/conference.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/conference.h 2025-04-17 11:11:22.802703516 +1200 @@ -0,0 +1,84 @@ +/* + * 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_peer; +struct sip_dialog; +struct sip_request; +struct sip_remotecc_data; + +struct sip_conference { + AST_LIST_ENTRY(sip_conference) next; + int id; + int next_participant_id; + struct ast_bridge *bridge; + unsigned int keep:1; + unsigned int multi_admin:1; + int administrators; + int users; + 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 sip_conferences_head sip_conferences; + +int sip_remotecc_conference(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data); +int sip_remotecc_conflist(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data); +int sip_remotecc_rmlastconf(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data); +int sip_remotecc_join(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data); +int sip_remotecc_select(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data); +int sip_remotecc_unselect(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data); + +void sip_selected_destroy_all(struct sip_peer *peer); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/config.h asterisk-22.3.0/channels/sip/include/config.h --- asterisk-22.3.0.orig/channels/sip/include/config.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/config.h 2025-04-17 11:11:22.803703489 +1200 @@ -0,0 +1,158 @@ +/* + * 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 + +/* Settings for the 'notifycid' option, see sip.conf.sample for details. */ +enum { + SIP_NOTIFY_CALLERID_DISABLED, + SIP_NOTIFY_CALLERID_ENABLED, + SIP_NOTIFY_CALLERID_IGNORE_CONTEXT, +}; + +/* Global settings apply to the channel (often settings you can change in the general section of sip.conf */ +struct sip_config { + struct ast_flags flags[SIP_MAX_FLAGS]; /* Global SIP_ flags */ + unsigned int direct_rtp_setup:1; /* Enable support for Direct RTP setup (no re-invites) */ + unsigned int pedantic_checking:1; /* Extra checking? Default on */ + unsigned int srv_lookup:1; /* SRV Lookup on or off. Default is on */ + unsigned int always_auth_reject:1; /* Send 401 Unauthorized for all failing requests */ + unsigned int auth_options_requests:1; /* Authenticate OPTIONS requests */ + unsigned int auth_message_requests:1; /* Authenticate MESSAGE requests */ + unsigned int accept_outofcall_message:1; /* Accept MESSAGE outside of a call */ + unsigned int allow_external_domains:1; /* Accept calls to external SIP domains? */ + unsigned int send_diversion:1; /* Whether to Send SIP Diversion headers */ + unsigned int match_external_address_locally:1; /* Match externaddr/externhost setting against localnet setting */ + unsigned int disallowed_methods; /* methods that we should never try to use */ + unsigned int notify_ringing:1; /* Send notifications on ringing */ + unsigned int notify_hold:1; /* Send notifications on hold */ + unsigned int notify_callerid; /* Send caller ID with ringing notifications */ + char realm[MAXHOSTNAMELEN]; /* Default realm */ + unsigned int domains_as_realm:1; /* Use domains lists as realms */ + struct sip_proxy proxy; /* Outbound proxy */ + char context[AST_MAX_CONTEXT]; /* Inbound context */ + char subscribe_context[AST_MAX_CONTEXT]; /* Context to look for hints */ + char message_context[AST_MAX_CONTEXT]; /* Default context for out of dialog msgs. */ + char language[MAX_LANGUAGE]; /* Default language setting for new channels */ + char callerid[AST_MAX_EXTENSION]; /* Default caller ID for sip messages */ + char mwi_from[AST_MAX_EXTENSION]; /* Default caller ID for MWI updates */ + char mwi_exten[AST_MAX_EXTENSION]; /* Default From Username on MWI updates */ + char from_domain[AST_MAX_EXTENSION]; /* Default domain on outbound messages */ + int from_domain_port; /* Default domain port on outbound messages */ + int keepalive; /* Default keepalive= setting */ + 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 parkinglot[AST_MAX_CONTEXT]; /* Parkinglot */ + char rtp_engine[256]; /* Default RTP engine */ + int max_call_bitrate; /* Maximum bitrate for call */ + char zone[MAX_TONEZONE_COUNTRY]; /* Default tone zone for channels created from the SIP driver */ + unsigned int transports; /* Default Transports (enum ast_transport) that are acceptable */ + unsigned int primary_transport; /* Default primary Transport (enum ast_transport) for outbound connections to devices */ + unsigned int match_auth_username:1; /* Match auth username if available instead of From: Default off. */ + unsigned int relax_dtmf:1; /* Relax DTMF */ + int premature_media_filter; /* Enable/disable premature frames in a call (causing 183 early media) */ + unsigned int record_history; /* Record SIP history. Off by default */ + unsigned int dump_history; /* Dump history to verbose before destroying SIP dialog */ + int tcp_auth_limit; /* TCP auth limit */ + int tcp_auth_timeout; /* TCP auth timeout */ + struct ast_acl_list *contact_acl; /* Global list of addresses dynamic peers are not allowed to use */ + struct ast_format_cap *format_cap; /* Supported codecs */ + unsigned int tcp_enabled:1; /* TCP enabled */ + int max_forwards; /* Default max forwards (SIP Anti-loop) */ + 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 stimer_mode; /* Mode of operation for Session-Timers */ + int stimer_refresher; /* Session-Timer refresher */ + int stimer_min_se; /* Lowest threshold for session refresh interval */ + int stimer_max_se; /* Highest threshold for session refresh interval */ + unsigned int dynamic_exclude_static:1; /* Exclude static peers from contact registrations */ + int qualify_max; /* Default Qualify= setting */ + int qualify_freq; /* Qualify frequency */ + int qualify_gap; /* Time between our group of peer qualify */ + int qualify_peers; /* Number of peers to qualify at a given time */ + int timer_t1; /* T1 time */ + int t1_min; /* T1 roundtrip time minimum */ + int timer_b; /* Timer B - RFC 3261 Section 17.1.1.2 */ + unsigned int auto_framing:1; /* Turn autoframing on or off. */ + 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 */ + unsigned int auth_failure_events:1; /* Whether we send authentication failure manager events or not. Default no. */ + int register_timeout; /* Global time between attempts for outbound registrations */ + int register_max_attempts; /* Registration attempts before giving up */ + unsigned int register_retry_on_403:1; /* Treat 403 responses to registrations as 401 responses */ + unsigned int shrink_callerid:1; /* enable or disable shrinking of caller id */ + unsigned int call_counter:1; /* Enable call counters for all devices. This is currently enabled by setting the peer call-limit + * to INT_MAX. When we remove the call-limit from the code, we can make it with just a boolean flag + * in the device structure */ + struct ast_sockaddr 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 */ + time_t external_expiry; /* Expiration counter for re-resolving external host name in dynamic DNS */ + int external_refresh; /* 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 *local_address; /* List of local 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 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 min_expiry; /* Minimum accepted registration time */ + int max_expiry; /* Maximum accepted registration time */ + int default_expiry; /* Default expiry */ + int min_subscribe_expiry; /* Minimum accepted subscription time */ + int max_subscribe_expiry; /* Maximum accepted subscription time */ + int mwi_expiry; /* Default MWI expiry */ + unsigned int realtime_update_peer:1; /* G: Update database with registration data for peer? */ + unsigned int realtime_save_sysname:1; /* G: Save system name at registration? */ + unsigned int realtime_save_path:1; /* G: Save path header on registration */ + unsigned int realtime_auto_clear; /* Realtime ?? */ + unsigned int realtime_ignore_expiry:1; /* G: Ignore expiration of peer */ +}; + +extern struct sip_config sip_config; +extern struct ast_config *sip_notify_types; + +extern struct ast_jb_conf sip_jb_config; +extern struct ast_tls_config sip_tls_config; + +extern ast_mutex_t sip_reload_lock; +extern int sip_reloading; +extern enum channelreloadreason sip_reload_reason; + +extern struct ast_sockaddr sip_our_address; + +void sip_config_reload(void); +int sip_config_parse(enum channelreloadreason reason); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/dialog.h asterisk-22.3.0/channels/sip/include/dialog.h --- asterisk-22.3.0.orig/channels/sip/include/dialog.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/dialog.h 2025-04-17 11:11:22.803703489 +1200 @@ -0,0 +1,448 @@ +/* + * 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 + +#define SIP_INITIAL_CSEQ 0 /* Our initial sip sequence number */ +#define SIP_MAX_AUTHENTICATION_ATTEMPTS 3 /* Try authentication three times, then fail */ + +#define SIP_TIMEOUT (64 * 500) /* SIP request timeout (RFC 3261) 64*T1 */ +#define SIP_DEFAULT_TIMEOUT -1 /* Use default SIP transaction timeout */ +#define SIP_PROVISIONAL_KEEPALIVE_TIMEOUT 60000 /* How long to wait before retransmitting a provisional response (RFC 3261 13.3.1.1) */ + +enum { + 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_DEC_CALL_LIMIT = 0, + SIP_INC_CALL_LIMIT, + SIP_DEC_CALL_RINGING, + SIP_INC_CALL_RINGING, +}; + +/* Authentication result from check_auth* functions */ +enum { + SIP_AUTHORIZATION_DONT_KNOW = -10, /* no result, need to check further */ + SIP_AUTHORIZATION_SESSION_LIMIT = -9, + SIP_AUTHORIZATION_RTP_FAILED = -8, + SIP_AUTHORIZATION_BAD_TRANSPORT = -7, + SIP_AUTHORIZATION_ACL_FAILED = -6, + SIP_AUTHORIZATION_PEER_NOT_DYNAMIC = -5, + SIP_AUTHORIZATION_UNKNOWN_DOMAIN = -4, + SIP_AUTHORIZATION_NOT_FOUND = -3, /* returned by register_verify */ + SIP_AUTHORIZATION_USERNAME_MISMATCH = -2, + SIP_AUTHORIZATION_SECRET_FAILED = -1, + SIP_AUTHORIZATION_SUCCESSFUL = 0, + SIP_AUTHORIZATION_CHALLENGE_SENT = 1, +}; + +/* Result from get_destination function */ +enum { + 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_NONE = 0, /* No state at all, maybe not an SIP_INVITE dialog */ + 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 */ +}; + +/* Parameters to know status of transfer */ +enum { + SIP_REFER_NONE = 0, /* No REFER request */ + SIP_REFER_SENT, /* Sent REFER to transferee */ + SIP_REFER_ACCEPTED, /* Accepted by transferee */ + SIP_REFER_200_OK, /* Answered by transfer target */ + SIP_REFER_FAILED, /* REFER declined - go on */ + SIP_REFER_NO_AUTH, /* We had no auth for REFER */ +}; + +/* Type of subscription, based on the packages we do support */ +enum { + SIP_SUBSCRIBE_NONE = 0, + SIP_SUBSCRIBE_DIALOG_INFO_XML, + SIP_SUBSCRIBE_PIDF_XML, + SIP_SUBSCRIBE_MESSAGE_SUMMARY, + SIP_SUBSCRIBE_FEATURE_EVENTS, + SIP_SUBSCRIBE_REMOTECC_XML, +}; + +enum { + SIP_FEATURE_NONE = 0, + SIP_FEATURE_BULK_UPDATE, + SIP_FEATURE_DO_NOT_DISTURB, + SIP_FEATURE_CALL_FORWARD +}; + +/* T38 States for a call */ +enum { + SIP_T38_DISABLED = 0, /* Not enabled */ + SIP_T38_LOCAL_REINVITE, /* Offered from local - REINVITE */ + SIP_T38_PEER_REINVITE, /* Offered from peer - REINVITE */ + SIP_T38_ENABLED, /* Negotiated (enabled) */ + SIP_T38_REJECTED, /* Refused */ +}; + +/* Forward declarations */ +struct sip_auth_realms; +struct sip_peer; +struct sip_registry; +struct sip_mwi_subscription; +struct sip_sdp_media; + +/* sip_history: Structure for saving transactions within a SIP dialog */ +struct sip_history { + AST_LIST_ENTRY(sip_history) next; + char event[0]; /* actually more, depending on needs */ +}; + +/* Structure used for each SIP dialog, ie. a call, a registration, a subscribe. Created and initialized by sip_dialog_alloc(), the + * descriptor goes into the list of descriptors (sip_dialogs). */ +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(branch); /* The branch identifier of this session */ + AST_STRING_FIELD(invite_branch); /* The branch used when we sent the initial INVITE */ + AST_STRING_FIELD(accountcode); /* Account code */ + AST_STRING_FIELD(realm); /* Authorization realm */ + AST_STRING_FIELD(nonce); /* Authorization nonce */ + AST_STRING_FIELD(opaque); /* Opaque nonsense */ + AST_STRING_FIELD(qop); /* Quality of Protection, since SIP wasn't complicated enough yet. */ + AST_STRING_FIELD(domain); /* Authorization domain */ + AST_STRING_FIELD(from); /* The From: header */ + AST_STRING_FIELD(useragent); /* User agent in SIP request */ + AST_STRING_FIELD(exten); /* Extension where to start */ + AST_STRING_FIELD(context); /* Context for this call */ + AST_STRING_FIELD(message_context); /* Default context for outofcall messages. */ + AST_STRING_FIELD(subscribe_context); /* Subscribecontext */ + AST_STRING_FIELD(subscribe_uri); /* Subscribecontext */ + 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_host); /* Host we should put in the "to" field */ + AST_STRING_FIELD(to_dnid); /* DNID of this call (overrides host) */ + AST_STRING_FIELD(language); /* Default language for this call */ + AST_STRING_FIELD(moh_interpret); /* MOH class to use when put on hold */ + AST_STRING_FIELD(moh_suggest); /* MOH class to suggest when putting a peer on hold */ + AST_STRING_FIELD(username); /* [user] name */ + AST_STRING_FIELD(peer_name); /* [peer] name, not set if [user] */ + AST_STRING_FIELD(auth_name); /* Who we use for authentication */ + AST_STRING_FIELD(uri); /* Original requested URI */ + AST_STRING_FIELD(ok_contact_uri); /* URI from the 200 OK on INVITE */ + AST_STRING_FIELD(peer_secret); /* Password */ + AST_STRING_FIELD(peer_md5secret); /* MD5 digest of password */ + 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(mwi_from); /* Name to place in the From header in outgoing NOTIFY requests */ + AST_STRING_FIELD(full_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(parkinglot); /* Parkinglot */ + AST_STRING_FIELD(rtp_engine); /* RTP engine to use */ + AST_STRING_FIELD(dial_string); /* The dialstring 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(message_content); /* Text for a MESSAGE body */ + AST_STRING_FIELD(sdp_unique); /* Remote UA's SDP Session unique parts */ + AST_STRING_FIELD(call_forward); /* Call Forward target */ + 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(last_message); /* Last Message sent/received */ + AST_STRING_FIELD(provisional_status_line); /* The last successfully transmitted provisional response message */ + AST_STRING_FIELD(remote_provisional_tag); /* Provisional their tag, used when evaluating responses to invites */ + AST_STRING_FIELD(notify_content); /* Content for NOTIFY requests */ + AST_STRING_FIELD(refer_to); /* Place to store REFER-TO extension */ + 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(refer_contact); /* Place to store Contact info from a REFER extension */ + 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(content_id); /* Outgoing Content-ID header */ + AST_STRING_FIELD(content_type); /* Outgoing Content-Type header */ + AST_STRING_FIELD(authorization); /* Authentication response */ + AST_STRING_FIELD(uri_options); /* URI options to add to the URI */ + 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_callid logger_callid; /* Identifier for call used in log messages */ + struct sip_socket socket; /* The socket used for this dialog */ + unsigned int record_history:1; /* Set if we want to record history */ + 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 final_destruction_scheduled:1; /* final dialog destruction is scheduled. Keep dialog around until then + * to handle retransmits. */ + unsigned int outgoing_call:1; /* this is an outgoing call */ + 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 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 */ + unsigned int secure_signaling:1;/* Whether we are required to have secure signaling or not */ + 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 do_not_disturb:1; /* Peer has set DoNotDisturb */ + struct ast_flags flags[SIP_MAX_FLAGS]; /* SIP_ flags */ + int timer_t1; /* SIP timer T1, ms rtt */ + int timer_b; /* SIP timer B, ms */ + int invite_state; /* Track state of SIP_INVITEs */ + int method; /* SIP 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 options; /* Supported SIP options on the other end */ + unsigned int required_options; /* Required SIP options on the other end */ + unsigned int allowed_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 disallowed_methods; /* Some peers are not trustworthy with their Allow headers, and so we need to override their + * wicked ways through configuration. This is a copy of the peer's disallowed_methods, so that we + * can apply them to the sip_dialog at various stages of dialog establishment */ + int nonce_count; /* Nonce-count */ + int max_forwards; /* SIP Loop prevention */ + int hangupcause; /* Storage of hangupcause copied from our channel before we disconnect from the channel (only used at hangup) */ + int from_domain_port; /* Domain port to show in from field */ + char zone[MAX_TONEZONE_COUNTRY]; /* Default tone zone for channels created by this dialog */ + int caller_presentation; /* Calling presentation */ + int expiry; /* How long we take to expire */ + struct sip_request 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 local_sdp_version; /* SDP Session Version */ + int remote_sdp_version; /* Remote UA's SDP Session Version */ + struct ast_sockaddr address; /* Our peer */ + struct ast_sockaddr received_address; /* Received as */ + 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 */ + time_t last_rtp_received; /* Last RTP received */ + time_t 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_acl_list *direct_media_acl; /* Which IPs are allowed to interchange direct media with this peer, copied from sip_peer */ + struct ast_channel *channel; /* Who owns us (if we have an owner) */ + int amaflags; /* AMA Flags */ + int invite_sched_id; /* Auto-congest ID if appropriate (scheduler) */ + 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_destruct_sched_id; /* Auto-destruct ID (scheduler) */ + int request_queue_sched_id; /* Scheduler ID of any scheduled action to process queued requests */ + int provisional_keepalive_sched_id; /* Scheduler ID for provisional responses that need to be sent out to avoid cancellation */ + int t38_sched_id; /* T.38 Response ID */ + unsigned int attended_transfer:1; /* Attended or blind transfer? */ + unsigned int local_transfer:1; /* Transfer to local domain? */ + int refer_status; /* REFER status */ + int subscribe_events; /* SUBSCRIBE: Is this dialog a subscription? */ + uint32_t dialog_version; /* SUBSCRIBE: Version for subscription dialog-info */ + int extension_state_id; /* SUBSCRIBE: ID for devicestate subscriptions */ + int last_exten_state; /* SUBSCRIBE: Last known extension state */ + int last_presence_state; /* SUBSCRIBE: Last known presence state */ + struct ao2_container *last_device_state_info; /* SUBSCRIBE: last known extended extension state (take care of refs) */ + struct timeval last_ringing_time; /* SUBSCRIBE: channel timestamp of the channel which caused the last early-state + * notification */ + struct ast_format_cap *format_cap; /* Special capability (codec) */ + struct ast_format_cap *joint_format_cap; /* Supported capability at both ends (codecs) */ + struct ast_format_cap *remote_format_cap; /* Supported peer 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 */ + int max_call_bitrate; /* Maximum Call Bitrate for Video Calls */ + int t38_max_datagram; /* T.38 FaxMaxDatagram override */ + 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 */ + /* 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. */ + AST_LIST_HEAD_NOLOCK(, sip_sdp_media) sdp_media; + AST_LIST_HEAD_NOLOCK(, sip_packet) packet_queue; /* Packets scheduled for re-transmission */ + AST_LIST_HEAD_NOLOCK(, sip_request) request_queue; /* Requests that arrived but could not be processed immediately */ + AST_LIST_HEAD_NOLOCK(, sip_history) history; /* History of this SIP dialog */ + size_t history_count; /* Number of entires in the history */ + 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 ast_dsp *dsp; /* Inband DTMF or Fax CNG tone Detection dsp */ + struct sip_stimer *stimer; /* SIP Session-Timers */ + struct sip_proxy *proxy; /* Outbound proxy for this dialog. Use ref_proxy to set this instead of setting it directly*/ + struct sip_route route; /* List of routing steps (Record-Route) */ + struct sip_auth_realm_head *auth_realms; /* Realm authentication credentials */ + struct ast_variable *notify_headers; + struct sip_peer *peer; /* If this dialog is related to a peer, which one used in qualify and mwi subscriptions */ + struct sip_registry *registry; /* If this is a REGISTER dialog, to which registry */ + struct sip_mwi_subscription *mwi; /* 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. */ + struct ast_udptl *udptl; /* T.38 UDPTL session */ + int redirecting_code; /* Redirect code */ + unsigned int red:1; /* T.140 RTP Redundancy */ + 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 authentication_attempts; /* Times we've tried to authenticate */ + unsigned int add_headers:1; /* Add extra headers in SIPADDHEADER */ + int via_ttl; /* Via maddr TTL */ + int t38_state; /* T.38 state */ + struct ast_control_t38_parameters local_t38_parameters; /* Local t38 state */ + struct ast_control_t38_parameters remote_t38_parameters; /* Remote t38 state */ +}; + +extern struct ao2_container *sip_dialogs; +extern struct ao2_container *sip_dialogs_need_destroy; +extern struct ao2_container *sip_dialogs_rtp_check; + +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 useglobal_nat, const int method, + struct sip_request *request, ast_callid logger_callid); +struct ast_channel *sip_dialog_alloc_channel(struct sip_dialog *dialog, int state, const char *peername, + 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 *p, int when); +void sip_dialog_cancel_destroy(struct sip_dialog *dialog); + +int sip_dialog_need_destroy(void *data, void *arg, int flags); +int sip_dialog_rtp_check(void *data, void *arg, int flags); + +int sip_dialog_auto_destruct(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_socket_transport(struct sip_socket *socket, int transport); +unsigned int sip_dialog_set_allowed_methods(struct sip_dialog *dialog, struct sip_request *request); +void sip_dialog_set_realm(struct sip_dialog *dialog, const struct sip_request *request); +void sip_dialog_set_dsp_detect(struct sip_dialog *dialog, int enabled); +void sip_dialog_set_address(struct sip_dialog *dialog, const char *uri); + +int sip_dialog_get_destination(struct sip_dialog *dialog, struct sip_request *request); +const struct ast_sockaddr *sip_dialog_get_address(struct sip_dialog *dialog); + +void sip_dialog_check_nat(struct sip_dialog *dialog, const struct ast_sockaddr *address); +void sip_dialog_set_nat(struct sip_dialog *dialog, struct sip_peer *peer); +void sip_dialog_set_rtp_nat(struct sip_dialog *dialog); + +int sip_dialog_build_from_peer(struct sip_dialog *dialog, struct sip_peer *peer); +int sip_dialog_build(struct sip_dialog *dialog, const char *opeer, struct ast_sockaddr *address, int new_dialog); +void sip_dialog_build_contact(struct sip_dialog *dialog, struct sip_request *request); +void sip_dialog_build_route(struct sip_dialog *dialog, struct sip_request *request, int backwards); +void sip_dialog_build_local_tag(struct sip_dialog *dialog); +void sip_dialog_build_nonce(struct sip_dialog *dialog, int force_update); + +void sip_dialog_check_via(struct sip_dialog *dialog, struct sip_request *request); +void sip_dialog_stop_rtp(struct sip_dialog *dialog); + +void sip_dialog_stop_reinvite(struct sip_dialog *dialog); +int sip_dialog_reinvite_timeout(const void *data); +int sip_dialog_start_need_reinvite(const void *data); +void sip_dialog_stop_need_reinvite(struct sip_dialog *dialog); + +int sip_dialog_parse_authorization(struct sip_dialog *dialog, struct sip_request *response, int method); +int sip_dialog_build_authorization(struct sip_dialog *dialog, int method); + +void sip_dialog_queue_connected_line_update(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_update_call_counter(struct sip_dialog *dialog, int event); +void sip_dialog_change_onhold(struct sip_dialog *dialog, struct sip_request *request, int onhold, int send_recv); + +int sip_dialog_check_peer_authorization(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer **auth_peer, int reliable); +int sip_dialog_check_register_authorization(struct sip_dialog *dialog, struct sip_request *request); +int sip_dialog_handle_authentication(struct sip_dialog *dialog, struct sip_request *request, int method, int init); + +void sip_history_append(struct sip_dialog *dialog, const char *type, const char *format, ...); + +void sip_extension_state_destroy(int 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 -durN asterisk-22.3.0.orig/channels/sip/include/dialplan_apps.h asterisk-22.3.0/channels/sip/include/dialplan_apps.h --- asterisk-22.3.0.orig/channels/sip/include/dialplan_apps.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/dialplan_apps.h 2025-04-17 11:11:22.805703436 +1200 @@ -0,0 +1,32 @@ +/* + * 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_APPS_H +#define _SIP_DIALPLAN_APPS_H + +int sip_app_dtmfmode(struct ast_channel *channel, const char *data); +int sip_app_addheader(struct ast_channel *channel, const char *data); +int sip_app_removeheader(struct ast_channel *channel, const char *data); +int sip_app_ciscopage(struct ast_channel *channel, const char *data); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/dialplan_funcs.h asterisk-22.3.0/channels/sip/include/dialplan_funcs.h --- asterisk-22.3.0.orig/channels/sip/include/dialplan_funcs.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/dialplan_funcs.h 2025-04-17 11:11:22.805703436 +1200 @@ -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_DIALPLAN_FUNCS_H +#define _SIP_DIALPLAN_FUNCS_H + +extern struct ast_custom_function sip_func_checkdomain; +extern struct ast_custom_function sip_func_header; +extern struct ast_custom_function sip_func_headers; +extern struct ast_custom_function sip_func_peer; + +int sip_func_channel_read(struct ast_channel *channel, const char *function, char *data, char *buf, size_t buf_len); +int sip_func_checkdomain_read(struct ast_channel *channel, const char *function, char *data, struct ast_str **buf, ssize_t max_len); +int sip_func_header_read(struct ast_channel *channel, const char *function, char *data, struct ast_str **buf, ssize_t max_len); +int sip_func_headers_read(struct ast_channel *channel, const char *function, char *data, struct ast_str **buf, ssize_t max_len); +int sip_func_peer_read(struct ast_channel *channel, const char *function, char *data, struct ast_str **buf, ssize_t max_len); +int sip_func_peer_write(struct ast_channel *channel, const char *function, char *data, const char *value); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/domain.h asterisk-22.3.0/channels/sip/include/domain.h --- asterisk-22.3.0.orig/channels/sip/include/domain.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/domain.h 2025-04-17 11:11:22.805703436 +1200 @@ -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_DOMAIN_H +#define _SIP_DOMAIN_H + +/* Modes for SIP domain handling in the PBX */ +enum { + SIP_DOMAIN_AUTO = 0, /* This domain is auto-configured */ + SIP_DOMAIN_CONFIG, /* This domain is from configuration */ +}; + +struct sip_domain { + AST_LIST_ENTRY(sip_domain) next; /* List mechanics */ + char name[MAXHOSTNAMELEN]; /* SIP domain name we are responsible for */ + char context[AST_MAX_EXTENSION]; /* Incoming context for this domain */ + int mode; /* How did we find this domain? */ +}; + +AST_LIST_HEAD(sip_domains_head, sip_domain); + +extern struct sip_domains_head sip_domains; /* The SIP domain list */ + +const char *sip_domain_mode2str(int mode); +int sip_domain_build(const char *name, int lineno, int mode); +void sip_domain_destroy_all(void); +int sip_domain_check(const char *name, char *context, size_t context_len); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/events.h asterisk-22.3.0/channels/sip/include/events.h --- asterisk-22.3.0.orig/channels/sip/include/events.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/events.h 2025-04-17 11:11:22.805703436 +1200 @@ -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 -durN asterisk-22.3.0.orig/channels/sip/include/fax.h asterisk-22.3.0/channels/sip/include/fax.h --- asterisk-22.3.0.orig/channels/sip/include/fax.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/fax.h 2025-04-17 11:11:22.805703436 +1200 @@ -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_FAX_H +#define _SIP_FAX_H + +/* 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 -durN asterisk-22.3.0.orig/channels/sip/include/handlers.h asterisk-22.3.0/channels/sip/include/handlers.h --- asterisk-22.3.0.orig/channels/sip/include/handlers.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/handlers.h 2025-04-17 11:11:22.807703382 +1200 @@ -0,0 +1,62 @@ +/* + * 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 + +/* Remotecc applications */ +enum { + SIP_REMOTECC_NONE = 0, + SIP_REMOTECC_CONFLIST, + SIP_REMOTECC_CALLBACK, +}; + +/* Forward declarations */ +struct sip_request; + +/* 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_handle_incoming(struct sip_socket *socket, char *data); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/manager.h asterisk-22.3.0/channels/sip/include/manager.h --- asterisk-22.3.0.orig/channels/sip/include/manager.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/manager.h 2025-04-17 11:11:22.807703382 +1200 @@ -0,0 +1,38 @@ +/* + * 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_qualify_peer(struct mansession *session, const struct message *message); +int sip_manager_show_registry(struct mansession *session, const struct message *message); +int sip_manager_peers(struct mansession *session, const struct message *message); +int sip_manager_show_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 *id, const char *peer); +void sip_publish_session_timeout(struct ast_channel *channelnel, const char *source); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/mwi_subscriptions.h asterisk-22.3.0/channels/sip/include/mwi_subscriptions.h --- asterisk-22.3.0.orig/channels/sip/include/mwi_subscriptions.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/mwi_subscriptions.h 2025-04-17 11:11:22.807703382 +1200 @@ -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_MWI_SUBSCRIPTIONS_H +#define _SIP_MWI_SUBSCRIPTIONS_H + +/* Forward declarations */ +struct sip_dialog; + +struct sip_mwi_subscription { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(username); /* Who we are sending the subscription as */ + AST_STRING_FIELD(auth_user); /* Who we *authenticate* as */ + AST_STRING_FIELD(hostname); /* Domain or host we subscribe to */ + AST_STRING_FIELD(secret); /* Password in clear text */ + 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 resubscribe_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 */ +}; + +/* The MWI subscription list */ +extern struct ao2_container *sip_mwi_subscriptions; + +int sip_mwi_subscription_build(const char *config, int lineno); +void sip_mwi_subscription_destroy_all(void); +void sip_mwi_subscription_start(struct sip_mwi_subscription *mwi, int when); +void sip_mwi_subscription_send_all(void); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/netsock.h asterisk-22.3.0/channels/sip/include/netsock.h --- asterisk-22.3.0.orig/channels/sip/include/netsock.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/netsock.h 2025-04-17 11:11:22.807703382 +1200 @@ -0,0 +1,101 @@ +/* + * 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_request; +struct sip_dialog; + +/* The SIP socket definition */ +struct sip_socket { + enum ast_transport transport; /* UDP, TCP or TLS */ + int fd; /* Filed descriptor, the actual socket */ + struct ast_tcptls_session_instance *tcptls_session; /* If tcp or tls, a socket manager */ + 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; /* 1 if this is a response packet (e.g. 200 OK), 0 if it is a request */ + unsigned int critical:1; /* non-zero if there is a critical error */ + 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; /* Retransmission number */ + 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; + int stop:1; /* TRUE if the thread needs to kill itself. (The module is being unloaded.) */ + 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_fd; +extern int *sip_socket_read_id; + +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_read(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_packet_cancel_resend(struct sip_packet *packet); +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); +int sip_packet_ack(struct sip_dialog *dialog, int method, uint32_t cseq, int response); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/parking.h asterisk-22.3.0/channels/sip/include/parking.h --- asterisk-22.3.0.orig/channels/sip/include/parking.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/parking.h 2025-04-17 11:11:22.807703382 +1200 @@ -0,0 +1,38 @@ +/* + * 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_request; +struct sip_peer; +struct sip_remotecc_data; + +int sip_remotecc_park(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data); +int sip_remotecc_parkmonitor(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/peers.h asterisk-22.3.0/channels/sip/include/peers.h --- asterisk-22.3.0.orig/channels/sip/include/peers.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/peers.h 2025-04-17 11:11:22.808703356 +1200 @@ -0,0 +1,238 @@ +/* + * 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_BOGUS_PEER_MD5SECRET "intentionally_invalid_md5_string" +#define SIP_QUALIFY_FREQ_NOT_OK (10 * 1000) /* Qualification: How often to check, if the host is down... */ + +enum { + SIP_PEER_UNMONITORED = -1, + SIP_PEER_UNREACHABLE = 0, + SIP_PEER_REACHABLE = 1, +}; + +/* Forward declarations */ +struct sip_dialog; +struct sip_selected; +struct sip_callback; + +/* Structure for SIP peer data, we place calls to peers if registered or fixed IP address (host) The field 'name' must be first otherwise + * sip_peer_cmp() will fail, as will astobj2 hashing of the structure */ +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(md5secret); /* Password in MD5 */ + AST_STRING_FIELD(description); /* Description of this peer */ + AST_STRING_FIELD(remote_secret); /* Remote secret (trunks, remote devices) */ + 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(username); /* Temporary username until registration */ + 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(full_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(mwi_from); /* Name to place in From header for outgoing NOTIFY requests */ + AST_STRING_FIELD(rtp_engine); /* RTP Engine to use */ + AST_STRING_FIELD(unsolicited_mailbox); /* Mailbox to store received unsolicited MWI NOTIFY messages information in */ + AST_STRING_FIELD(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(cisco_auth_name); /* Name of the primary line */ + AST_STRING_FIELD(cisco_soft_key); /* Name of the last soft-key sent via remotecc */ + AST_STRING_FIELD(cisco_device_name); /* Name of the device */ + AST_STRING_FIELD(cisco_active_load); /* Name of the active firmware load */ + AST_STRING_FIELD(cisco_inactive_load); /* Name of the inactive firmware load */ + AST_STRING_FIELD(cisco_qrt_url); /*< QRT URL */ + ); + struct sip_socket socket; /* Socket used for this peer */ + enum ast_transport default_outgoing_transport; /* Peer Registration may change the default outbound transport If register expires, + * default should be reset. to this value */ + /* things that don't belong in flags */ + unsigned int transports; /* Transports (enum ast_transport) that are acceptable for this peer */ + unsigned int realtime:1; /* this is a 'realtime' peer */ + unsigned int realtime_from_contact:1;/* copy fromcontact from realtime */ + unsigned int host_dynamic:1; /* Dynamic Peers register with Asterisk */ + unsigned int removed:1; /* That which bears the_mark should be deleted! */ + 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 sip_auth_realm_head *auth_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 */ + unsigned int do_not_disturb:1; /* Peer has set DoNotDisturb */ + unsigned int hunt_group:1; /* Peer is logged into the HuntGroup */ + int offhook; /* Peer has signalled that they are off-hook */ + int call_limit; /* Limit of concurrent calls */ + unsigned int t38_max_datagram; /* T.38 FaxMaxDatagram override */ + int busy_level; /* Level of active channels where we signal busy */ + int max_forwards; /* SIP Loop prevention */ + int new_messages; /* Heard voicemail messages */ + int old_messages; /* Unheard voicemail messages */ + unsigned int options; /* Supported SIP options */ + struct ast_flags flags[SIP_MAX_FLAGS]; /* SIP_ flags */ + AST_LIST_HEAD_NOLOCK(, sip_mailbox) mailboxes; /* Mailboxes that this peer cares about */ + AST_LIST_HEAD_NOLOCK(, sip_alias) aliases; /* Bulk register aliases that this peer cares about */ + AST_LIST_HEAD_NOLOCK(, sip_subscription) subscriptions; /* Subscriptions that this peer cares about */ + AST_LIST_HEAD_NOLOCK(, sip_selected) selected; /* Dialogs selected for joining */ + int max_call_bitrate; /* Maximum Bitrate for a video call */ + int register_expire_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 */ + struct ast_sockaddr default_address; /* Default IP address, used until registration */ + unsigned int port_in_uri:1; /* Whether the port should be included in the URI */ + struct sip_dialog *qualify_dialog; /* Call pointer */ + int qualify_expire_sched_id; /* Qualification: When to expire qualify (qualify= checking) */ + int last_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_freq; /* Qualification: Qualification: How often to check for the host to be up */ + struct timeval qualify_start; /* Qualification: Time for sending SIP OPTION in sip_peer_qualify() */ + int keepalive; /* Keepalive: How often to send keep alive packet */ + int keepalive_sched_id; /* Keepalive: Scheduled item for sending keep alive packet */ + struct ast_acl_list *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 stimer_mode; /* Mode of operation for Session-Timers */ + int stimer_refresher; /* Session-Timer refresher */ + int stimer_min_se; /* Lowest threshold for session refresh interval */ + int stimer_max_se; /* Highest 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 (fm Path headers) */ + int cisco_line_index; /* Line index number */ + int cisco_pickup_notify_timer; /* Toast timer for pickup notify */ + time_t cisco_pickup_notify_sent; /* Last time a pickup notify was sent */ + unsigned int disallowed_methods; + 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; + unsigned int removed:1; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(exten); + AST_STRING_FIELD(context); + ); +}; + +extern struct ao2_container *sip_peers; +extern struct ao2_container *sip_peers_by_address; + +extern int sip_static_count; +extern int sip_realtime_count; + +extern struct sip_peer *sip_bogus_peer; + +int sip_peer_hash(const void *data, int flags); +int sip_peer_cmp(void *data, void *arg, int flags); +int sip_peer_hash_by_address(const void *data, int flags); +int sip_peer_cmp_by_address(void *data, void *arg, int flags); +struct sip_peer *sip_peer_find(const char *peer, int realtime, int devstate_only); +struct sip_peer *sip_peer_find_by_address(const struct ast_sockaddr *address, int transport, int realtime, int devstate_only); + +int sip_peer_set_removed(void *data, void *arg, int flags); +void sip_peer_unlink_all(int removed_only); + +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 devstate_only); +int sip_peer_check_transport(struct sip_peer *peer, int transport); + +void sip_peer_update(struct sip_peer *peer, int expiry); +void sip_peer_register_exten(struct sip_peer *peer, int add); + +void sip_peer_register_aliases(struct sip_peer *peer); +void sip_peer_register_aliases_all(void); +int sip_peer_expire_register(const void *data); + +void sip_peer_update_subscriptions(struct sip_peer *peer); +void sip_peer_update_mailboxes(struct sip_peer *peer); +void sip_peer_set_messages(struct sip_peer *peer, int new_messages, int old_messages, int locked); +void sip_peer_dnsmgr_lookup(struct ast_sockaddr *old_address, struct ast_sockaddr *new_address, void *data); + +int sip_peer_get_status(struct sip_peer *peer, struct ast_str **status); +void sip_peer_get_mailboxes(struct sip_peer *peer, struct ast_str **mailboxes); +int sip_peer_send_mwi(struct sip_peer *peer, int cache_only); + +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); +int sip_peer_qualify_now(const void *data); +void sip_peer_qualify_all(void); +void sip_peer_keepalive_all(void); + +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_hunt_group(struct sip_peer *peer); +int sip_peer_send_call_forward(struct sip_peer *peer); +void sip_peer_send_qrt_url(struct sip_peer *peer); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/pickup.h asterisk-22.3.0/channels/sip/include/pickup.h --- asterisk-22.3.0.orig/channels/sip/include/pickup.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/pickup.h 2025-04-17 11:11:22.808703356 +1200 @@ -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 -durN asterisk-22.3.0.orig/channels/sip/include/proxy.h asterisk-22.3.0/channels/sip/include/proxy.h --- asterisk-22.3.0.orig/channels/sip/include/proxy.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/proxy.h 2025-04-17 11:11:22.808703356 +1200 @@ -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.outbound_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 */ + int force; /* If it's an outbound proxy, 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 -durN asterisk-22.3.0.orig/channels/sip/include/realtime.h asterisk-22.3.0/channels/sip/include/realtime.h --- asterisk-22.3.0.orig/channels/sip/include/realtime.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/realtime.h 2025-04-17 11:11:22.809703329 +1200 @@ -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 devstate_only); +void sip_realtime_update(struct sip_peer *peer, int expiry); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/recording.h asterisk-22.3.0/channels/sip/include/recording.h --- asterisk-22.3.0.orig/channels/sip/include/recording.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/recording.h 2025-04-17 11:11:22.809703329 +1200 @@ -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 -durN asterisk-22.3.0.orig/channels/sip/include/registry.h asterisk-22.3.0/channels/sip/include/registry.h --- asterisk-22.3.0.orig/channels/sip/include/registry.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/registry.h 2025-04-17 11:11:22.810703302 +1200 @@ -0,0 +1,112 @@ +/* + * 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_REGISTRY_H +#define _SIP_REGISTRY_H + +/* States for outbound registrations (with register= lines in sip.conf */ +enum { + SIP_REGISTRY_UNREGISTERED = 0, /* We are not registered. We should have a timeout scheduled for the initial (or next) registration + * transmission */ + SIP_REGISTRY_REQUEST_SENT, /* Registration request sent sent initial request, waiting for an ack or a timeout to retransmit the + * initial request. */ + SIP_REGISTRY_AUTHORIZATION_SENT, /* We have tried to authenticate entered after transmit_register with auth info, waiting for an ack. */ + SIP_REGISTRY_REGISTERED, /* Registered and done */ + SIP_REGISTRY_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 (not sure how correctly). */ + SIP_REGISTRY_TIMEOUT, /* Registration about to expire, renewing registration */ + SIP_REGISTRY_AUTHORIZATION_FAILED, /* We have no accepted credentials fatal - no chance to proceed */ + SIP_REGISTRY_FAILED, /* Registration failed after several tries fatal - no chance to proceed */ +}; + +/* Forward declarations */ +struct sip_dialog; +struct sip_request; + +/* Registrations with other SIP proxies Created by sip_register_build(), the entry is linked in the 'sip_registry' list, and never eleted + * (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_registry { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(config);/* register string from config */ + AST_STRING_FIELD(call_id); /* Global Call-ID */ + AST_STRING_FIELD(local_tag); /* Local tag generated same time as call-id */ + AST_STRING_FIELD(realm); /* Authorization realm */ + AST_STRING_FIELD(nonce); /* Authorization nonce */ + AST_STRING_FIELD(opaque); /* Opaque nonsense */ + AST_STRING_FIELD(qop); /* Quality of Protection, since SIP wasn't complicated enough yet. */ + AST_STRING_FIELD(domain); /* Registration domain */ + AST_STRING_FIELD(username); /* Who we are registering as */ + AST_STRING_FIELD(auth_user); /* Who we *authenticate* as */ + AST_STRING_FIELD(hostname); /* Domain or host we register to */ + AST_STRING_FIELD(secret); /* Password in clear text */ + AST_STRING_FIELD(md5secret); /* Password in md5 */ + AST_STRING_FIELD(exten); /* Contact extension */ + AST_STRING_FIELD(peer_name); /* Peer registering to */ + ); + 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 expire_sched_id; /* Sched ID of expiration */ + int default_expiry; /* Configured value to use for the Expires header */ + int expiry; /* Negotiated value used for the Expires header */ + int outgoing_expiry; /* How often to refresh */ + int attempts; /* Number of attempts (since the last success) */ + int timeout_sched_id; /* sched id of sip_reg_timeout */ + struct sip_dialog *dialog; /* create a sip_dialog structure for each outbound "registration dialog" in progress */ + int state; /* Registration state (see above) */ + struct timeval time; /* Last successful registration time */ + unsigned int valid_call_id:1; /* 0 means we haven't chosen call-id for this registry yet. */ + uint32_t outgoing_cseq; /* Sequence number we got to for REGISTERs for this registry */ + struct ast_dnsmgr_entry *dnsmgr; /* DNS refresh manager for register */ + struct ast_sockaddr address; /* Who the server thinks we are */ + int nonce_count; /* Nonce-count */ +}; + +extern struct ao2_container *sip_registry; + +const char *sip_registry_state2str(int state); + +int sip_registry_hash(const void *data, int flags); +int sip_registry_cmp(void *data, void *arg, int flags); + +int sip_registry_build(const char *config, int lineno); +void sip_registry_destroy_all(void); + +void sip_registry_build_call_id(struct sip_registry *registry); +void sip_registry_build_local_tag(struct sip_registry *registry); + +int sip_registry_handle_authentication(struct sip_dialog *dialog, struct sip_request *request); +void sip_registry_dnsmgr_lookup(struct ast_sockaddr *old_address, struct ast_sockaddr *new_address, void *data); + +void sip_registry_send_all(void); +int sip_registry_send(const void *data); +void sip_registry_expires(struct sip_registry *registry, int when); + +int sip_registry_timeout(const void *data); +void sip_registry_start_timeout(struct sip_registry *registry); +void sip_registry_stop_timeout(struct sip_registry *registry); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/request.h asterisk-22.3.0/channels/sip/include/request.h --- asterisk-22.3.0.orig/channels/sip/include/request.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/request.h 2025-04-17 11:11:22.810703302 +1200 @@ -0,0 +1,140 @@ +/* + * 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 + +#define SIP_MAX_HEADERS 64 /* Max amount of SIP headers to read */ +#define SIP_MAX_LINES 256 /* Max amount of lines in content (eg: SDP) */ + +/* SIP methods we suppory */ +#define SIP_ALLOWED_METHODS "ACK,BYE,CANCEL,INFO,INVITE,MESSAGE,NOTIFY,OPTIONS,PUBLISH,REFER,SUBSCRIBE,UPDATE" + +/* When sending a SIP 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_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 session */ +}; + +/* Whether to initialise a new request/branch in sip_send_invite */ +enum { + SIP_INIT_NONE = 0, + SIP_INIT_BRANCH, + SIP_INIT_REQUEST, +}; + +/* Forward declarations */ +struct sip_route; +struct sip_dialog; + +struct sip_request { + AST_LIST_ENTRY(sip_request) next; + const char *uri; /* Request URI */ + const char *status_line; /* Response status line */ + struct { + const char *name; /* Header name */ + const char *value; /* Header value */ + } header[SIP_MAX_HEADERS]; + const char *line[SIP_MAX_LINES]; /* String of each content line */ + int header_count; /* # of SIP Headers */ + int line_count; /* Body Content */ + int method; /* Method of this request */ + const char *call_id; /* Call-ID of this request */ + uint32_t cseq; /* CSeq of this request */ + int code; /* Response code */ + unsigned int options; /* Items needed for Required header in responses */ + 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; /* if non-zero This is a re-transmit, ignore it */ + unsigned int has_to_tag:1; /* non-zero if packet has To: tag */ + 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_request_alloc(struct sip_request *request, int method, const char *uri); +void sip_request_init(struct sip_request *request, struct sip_dialog *dialog, int method, const char *explicit_uri); +int sip_request_prepare(struct sip_request *request, struct sip_dialog *dialog, int method, uint32_t cseq, int new_branch); +int sip_request_parse(struct sip_request *request, char *data); +void sip_request_destroy(struct sip_request *request); +int sip_request_send(struct sip_dialog *dialog, struct sip_request *request, int reliable, uint32_t cseq); +void sip_request_copy(struct sip_request *to_req, const struct sip_request *from_req); +struct ast_str *sip_request_build(struct sip_request *request); + +const char *sip_request_next_header(const struct sip_request *request, const char *name, int *iter); +const char *sip_request_get_header(const struct sip_request *request, const char *name); +char *sip_request_get_content(struct sip_request *request, int line_start, int line_end); + +struct sip_dialog *sip_request_find_dialog(struct sip_request *request, struct sip_socket *socket); +int sip_request_find_boundary(struct sip_request *request, const char *boundary, int line_start, int *done); +const char *sip_request_find_content_type(struct sip_request *request); + +void sip_request_add_header(struct sip_request *request, const char *name, const char *value); +void sip_request_build_header(struct sip_request *request, const char *name, const char *format, ...); +void sip_request_copy_header(struct sip_request *request, const struct sip_request *orig_req, const char *name); + +void sip_request_add_via(struct sip_request *request, struct sip_dialog *dialog); +void sip_request_add_supported(struct sip_request *request, struct sip_dialog *dialog); +void sip_request_add_expires(struct sip_request *request, int expires); +void sip_request_add_date(struct sip_request *request); +void sip_request_add_authorization(struct sip_request *request, struct sip_dialog *dialog); +void sip_request_add_remote_party_id(struct sip_request *request, struct sip_dialog *dialog); +void sip_request_add_max_forwards(struct sip_request *request, struct sip_dialog *dialog); +void sip_request_add_call_info(struct sip_request *request, struct sip_dialog *dialog); +void sip_request_add_route(struct sip_request *request, struct sip_route *route, int skip); +void sip_request_add_diversion(struct sip_request *request, struct sip_dialog *dialog); +void sip_request_add_join(struct sip_request *request, struct sip_dialog *dialog); +void sip_request_add_require(struct sip_request *request); + +void sip_request_add_content(struct sip_request *request, const char *line); +void sip_request_build_content(struct sip_request *request, const char *format, ...); + +int sip_send_request(struct sip_dialog *dialog, int method, int add_sdp, int init, const char *explicit_uri); + +int sip_send_invite(struct sip_dialog *dialog, int add_sdp, int init, const char *explicit_uri); +int sip_send_reinvite_with_sdp(struct sip_dialog *dialog, int old_sdp, int add_image); +int sip_send_update(struct sip_dialog *dialog); +int sip_send_options(struct sip_dialog *dialog); +int sip_send_ack(struct sip_dialog *dialog, uint32_t cseq, int new_branch); +int sip_send_bye(struct sip_dialog *dialog); +int sip_send_cancel(struct sip_dialog *dialog); +int sip_send_register(struct sip_registry *registry, int add_authorization); +int sip_send_subscribe(struct sip_dialog *dialog, int init); +int sip_send_notify(struct sip_dialog *dialog, int init); +int sip_send_notify_with_sipfrag(struct sip_dialog *dialog, uint32_t cseq, char *message, int terminate); +int sip_send_notify_with_extension_state(struct sip_dialog *dialog, struct ast_state_cb_info *state_info, int timeout); +int sip_send_notify_with_mwi(struct sip_dialog *dialog, int new_messages, int old_messages, const char *mwi_exten); +int sip_send_refer(struct sip_dialog *dialog); +int sip_send_refer_with_content(struct sip_dialog *dialog, const char *content_type, const char *content); +int sip_send_info_with_media_control(struct sip_dialog *dialog); +int sip_send_message(struct sip_dialog *dialog, int init, int add_authorization); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/response.h asterisk-22.3.0/channels/sip/include/response.h --- asterisk-22.3.0.orig/channels/sip/include/response.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/response.h 2025-04-17 11:11:22.810703302 +1200 @@ -0,0 +1,57 @@ +/* + * 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_request; + +int sip_response_prepare(struct sip_request *response, struct sip_dialog *dialog, const char *status_line, struct sip_request *request); +int sip_response_send(struct sip_dialog *dialog, struct sip_request *response, int reliable, uint32_t cseq); + +int sip_send_response(struct sip_dialog *dialog, const char *status_line, struct sip_request *request); +int sip_send_response_provisional(struct sip_dialog *dialog, const char *status_line, struct sip_request *request, int with_sdp); +int sip_send_response_reliable(struct sip_dialog *dialog, const char *status_line, struct sip_request *request); +int sip_send_response_with_sdp(struct sip_dialog *dialog, const char *status_line, struct sip_request *request, int reliable, + int old_sdp, int add_remote_party_id); +int sip_send_response_with_date(struct sip_dialog *dialog, const char *status_line, struct sip_request *request); +int sip_send_response_with_unsupported(struct sip_dialog *dialog, const char *status_line, struct sip_request *request, + const char *unsupported); +int sip_send_response_with_www_authenticate(struct sip_dialog *dialog, const char *status_line, struct sip_request *request, + int reliable, int stale); +int sip_send_response_with_authorization_failure(struct sip_dialog *dialog, struct sip_request *request, int res, int reliable); +int sip_send_response_with_fake_authorization(struct sip_dialog *dialog, struct sip_request *request); +int sip_send_response_with_remote_party_id(struct sip_dialog *dialog, const char *status_line, struct sip_request *request); +int sip_send_response_with_min_se(struct sip_dialog *dialog, const char *status_line, struct sip_request *request, int min_se); +int sip_send_response_with_min_expires(struct sip_dialog *dialog, const char *status_line, struct sip_request *request, int min_expires); +int sip_send_response_with_optionsind(struct sip_dialog *dialog, struct sip_request *request); +int sip_send_response_with_accept(struct sip_dialog *dialog, const char *status_line, struct sip_request *request); +int sip_send_response_with_retry_after(struct sip_dialog *dialog, const char *status_line, struct sip_request *request, int retry_after); +int sip_send_response_with_feature_event(struct sip_dialog *dialog, int feature, struct sip_request *request); + +int sip_send_response_using_temp(struct sip_socket *socket, const char *status_line, struct sip_request *request); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/route.h asterisk-22.3.0/channels/sip/include/route.h --- asterisk-22.3.0.orig/channels/sip/include/route.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/route.h 2025-04-17 11:11:22.811703276 +1200 @@ -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_ROUTE_H +#define _SIP_ROUTE_H + +/* Internal enum to remember last calculated */ +enum { + SIP_ROUTE_LOOSE = 0, /* The first hop contains ;lr or does not exist */ + SIP_ROUTE_STRICT, /* The first hop exists and does not contain ;lr */ + SIP_ROUTE_INVALIDATED, /* strict/loose routing needs to be rechecked */ +}; + +/* 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; +}; + +const char *sip_route_add(struct sip_route *route, const char *uri, int insert_head); +void sip_route_parse(struct sip_route *route, const char *header, int insert_head); +void sip_route_copy(struct sip_route *to_route, const struct sip_route *from_route); +void sip_route_clear(struct sip_route *route); +void sip_route_dump(const struct sip_route *route); +struct ast_str *sip_route_list(const struct sip_route *route, int cli_format, 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 -durN asterisk-22.3.0.orig/channels/sip/include/rtp_glue.h asterisk-22.3.0/channels/sip/include/rtp_glue.h --- asterisk-22.3.0.orig/channels/sip/include/rtp_glue.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/rtp_glue.h 2025-04-17 11:11:22.811703276 +1200 @@ -0,0 +1,39 @@ +/* + * 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 -durN asterisk-22.3.0.orig/channels/sip/include/sdp.h asterisk-22.3.0/channels/sip/include/sdp.h --- asterisk-22.3.0.orig/channels/sip/include/sdp.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/sdp.h 2025-04-17 11:11:22.811703276 +1200 @@ -0,0 +1,57 @@ +/* + * 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_T38_NONE = 0, /* Do not modify T38 information at all */ + SIP_SDP_T38_INITIATE, /* Remote side has requested T38 with us */ + SIP_SDP_T38_ACCEPT, /* Remote side accepted our T38 request */ +}; + +enum { + SIP_SDP_SEND_UNKNOWN = -1, + SIP_SDP_SEND_RECV = 0, + SIP_SDP_SEND_ONLY = 1, + SIP_SDP_SEND_INACTIVE = 2, +}; + +/* 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; + int 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_request; + +int sip_sdp_find(struct sip_request *request); +int sip_sdp_parse(struct sip_dialog *dialog, struct sip_request *request, int add_image, int is_offer); +int sip_sdp_build(struct sip_dialog *dialog, struct sip_request *request, int old_sdp, int add_media, int add_image); + +void sip_sdp_media_destroy(struct sip_dialog *dialog); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/security_events.h asterisk-22.3.0/channels/sip/include/security_events.h --- asterisk-22.3.0.orig/channels/sip/include/security_events.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/security_events.h 2025-04-17 11:11:22.812703249 +1200 @@ -0,0 +1,44 @@ +/* + * 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_request; + +void sip_report_inval_acct(const struct sip_dialog *dialog); +void sip_report_failed_acl(const struct sip_dialog *dialog, const char *acl_name); +void sip_report_inval_password(const struct sip_dialog *dialog, const char *response_challenge, const char *response_hash); +void sip_report_auth_success(const struct sip_dialog *dialog, uint32_t using_password); +void sip_report_session_limit(const struct sip_dialog *dialog); +void sip_report_chal_resp_failed(const struct sip_dialog *dialog, const char *response, const char *expected_response); +void sip_report_chal_sent(const struct sip_dialog *dialog); +void sip_report_inval_transport(const struct sip_dialog *dialog, const char *transport); + +int sip_report_security_event(const struct sip_dialog *dialog, const struct sip_request *request, const int res); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/sip.h asterisk-22.3.0/channels/sip/include/sip.h --- asterisk-22.3.0.orig/channels/sip/include/sip.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/sip.h 2025-04-17 11:11:22.812703249 +1200 @@ -0,0 +1,221 @@ +/* + * 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. */ + +/* Standard SIP unsecure port for UDP and TCP from RFC 3261. DO NOT CHANGE THIS */ +#define SIP_STANDARD_PORT 5060 +/* Standard SIP TLS port from RFC 3261. DO NOT CHANGE THIS */ +#define SIP_STANDARD_TLS_PORT 5061 +/* z9hG4bK is a magic cookie. See RFC 3261 section 8.1.1.7 */ +#define SIP_MAGIC_COOKIE "z9hG4bK" +#define SIP_MAX_FLAGS 3 /* Flags array size */ + +/* Various flags for the flags field in the dialog structure Trying to sort these up (one or more of the following): D: Dialog (dialog), + * P: Peer, G: Global. When flags are used by multiple structures, it is important that they have a common layout so it is easy to + * copy them */ + +/* flags[0]: */ +enum { + SIP_OUTGOING = 1 << 0, /* D: Direction of the last transaction in this dialog */ + SIP_RINGING = 1 << 1, /* D: Have sent 180 ringing */ + SIP_PROGRESS_SENT = 1 << 2, /* D: Have sent 183 message progress */ + SIP_NEED_REINVITE = 1 << 3, /* D: Do we need to send another reinvite? */ + SIP_PENDING_BYE = 1 << 4, /* D: Need to send bye after we ack? */ + SIP_GOT_REFER = 1 << 5, /* D: Got a refer? */ + SIP_CALL_LIMIT = 1 << 6, /* D: Call limit enforced for this call */ + SIP_CALL_COUNTER_INUSE = 1 << 7, /* D: Did this dialog increment the counter of in-use calls? */ + SIP_CALL_COUNTER_RINGING = 1 << 8, /* D: Did this connection increment the counter of in-use calls? */ + SIP_DEFER_BYE_ON_TRANSFER = 1 << 9, /* D: Do not hangup at first ast_hangup */ + SIP_PROMISCUOUS_REDIRECT = 1 << 10, /* DP: Promiscuous redirection */ + SIP_USER_EQ_PHONE = 1 << 11, /* DP: Add user=phone to numeric URI. Default off */ + SIP_DTMF_RFC2833 = 1 << 12, /* DP: DTMF Support: RTP DTMF - "rfc2833" */ + SIP_DTMF_INBAND = 1 << 13, /* DP: DTMF Support: Inband audio, only for ULAW/ALAW - "inband" */ + SIP_DTMF_AUTO = 1 << 14, /* DP: DTMF Support: AUTO switch between rfc2833 and in-band DTMF */ + SIP_DTMF = SIP_DTMF_RFC2833 | SIP_DTMF_INBAND | SIP_DTMF_AUTO, /* DP: DTMF Support: settings */ + SIP_NAT_FORCE_RPORT = 1 << 15, /* DP: Force rport even if not present in the request */ + SIP_NAT_RPORT_PRESENT = 1 << 16, /* DP: rport was present in the request */ + SIP_REINVITE_NONE = 0, /* DP: no reinvite allowed */ + SIP_DIRECT_MEDIA = 1 << 17, /* DP: allow peers to be reinvited to send media directly p2p */ + SIP_DIRECT_MEDIA_NAT = 1 << 18, /* DP: allow media reinvite when new peer is behind NAT */ + SIP_REINVITE_UPDATE = 1 << 19, /* DP: use UPDATE RFC3311 when reinviting this peer */ + /* DP: four settings, uses three bits */ + SIP_REINVITE = SIP_REINVITE_NONE | SIP_DIRECT_MEDIA | SIP_DIRECT_MEDIA_NAT | SIP_REINVITE_UPDATE, + SIP_PROGRESS_INBAND_NO = 0, + SIP_PROGRESS_INBAND_YES = 1 << 20, + SIP_PROGRESS_INBAND_NEVER = 1 << 21, + /* DP: three settings, uses two bits */ + SIP_PROGRESS_INBAND = SIP_PROGRESS_INBAND_NO | SIP_PROGRESS_INBAND_NEVER | SIP_PROGRESS_INBAND_YES, + SIP_USE_PATH = 1 << 22, /* GDP: Trust and use incoming Path headers? */ + SIP_TRUST_REMOTE_PARTY_ID = 1 << 23, /* DP: Trust RPID headers? */ + SIP_SEND_REMOTE_PARTY_ID_NO = 0, + SIP_SEND_REMOTE_PARTY_ID_YES = 1 << 24, /* Use "Remote-Party-ID" for rpid */ + SIP_SEND_REMOTE_PARTY_ID_PAI = 1 << 25, /* Use "P-Asserted-Identity" for rpid */ + /* DP: Remote Party-ID Support */ + SIP_SEND_REMOTE_PARTY_ID = SIP_SEND_REMOTE_PARTY_ID_NO | SIP_SEND_REMOTE_PARTY_ID_YES | SIP_SEND_REMOTE_PARTY_ID_PAI, + SIP_RFC2833_COMPENSATE = 1 << 26, /* DP: Compensate for buggy RFC2833 implementations */ + SIP_USE_SRTP = 1 << 27, /* DP: Whether we should offer only SRTP */ + SIP_DIRECT_MEDIA_OUTGOING = 1 << 28, /* DP: Only send direct media reinvites on outgoing calls */ + SIP_FORCE_STATE_CHANGE = 1 << 29, /* Force a extension state change notification */ + + /* Which flags to copy */ + SIP_FLAGS0_MASK = SIP_PROMISCUOUS_REDIRECT | SIP_TRUST_REMOTE_PARTY_ID | SIP_SEND_REMOTE_PARTY_ID | SIP_DTMF | SIP_REINVITE | + SIP_PROGRESS_INBAND | SIP_NAT_FORCE_RPORT | SIP_USE_SRTP | SIP_USER_EQ_PHONE | SIP_USE_PATH | SIP_RFC2833_COMPENSATE +}; + +/* flags[1]: */ +enum { + SIP_REALTIME_CACHE_PEERS = 1 << 0, /* GP: Should we keep RT objects in memory for extended time? */ + SIP_REALTIME_AUTO_CLEAR = 1 << 1, /* GP: Should we clean memory from peers after expiry? */ + SIP_Q850_REASON = 1 << 2, /* DP: Get/send cause code via Reason header */ + SIP_SYMMETRIC_RTP = 1 << 3, /* GDP: Whether symmetric RTP is enabled or not */ + SIP_QUEUED_STATE_CHANGE = 1 << 4, /* D: Unsent state pending change exists */ + SIP_CONNECTEDLINE_UPDATE_PENDING = 1 << 5, + SIP_RPORT_PRESENT = 1 << 6, /* D: Was rport received in the Via header? */ + SIP_PREFERRED_CODEC_ONLY = 1 << 7, /* GDP: Only respond with single most preferred joint codec */ + SIP_VIDEO_SUPPORT = 1 << 8, /* DP: Video supported if offered? */ + SIP_TEXT_SUPPORT = 1 << 9, /* GDP: Global text enable */ + SIP_ALLOW_SUBSCRIBE = 1 << 10, /* GP: Allow subscriptions from this peer? */ + SIP_ALLOW_OVERLAP_NO = 0, /* No, terminate with 404 Not found */ + SIP_ALLOW_OVERLAP_YES = 1 << 11, /* Yes, using the 484 Address Incomplete response */ + SIP_ALLOW_OVERLAP_DTMF = 1 << 12, /* Yes, using the DTMF transmission through Early Media */ + /* DP: Allow overlap dialing ? */ + SIP_ALLOW_OVERLAP = SIP_ALLOW_OVERLAP_NO | SIP_ALLOW_OVERLAP_YES | SIP_ALLOW_OVERLAP_DTMF, + SIP_SUBSCRIBE_MWI_ONLY = 1 << 13, /* GP: Only issue MWI notification if subscribed to */ + SIP_IGNORE_SDP_VERSION = 1 << 14, /* GDP: Ignore the SDP session version number we receive and treat all sessions as new */ + SIP_T38_SUPPORT_UDPTL = 1 << 15, /* GDP: T.38 Fax Support (no error correction, */ + SIP_T38_SUPPORT_UDPTL_FEC = 1 << 16, /* GDP: T.38 Fax Support (FEC error correction, */ + SIP_T38_SUPPORT_UDPTL_REDUNDANCY = 1 << 17, /* GDP: T.38 Fax Support (redundancy error correction, */ + /* GDP: T.38 Fax Support */ + SIP_T38_SUPPORT = SIP_T38_SUPPORT_UDPTL | SIP_T38_SUPPORT_UDPTL_FEC | SIP_T38_SUPPORT_UDPTL_REDUNDANCY, + SIP_ONHOLD_ACTIVE = 1 << 18, /* D: Active hold */ + SIP_ONHOLD_RECV_ONLY = 1 << 19, /* D: One directional hold */ + SIP_ONHOLD_INACTIVE = 1 << 20, /* D: Inactive hold */ + SIP_ONHOLD = SIP_ONHOLD_ACTIVE | SIP_ONHOLD_RECV_ONLY | SIP_ONHOLD_INACTIVE, /* D: Call hold states: */ + SIP_CISCO_USECALLMANAGER = 1 << 21, /* DP: Enable Cisco USECALLMANAGER phone support */ + SIP_DIALOG_ESTABLISHED = 1 << 22, /* D: Has a dialog been established? */ + SIP_FAX_DETECT_CNG = 1 << 23, /* DP: Fax Detection support - detect CNG in audio */ + SIP_FAX_DETECT_T38 = 1 << 24, /* DP: Fax Detection support - detect T.38 reinvite from peer */ + SIP_FAX_DETECT = SIP_FAX_DETECT_CNG | SIP_FAX_DETECT_T38, /* DP: Fax Detection support */ + SIP_UDPTL_DESTINATION = 1 << 25, /* DP: Use source IP of RTP as destination if NAT is enabled */ + SIP_VIDEO_SUPPORT_ALWAYS = 1 << 26, /* DP: Always set up video, even if endpoints don't support it */ + SIP_HAVE_PEER_CONTEXT = 1 << 27, /* Are we associated with a configured peer context? */ + SIP_TRUST_ID_OUTBOUND_NO = 1 << 28, /* Do not provide private presence information, do not include PAI/RPID when private */ + SIP_TRUST_ID_OUTBOUND_YES = 1 << 29, /* Yes, provide private presence information in PAI/RPID headers */ + /* DP: Do we trust the peer with private presence information? */ + SIP_TRUST_ID_OUTBOUND = SIP_TRUST_ID_OUTBOUND_NO | SIP_TRUST_ID_OUTBOUND_YES, + SIP_TRANSFER_RESPONSE = 1 << 30, /* D: Send specific transfer-response for call */ + SIP_ALLOW_TRANSFER = 1 << 31, /* GP: Allow transfer */ + + /* Which flags to copy */ + SIP_FLAGS1_MASK = SIP_ALLOW_SUBSCRIBE | SIP_ALLOW_OVERLAP | SIP_IGNORE_SDP_VERSION | SIP_VIDEO_SUPPORT | SIP_T38_SUPPORT | + SIP_TEXT_SUPPORT | SIP_CISCO_USECALLMANAGER | SIP_FAX_DETECT | SIP_UDPTL_DESTINATION | SIP_VIDEO_SUPPORT_ALWAYS | + SIP_PREFERRED_CODEC_ONLY | SIP_SYMMETRIC_RTP | SIP_DIRECT_MEDIA_OUTGOING | SIP_Q850_REASON | SIP_HAVE_PEER_CONTEXT | + SIP_TRUST_ID_OUTBOUND | SIP_ALLOW_TRANSFER +}; + +/* flags[2]: */ +enum { + SIP_SRTP_TAG_32 = 1 << 0, /* DP: Use a 32bit auth tag in INVITE not 80bit */ + SIP_NAT_AUTO_RPORT = 1 << 1, /* DGP: Set SIP_NAT_FORCE_RPORT when NAT is detected */ + SIP_NAT_AUTO_COMEDIA = 1 << 2, /* DGP: Set SIP_SYMMETRIC_RTP when NAT is detected */ + SIP_USE_AVPF = 1 << 3, /* DGP: Support a minimal AVPF-compatible profile */ + SIP_ICE_SUPPORT = 1 << 4, /* DGP: Enable ICE support */ + SIP_IGNORE_OUTGOING_FORMAT = 1 << 5, /* DP: Ignore prefcaps when setting up an outgoing call leg */ + SIP_FORCE_AVP = 1 << 6, /* DGP: Force 'RTP/AVP' for all streams, even DTLS */ + SIP_RTCP_MUX = 1 << 7, /* DGP: Attempt to negotiate RFC 5761 RTCP multiplexing */ + SIP_DND_BUSY = 1 << 8, /* DPG: Treat endpoint as busy when DND is enabled */ + SIP_SUBSCRIPTION_STATE_ACTIVE = 1 << 9, /* D: Force Subscription-State to be active for NOTIFYs */ + SIP_CISCO_KEEP_CONFERENCE = 1 << 10, /* DGP: Keep ad-hoc conference after initiator hangs up */ + SIP_CISCO_MULTI_ADMIN_CONFERENCE = 1 << 11, /* DGP: Allow participants to administrate conference */ + SIP_RELAY_NEAREND = 1 << 12, /* D: Add x-relay-nearend attribute to SDP */ + SIP_RELAY_FAREND = 1 << 13, /* D: Add x-relay-farend attribute to SDP */ + SIP_SDP_ACK = 1 << 14, /* D: Add SDP to ACK */ + SIP_CISCO_RECORDING = 1 << 15, /* D: Call is now being recorded */ + SIP_SEND_QRT_URL = 1 << 16, /* D: Display RTP stats on BYE */ + SIP_HUNTGROUP_DEFAULT = 1 << 17, /* GP: If peer is logged into huntgroup by default */ + SIP_CISCO_PICKUP_NOTIFY_FROM = 1 << 18, /* P: Include the from number in the statusline for pickup notify */ + SIP_CISCO_PICKUP_NOTIFY_TO = 1 << 19, /* P: Include the to number in the statusline for pickup notify */ + SIP_CISCO_PICKUP_NOTIFY_BEEP = 1 << 20, /* P: Play a beep for pickup notify */ + SIP_PROVISIONAL_KEEPALIVE_SDP = 1 << 21, /* D: Add SDP to provisional keepalive */ + + /* Which flags to copy */ + SIP_FLAGS2_MASK = SIP_SRTP_TAG_32 | SIP_NAT_AUTO_RPORT | SIP_NAT_AUTO_COMEDIA | SIP_USE_AVPF | SIP_ICE_SUPPORT | + SIP_IGNORE_OUTGOING_FORMAT | SIP_RTCP_MUX | SIP_DND_BUSY | SIP_CISCO_KEEP_CONFERENCE | SIP_CISCO_MULTI_ADMIN_CONFERENCE | + SIP_HUNTGROUP_DEFAULT | SIP_CISCO_PICKUP_NOTIFY_FROM | SIP_CISCO_PICKUP_NOTIFY_TO | SIP_CISCO_PICKUP_NOTIFY_BEEP | + SIP_FORCE_AVP +}; + +/* We store separately the debugging requests from the config file and requests from the CLI. Debugging is enabled if either is set + * (which means that if sipdebug is set in the config file, we can only turn it off by reloading the config). */ +enum { + SIP_DEBUG_NONE = 0, + SIP_DEBUG_CONFIG, + SIP_DEBUG_CONSOLE, +}; + +/* Forward declarations */ +struct sip_dialog; +struct sip_registry; +struct sip_mwi_subscription; + +/* Generic data for ast_sched_add callbacks */ +struct sip_sched_data { + union { + struct sip_dialog *dialog; + struct sip_registry *registry; + struct sip_mwi_subscription *mwi; + }; + int when; +}; + +extern int sip_log_level; +extern int sip_debug; +extern struct ast_sockaddr sip_debug_address; + +extern struct ast_sched_context *sip_sched_context; +extern pthread_t sip_monitor_threadid; + +void sip_module_ref(void); +void sip_module_unref(void); +void sip_module_notice(void); + +struct stasis_message_type *sip_session_timeout_type(void); + +int sip_monitor_restart(void); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/stimer.h asterisk-22.3.0/channels/sip/include/stimer.h --- asterisk-22.3.0.orig/channels/sip/include/stimer.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/stimer.h 2025-04-17 11:11:22.813703222 +1200 @@ -0,0 +1,75 @@ +/* + * 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_STIMER_H +#define _SIP_STIMER_H + +/* Modes in which Asterisk can be configured to run SIP Session-Timers */ +enum { + SIP_STIMER_MODE_INVALID = 0, /* Invalid value */ + SIP_STIMER_MODE_ACCEPT, /* Honor inbound Session-Timer requests */ + SIP_STIMER_MODE_ORIGINATE, /* Originate outbound and honor inbound requests */ + SIP_STIMER_MODE_REFUSE, /* Ignore inbound Session-Timers requests */ +}; + +/* The entity playing the refresher role for Session-Timers */ +enum { + SIP_STIMER_REFRESHER_AUTO, /* Negotiated */ + SIP_STIMER_REFRESHER_UAC, /* Initially prefer session refresh by Asterisk */ + SIP_STIMER_REFRESHER_UAS, /* Initially prefer session refresh by the other side */ +}; + +/* Forward declarations */ +struct sip_dialog; +struct sip_request; + +/* Structure that encapsulates all attributes related to running Session-Timers feature on a per dialog basis */ +struct sip_stimer { + int active:1; /* Session-Timers on/off */ + int remote_active:1; /* Session-Timers on/off in peer UA */ + int interval; /* Session-Timers negotiated session refresh interval */ + int refresher; /* Session-Timers cached refresher */ + int sched_id; /* Session-Timers ast_sched scheduler id */ + int cached_min_se; /* Session-Timers cached Min-SE */ + int cached_max_se; /* Session-Timers cached Session-Expires */ + int cached_mode; /* Session-Timers cached M.O. */ + int cached_refresher; /* Session-Timers session refresher */ +}; + +const char *sip_stimer_mode2str(int mode); +const char *sip_stimer_refresher2str(int refresher); + +struct sip_stimer *sip_stimer_alloc(struct sip_dialog *dialog); +int sip_stimer_handle_invite(struct sip_dialog *dialog, struct sip_request *request, int reinvite); + +int sip_stimer_get_expiry(struct sip_dialog *dialog, int cached_max); +int sip_stimer_get_refresher(struct sip_dialog *dialog); +int sip_stimer_get_mode(struct sip_dialog *dialog, int no_cached); + +void sip_stimer_start(struct sip_dialog *dialog); +void sip_stimer_stop(struct sip_dialog *dialog); +void sip_stimer_restart(struct sip_dialog *dialog); + +void sip_stimer_delete_sched(struct sip_dialog *dialog); + +#endif diff -durN asterisk-22.3.0.orig/channels/sip/include/utils.h asterisk-22.3.0/channels/sip/include/utils.h --- asterisk-22.3.0.orig/channels/sip/include/utils.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/include/utils.h 2025-04-17 11:11:22.813703222 +1200 @@ -0,0 +1,182 @@ +/* + * 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 Request methods known by Asterisk. MUST match the order of the methods in sip_methods */ +enum { + SIP_METHOD_UNKNOWN = -1, /* Unknown */ + SIP_METHOD_OPTIONS = 0, /* Check capabilities of a device, used for "ping" too */ + SIP_METHOD_INVITE, /* Set up a session */ + SIP_METHOD_ACK, /* End of a three-way handshake started with INVITE. */ + SIP_METHOD_BYE, /* End of a session */ + SIP_METHOD_CANCEL, /* Cancel an INVITE */ + SIP_METHOD_REGISTER, /* Registration to the proxy, tell us where you are located */ + SIP_METHOD_NOTIFY, /* Status update, Part of the event package standard, result of a SUBSCRIBE or a REFER */ + SIP_METHOD_REFER, /* Refer to another URI (transfer) */ + SIP_METHOD_SUBSCRIBE, /* Subscribe for updates (voicemail, session status, device status, presence) */ + SIP_METHOD_PUBLISH, /* Presence state */ + SIP_METHOD_UPDATE, /* Update a dialog. We can send UPDATE; but not accept it */ + SIP_METHOD_MESSAGE, /* Text messaging */ + SIP_METHOD_INFO, /* Information updates during a session */ + SIP_METHOD_PRACK, /* Reliable pre-call signalling. Not supported in Asterisk. */ + SIP_METHOD_PING, /* Not supported at all, no standard but still implemented out there */ +}; + +/* Define SIP option tags, used in Require: and Supported: headers. We need to be aware of these properties in the phones to use the + * Replaces: header. We should not do that without knowing that the other end supports it. This is nothing we can configure, we learn by + * the dialog Supported: header on the REGISTER (peer) or the INVITE (other devices). We are not using many of these today, */ +enum { + SIP_OPTION_REPLACES = 0, + SIP_OPTION_TIMER, + SIP_OPTION_NOREFERSUB, + SIP_OPTION_EXTENDED_REFER, + SIP_OPTION_100REL, + SIP_OPTION_EVENTLIST, + SIP_OPTION_EARLY_SESSION, + SIP_OPTION_FROM_CHANGE, + SIP_OPTION_GRUU, + SIP_OPTION_HISTINFO, + SIP_OPTION_JOIN, + SIP_OPTION_OUTBOUND, + SIP_OPTION_PATH, + SIP_OPTION_PREF, + SIP_OPTION_PRECONDITION, + SIP_OPTION_PRIVACY, + SIP_OPTION_RESOURCE_PRIORITY, + SIP_OPTION_RECIPIENT_LIST_INVITE, + SIP_OPTION_RECIPIENT_LIST_SUBSCRIBE, + SIP_OPTION_SEC_AGREE, + SIP_OPTION_SDP_ANAT, + SIP_OPTION_TARGET_DIALOG, +}; + +enum { + SIP_PARSE_REFER_MISSING_HEADER = -3, + SIP_PARSE_REFER_BAD_URI = -2, + SIP_PARSE_REFER_BAD_EXTENSION = -1, + SIP_PARSE_REFER_SUCCESS = 0, +}; + +/* Forward declarations */ +struct sip_peer; +struct sip_dialog; + +struct sip_methods { + const char *name; + unsigned int need_rtp:1; /* when this is the 'primary' use for a dialog structure, does it need RTP? */ + unsigned int can_create:1; +}; + +/* List of well-known SIP options. If we get this in a require, we should check the list and answer accordingly. */ +struct sip_options { + const char *name; /* Name, as in standard */ + unsigned int supported:1; /* Supported by Asterisk ? */ +}; + +/* Structure to store Authrization information */ +struct sip_authorization_data { + const char *uri; + const char *username; + const char *nonce; + const char *opaque; + const char *realm; + const char *domain; + const char *qop; + const char *algorithm; + const char *response; +}; + +extern const struct sip_options sip_options[]; +extern const struct sip_methods sip_methods[]; + +int sip_parse_uri(char *uri, const char *schemes, char **user, char **domain, char **transport); +char *sip_remove_uri_parameters(char *uri); +int sip_cmp_uri(const char *uri1, const char *uri2); + +int sip_get_name(const char *contact, struct ast_str **name); +char *sip_get_in_brackets(char *contact); +int sip_get_address(const char *full_contact, struct ast_sockaddr *addr); +int sip_get_tag(struct sip_request *request, const char *header, struct ast_str **tag); +void sip_get_our_address(unsigned int transport, const struct ast_sockaddr *address, struct ast_sockaddr *our_address); + +int sip_cmp_method(int method, const char *name); +int sip_find_method(const char *name); +void sip_set_method_allowed(unsigned int *allowed_methods, int method, int allow); +int sip_allowed_method(unsigned int *allowed_methods, int method); +void sip_parse_methods(unsigned int *methods, const char *allow); + +int sip_parse_allow(struct sip_dialog *dialog, struct sip_request *request); + +void sip_compress_headers(char *data); +void sip_pedantic_decode(char *buf); +unsigned int sip_str2port(const char *str, unsigned int standard_port); + +int sip_hangup2cause(int cause); +const char *sip_cause2hangup(int cause); + +const char *sip_reason2str(struct ast_party_redirecting_reason *reason); +const char *sip_dtmf_mode2str(int mode); +const char *sip_refer_status2str(int status); +const char *sip_invite_state2str(int state); +const char *sip_allow_overlap2str(int mode); +const char *sip_t38_ecmode2str(int faxec); +const char *sip_force_rport2str(struct ast_flags *flags); +const char *sip_comedia2str(struct ast_flags *flags); +const char *sip_trust_id_outbound2str(int mode); +const char *sip_nat_mode2str(struct ast_flags *flags); +const char *sip_comedia2str(struct ast_flags *flags); + +int sip_str2transport(const char *transport); +const char *sip_transports2str(unsigned int transports); + +const char *sip_srv_protocol(enum ast_transport transport); +const char *sip_srv_service(enum ast_transport transport); + +int sip_parse_ok_contact(struct sip_dialog *dialog, struct sip_request *request); +int sip_parse_authorization(char *authorization, struct sip_authorization_data *authorization_data); + +unsigned int sip_parse_options(struct sip_request *request, const char *supported, struct ast_str **unsupported, size_t max_len); +int sip_supported_option(unsigned int *options, int option); +void sip_set_option_supported(unsigned int *options, int option); +char *sip_options2str(unsigned int options); + +int sip_parse_via(struct sip_request *request); +int sip_parse_reason(struct sip_dialog *dialog, struct sip_request *request); +int sip_parse_path(struct sip_dialog *dialog, struct sip_peer *peer, struct sip_request *request, const char *path); +int sip_parse_remote_party_id(struct sip_dialog *dialog, struct sip_request *request); +int sip_parse_refer_to(struct sip_dialog *dialog, struct sip_request *request); +int sip_parse_diversion(struct sip_dialog *dialog, struct sip_request *request, int set_call_forward); +int sip_parse_oli(struct sip_dialog *dialog, struct sip_request *request); +int sip_parse_min_se(struct sip_request *request, int *interval); +int sip_parse_session_expires(struct sip_request *request, int *interval, int *refresher); +void sip_parse_rtp_stats(struct sip_dialog *dialog, struct sip_request *request); + +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 -durN asterisk-22.3.0.orig/channels/sip/manager.c asterisk-22.3.0/channels/sip/manager.c --- asterisk-22.3.0.orig/channels/sip/manager.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/manager.c 2025-04-17 11:11:22.814703196 +1200 @@ -0,0 +1,781 @@ +/* + * 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 "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/peers.h" +#include "include/registry.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/manager.h" + +/*** DOCUMENTATION + + + 1.6.1.0 + + + Qualify SIP peers. + + + + + The peer name you want to qualify. + + + + Qualify a SIP peer. + + + SIPQualifyPeerDone + + + + + 1.6.0 + + + Show SIP registrations (text format). + + + + + + Lists all registration requests and status. Registrations will follow as separate + events followed by a final event called RegistrationsComplete. + + + + + 1.6.1.0 + + + Send a SIP notify. + + + + + Peer to receive the notify. + + + At least one variable pair must be specified. + name=value + + + When specified, SIP notity will be sent as a part of an existing dialog. + + + + Sends a SIP Notify event. + All parameters for this event must be specified in the body of this request + via multiple Variable: name=value sequences. + + + + + 1.2.0 + + + List SIP peers (text format). + + + + + + Lists SIP peers in text format with details on current status. + Peerlist will follow as separate events, followed by a final event called + PeerlistComplete. + + + + + 1.2.0 + + + show SIP peer (text format). + + + + + The peer name you want to check. + + + + Show one SIP peer with details on current status. + + + + + 11.0.0 + + + Show the status of one or all of the 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. + + + + + Raised when SIPQualifyPeer has finished qualifying the specified peer. + + + The name of the peer. + + + This is only included if an ActionID Header was sent with the action request, + in which case it will be that ActionID. + + + + SIPqualifypeer + + + + + + Raised when a SIP session times out. + + + + The source of the session timeout. + + + + + + + + + ***/ + +/* 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(action_id, peer_name); + + ao2_t_cleanup(peer, "drop peer"); + } else { + astman_send_error(session, message, "Peer not found"); + } + + return 0; +} + +/* Show SIP registrations in the manager API */ +int sip_manager_show_registry(struct mansession *session, const struct message *message) +{ + char action_id[256]; + struct ao2_iterator iter; + struct sip_registry *registry; + int count; + + snprintf(action_id, sizeof(action_id), "ActionID: %s\r\n", astman_get_header(message, "ActionID")); + + if (strlen(action_id) == 13) { + action_id[0] = '\0'; + } + + astman_send_listack(session, message, "Registrations list will follow", "start"); + iter = ao2_iterator_init(sip_registry, 0); + + for (count = 0; (registry = ao2_t_iterator_next(&iter, "bump registry")); count++) { + ao2_lock(registry); + + astman_append(session, + "Event: RegistryEntry\r\n" + "%s" + "Host: %s\r\n" + "Port: %d\r\n" + "Username: %s\r\n" + "Domain: %s\r\n" + "DomainPort: %d\r\n" + "Refresh: %d\r\n" + "State: %s\r\n" + "RegistrationTime: %ld\r\n" + "\r\n", + action_id, + registry->hostname, + registry->port ? registry->port : SIP_STANDARD_PORT, + registry->username, + S_OR(registry->domain, registry->hostname), + registry->domain_port ? registry->domain_port : SIP_STANDARD_PORT, + registry->outgoing_expiry, + sip_registry_state2str(registry->state), + (long) registry->time.tv_sec); + + ao2_unlock(registry); + ao2_t_ref(registry, -1, "drop registry"); + } + + ao2_iterator_destroy(&iter); + + astman_send_list_complete_start(session, message, "RegistrationsComplete", 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, "Channel"); + + if (ast_strlen_zero(peer_name)) { + astman_send_error(session, message, "Peer name missing"); + return 0; + } + + if (!strncasecmp(peer_name, "SIP/", 4)) { + peer_name += 4; + } + + call_id = astman_get_header(message, "Call-ID"); + + /* check if Call-ID header is set */ + if (!ast_strlen_zero(call_id)) { + if (!(dialog = ao2_t_find(sip_dialogs, call_id, OBJ_SEARCH_KEY, "bump dialog"))) { + 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, ""); + } + } else { + if (!(dialog = sip_dialog_alloc(NULL, NULL, FALSE, SIP_METHOD_NOTIFY, NULL, 0))) { + astman_send_error(session, message, "Memory or socket error"); + return 0; + } + + if (sip_dialog_build(dialog, peer_name, NULL, TRUE)) { + /* Maybe they're not registered, etc. */ + sip_dialog_unlink(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + astman_send_error(session, message, "Could not create address"); + return 0; + } + + /* Notify is outgoing call */ + ast_set_flag(&dialog->flags[0], SIP_OUTGOING); + } + + 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")) { + ast_log(LOG_WARNING, "It is not necessary to specify Content-Length, ignoring\n"); + } 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)); + + if (ast_strlen_zero(call_id)) { + /* Now that we have the peer's address, set our ip and change call-id */ + sip_get_our_address(dialog->socket.transport, &dialog->address, &dialog->our_address); + + sip_dialog_sched_destroy(dialog, SIP_TIMEOUT); + sip_send_notify(dialog, SIP_INIT_REQUEST); + } else { + sip_dialog_sched_destroy(dialog, SIP_TIMEOUT); + sip_send_notify(dialog, SIP_INIT_BRANCH); + } + + ao2_t_cleanup(dialog, "drop dialog"); + + astman_send_ack(session, message, "Notify sent"); + ast_variables_destroy(variables); + + return 0; +} + +/* Show SIP peers in the manager API */ +int sip_manager_peers(struct mansession *session, const struct message *message) +{ + char action_id[256]; + struct sip_peer *peer; + struct ao2_iterator iter; + struct ast_str *status; + int count, realtime_peers; + + snprintf(action_id, sizeof(action_id), "ActionID: %s\r\n", astman_get_header(message, "ActionID")); + + if (strlen(action_id) == 13) { + action_id[0] = '\0'; + } + + realtime_peers = ast_check_realtime("sippeers"); + + astman_send_listack(session, message, "Peers list will follow", "start"); + + status = ast_str_alloca(16); + iter = ao2_iterator_init(sip_peers, 0); + + for (count = 0; (peer = ao2_t_iterator_next(&iter, "bump peer")); count++) { + sip_peer_get_status(peer, &status); + + astman_append(session, + "Event: PeerEntry\r\n" + "%s" + "ChannelType: SIP\r\n" + "ObjectName: %s\r\n" + "ChanObjectType: peer\r\n" + "IPaddress: %s\r\n" + "IPport: %s\r\n" + "Dynamic: %s\r\n" + "AutoForceRport: %s\r\n" + "Forcerport: %s\r\n" + "AutoComedia: %s\r\n" + "Comedia: %s\r\n" + "VideoSupport: %s\r\n" + "TextSupport: %s\r\n" + "ACL: %s\r\n" + "Status: %s\r\n" + "RealtimeDevice: %s\r\n" + "Description: %s\r\n" + "Accountcode: %s\r\n" + "\r\n", + action_id, + peer->name, + !ast_sockaddr_isnull(&peer->address) ? ast_sockaddr_stringify_addr(&peer->address) : "", + !ast_sockaddr_isnull(&peer->address) ? ast_sockaddr_stringify_port(&peer->address) : "0", + peer->host_dynamic ? "yes" : "no", /* Dynamic or not? */ + ast_test_flag(&peer->flags[2], SIP_NAT_AUTO_RPORT) ? "yes" : "no", + ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT) ? "yes" : "no", + ast_test_flag(&peer->flags[2], SIP_NAT_AUTO_COMEDIA) ? "yes" : "no", + ast_test_flag(&peer->flags[1], SIP_SYMMETRIC_RTP) ? "yes" : "no", + ast_test_flag(&peer->flags[1], SIP_VIDEO_SUPPORT) ? "yes" : "no", /* VIDEOSUPPORT=yes? */ + ast_test_flag(&peer->flags[1], SIP_TEXT_SUPPORT) ? "yes" : "no", /* TEXTSUPPORT=yes? */ + ast_acl_list_is_empty(peer->acl) ? "no" : "yes", /* permit/deny/acl */ + ast_str_buffer(status), + realtime_peers ? (peer->realtime ? "yes" : "no") : "no", + peer->description, + peer->accountcode); + + ao2_unlock(peer); + ao2_t_cleanup(peer, "drop peer"); + } + + ao2_iterator_destroy(&iter); + + /* Send final confirmation */ + astman_send_list_complete_start(session, message, "PeerListComplete", 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[80], groups[256]; + struct sip_peer *peer; + struct ast_str *status, *format_names, *named_groups, *mailboxes; + struct sip_alias *alias; + struct sip_subscription *subscription; + + 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_get_header(message, "ActionID"))) { + astman_append(session, "ActionID: %s\r\n", action_id); + } + + astman_append(session, "ChannelType: SIP\r\n"); + astman_append(session, "ObjectName: %s\r\n", peer->name); + astman_append(session, "ChanObjectType: peer\r\n"); + astman_append(session, "SecretExist: %s\r\n", !ast_strlen_zero(peer->secret) ? "yes" : "no"); + astman_append(session, "RemoteSecretExist: %s\r\n", !ast_strlen_zero(peer->remote_secret) ? "yes" : "no"); + astman_append(session, "MD5SecretExist: %s\r\n", !ast_strlen_zero(peer->md5secret) ? "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); + } + + astman_append(session, "Language: %s\r\n", peer->language); + astman_append(session, "ToneZone: %s\r\n", peer->zone); + + 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)); + astman_append(session, "CIDCallingPres: %s\r\n", ast_describe_caller_presentation(peer->caller_presentation)); + + 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: "); + astman_append(session, "%s\r\n", ast_print_group(groups, sizeof(groups), peer->callgroup)); + + astman_append(session, "Pickupgroup: "); + astman_append(session, "%s\r\n", ast_print_group(groups, sizeof(groups), peer->pickupgroup)); + + named_groups = ast_str_alloca(512); + + astman_append(session, "NamedCallgroup: "); + astman_append(session, "%s\r\n", ast_print_namedgroups(&named_groups, peer->named_callgroups)); + + ast_str_reset(named_groups); + + astman_append(session, "NamedPickupgroup: "); + astman_append(session, "%s\r\n", ast_print_namedgroups(&named_groups, peer->named_pickupgroups)); + + astman_append(session, "MOHSuggest: %s\r\n", peer->moh_suggest); + astman_append(session, "MOHInterpret: %s\r\n", peer->moh_interpret); + + mailboxes = ast_str_alloca(512); + + sip_peer_get_mailboxes(peer, &mailboxes); + astman_append(session, "VoiceMailbox: %s\r\n", ast_str_buffer(mailboxes)); + + astman_append(session, "TransferMode: %s\r\n", ast_test_flag(&peer->flags[1], SIP_ALLOW_TRANSFER) ? "yes" : "no"); + astman_append(session, "VoiceMailMessages: %d/%d\r\n", peer->new_messages, peer->old_messages); + astman_append(session, "MaxForwards: %d\r\n", peer->max_forwards); + astman_append(session, "CallLimit: %d\r\n", peer->call_limit); + astman_append(session, "BusyLevel: %d\r\n", peer->busy_level); + astman_append(session, "MaxCallBitrate: %d\r\n", peer->max_call_bitrate); + astman_append(session, "Dynamic: %s\r\n", peer->host_dynamic ? "yes" : "no"); + astman_append(session, "CallerID: %s\r\n", ast_callerid_merge(callerid, sizeof(callerid), peer->caller_name, peer->caller_number, "")); + astman_append(session, "RegExpires: %ld\r\n", ast_sched_when(sip_sched_context, peer->register_expire_sched_id)); + + astman_append(session, "ForceRport: %s\r\n", + ast_test_flag(&peer->flags[2], SIP_NAT_AUTO_RPORT) ? + (ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT) ? "yes,auto" : "no,auto") : + (ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT) ? "yes" : "no")); + + astman_append(session, "Comedia: %s\r\n", + ast_test_flag(&peer->flags[2], SIP_NAT_AUTO_COMEDIA) ? + (ast_test_flag(&peer->flags[1], SIP_SYMMETRIC_RTP) ? "yes,auto" : "no,auto") : + (ast_test_flag(&peer->flags[1], SIP_SYMMETRIC_RTP) ? "yes" : "no")); + + astman_append(session, "ACL: %s\r\n", !ast_acl_list_is_empty(peer->acl) ? "yes" : "no"); + astman_append(session, "CanReinvite: %s\r\n", ast_test_flag(&peer->flags[0], SIP_DIRECT_MEDIA) ? "yes" : "no"); + astman_append(session, "DirectMedia: %s\r\n", ast_test_flag(&peer->flags[0], SIP_DIRECT_MEDIA) ? "yes" : "no"); + astman_append(session, "PromiscRedir: %s\r\n", ast_test_flag(&peer->flags[0], SIP_PROMISCUOUS_REDIRECT) ? "yes" : "no"); + astman_append(session, "UserEqPhone: %s\r\n", ast_test_flag(&peer->flags[0], SIP_USER_EQ_PHONE) ? "yes" : "no"); + astman_append(session, "VideoSupport: %s\r\n", ast_test_flag(&peer->flags[1], SIP_VIDEO_SUPPORT) ? "yes" : "no"); + astman_append(session, "TextSupport: %s\r\n", ast_test_flag(&peer->flags[1], SIP_TEXT_SUPPORT) ? "yes" : "no"); + astman_append(session, "T38Support: %s\r\n", ast_test_flag(&peer->flags[1], SIP_T38_SUPPORT) ? "yes" : "no"); + astman_append(session, "T38EC: %s\r\n", sip_t38_ecmode2str(ast_test_flag(&peer->flags[1], SIP_T38_SUPPORT))); + astman_append(session, "T38MaxDtgrm: %u\r\n", peer->t38_max_datagram); + astman_append(session, "SessionTimers: %s\r\n", sip_stimer_mode2str(peer->stimer_mode)); + astman_append(session, "SessionRefresher: %s\r\n", sip_stimer_refresher2str(peer->stimer_refresher)); + astman_append(session, "SessionExpires: %d\r\n", peer->stimer_max_se); + astman_append(session, "SessionMinExpires: %d\r\n", peer->stimer_min_se); + astman_append(session, "RTPEngine: %s\r\n", peer->rtp_engine); + astman_append(session, "Encryption: %s\r\n", ast_test_flag(&peer->flags[0], SIP_USE_SRTP) ? "yes" : "no"); + astman_append(session, "RTCPMux: %s\r\n", ast_test_flag(&peer->flags[2], SIP_RTCP_MUX) ? "yes" : "no"); + astman_append(session, "DTMFmode: %s\r\n", sip_dtmf_mode2str(ast_test_flag(&peer->flags[0], SIP_DTMF))); + astman_append(session, "ToHost: %s\r\n", peer->host); + astman_append(session, "AddressIP: %s\r\n", ast_sockaddr_stringify_addr(&peer->address)); + astman_append(session, "AddressPort: %d\r\n", ast_sockaddr_port(&peer->address)); + astman_append(session, "DefaultAddressIP: %s\r\n", ast_sockaddr_stringify_addr(&peer->default_address)); + astman_append(session, "DefaultAddressPort: %d\r\n", ast_sockaddr_port(&peer->default_address)); + astman_append(session, "DefaultUsername: %s\r\n", peer->username); + + 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)); + + status = ast_str_alloca(16); + + sip_peer_get_status(peer, &status); + + astman_append(session, "Status: %s\r\n", ast_str_buffer(status)); + astman_append(session, "Useragent: %s\r\n", peer->useragent); + astman_append(session, "RegContact: %s\r\n", peer->full_contact); + astman_append(session, "QualifyFreq: %d\r\n", peer->qualify_freq); + 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, "ReasonHeader: %s\r\n", (ast_test_flag(&peer->flags[1], SIP_Q850_REASON)) ? "yes" : "no"); + astman_append(session, "Description: %s\r\n", peer->description); + 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, "OffHook: %s\r\n", peer->offhook ? "yes" : "no"); + astman_append(session, "CiscoDeviceName: %s\r\n", peer->cisco_device_name); + astman_append(session, "CiscoActiveLoad: %s\r\n", peer->cisco_active_load); + astman_append(session, "CiscoInactiveLoad: %s\r\n", peer->cisco_inactive_load); + astman_append(session, "CiscoLineIndex: %d\r\n", peer->cisco_line_index); + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + astman_append(session, "Register: %s\r\n", alias->name); + } + + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, next) { + astman_append(session, "Subscribe: %s@%s\r\n", subscription->exten, subscription->context); + } + + ao2_t_cleanup(peer, "drop peer"); + + 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; + char action_id[256]; + struct sip_peer *peer; + int count; + + peer = NULL; + + snprintf(action_id, sizeof(action_id), "ActionID: %s\r\n", astman_get_header(message, "ActionID")); + + if (strlen(action_id) == 13) { + action_id[0] = '\0'; + } + + peer_name = astman_get_header(message, "Peer"); + + if (!ast_strlen_zero(peer_name)) { + /* strip SIP/ from the begining of the peer name */ + if (!strncasecmp("SIP/", peer_name, 4)) { + peer_name += 4; + } + + if (!(peer = sip_peer_find(peer_name, TRUE, FALSE))) { + astman_send_error(session, message, "No such peer"); + return 0; + } + } + + 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_t_iterator_next(&iter, "bump peer")); count++) { + ao2_lock(peer); + + if (peer->qualify_max) { + if (peer->last_qualify < 0) { + status = "Unreachable"; + } else if (peer->last_qualify > peer->qualify_max) { + status = "Lagged"; + } else if (peer->last_qualify) { + status = "Reachable"; + } else { + status = "Unknown"; + } + } else { + status = "Unmonitored"; + } + + astman_append(session, + "Event: PeerStatus\r\n" + "Privilege: System\r\n" + "ChannelType: SIP\r\n" + "Peer: SIP/%s\r\n" + "PeerStatus: %s\r\n" + "Time: %d\r\n" + "%s" + "\r\n", + peer->name, status, peer->last_qualify, action_id); + + ao2_unlock(peer); + ao2_t_cleanup(peer, "drop peer"); + } + + ao2_iterator_destroy(&iter); + } else { + ao2_lock(peer); + + if (peer->qualify_max) { + if (peer->last_qualify < 0) { + status = "Unreachable"; + } else if (peer->last_qualify > peer->qualify_max) { + status = "Lagged"; + } else if (peer->last_qualify) { + status = "Reachable"; + } else { + status = "Unknown"; + } + } else { + status = "Unmonitored"; + } + + astman_append(session, + "Event: PeerStatus\r\n" + "Privilege: System\r\n" + "ChannelType: SIP\r\n" + "Peer: SIP/%s\r\n" + "PeerStatus: %s\r\n" + "Time: %d\r\n" + "%s" + "\r\n", + peer->name, status, peer->last_qualify, action_id); + + ao2_unlock(peer); + ao2_t_cleanup(peer, "drop peer"); + + count = 1; + } + + astman_send_list_complete_start(session, message, "SIPpeerStatusComplete", count); + astman_send_list_complete_end(session); + + return 0; +} + +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); + + ast_assert(channel != NULL); + ast_assert(source != NULL); + + if (!(blob = ast_json_pack("{s: s}", "source", source))) { + return; + } + + ast_channel_publish_blob(channel, sip_session_timeout_type(), blob); +} + +void sip_publish_qualify_peer(const char *id, const char *peer) +{ + RAII_VAR(struct ast_json *, body, NULL, ast_json_unref); + + if (ast_strlen_zero(id)) { + body = ast_json_pack("{s: s}", "Peer", peer); + } else { + body = ast_json_pack("{s: s, s: s}", "Peer", peer, "ActionID", id); + } + + if (!body) { + return; + } + + ast_manager_publish_event("SIPQualifyPeerDone", EVENT_FLAG_CALL, body); +} diff -durN asterisk-22.3.0.orig/channels/sip/mwi_subscriptions.c asterisk-22.3.0/channels/sip/mwi_subscriptions.c --- asterisk-22.3.0.orig/channels/sip/mwi_subscriptions.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/mwi_subscriptions.c 2025-04-17 11:11:22.815703169 +1200 @@ -0,0 +1,406 @@ +/* + * 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/netsock.h" +#include "include/route.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/dialog.h" +#include "include/mwi_subscriptions.h" + +static int sip_mwi_subscription_unlink(void *data, void *arg, int flags); +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_send(const void *data); +static int __sip_mwi_subscription_send(struct sip_mwi_subscription *mwi); +static int __sip_mwi_subscription_start(const void *data); +static void sip_mwi_subscription_stop(struct sip_mwi_subscription *mwi); +static int __sip_mwi_subscription_stop(const void *data); + +/* The MWI subscription list */ +struct ao2_container *sip_mwi_subscriptions; + +/* Parse mwi=> line */ +int sip_mwi_subscription_build(const char *config, int lineno) +{ + struct sip_mwi_subscription *mwi; + enum ast_transport transport; + char *username, *hostname, *secret, *auth_user, *port, *mailbox, *context; + + if (ast_strlen_zero(config)) { + return -1; + } + + username = ast_strdupa(config); + + if (!strncmp(username, "udp://", 6)) { + username += 6; + transport = AST_TRANSPORT_UDP; + } else if (!strncmp(username, "tcp://", 6)) { + username += 6; + transport = AST_TRANSPORT_TCP; + } else if (!strncmp(username, "tls://", 6)) { + username += 6; + transport = AST_TRANSPORT_TLS; + } else if (strstr(username, "://")) { + ast_log(LOG_WARNING, "Invalid transport for '%s' at line %d\n", config, lineno); + } else { + transport = AST_TRANSPORT_UDP; + } + + /* Dont fail here as the error message is logged below */ + if ((hostname = strchr(username, '@'))) { + *hostname++ = '\0'; + + if ((port = strchr(hostname, ':'))) { + *port++ = '\0'; + + if (!atoi(port)) { + ast_log(LOG_WARNING, "Invalid port for '%s' at line %d\n", config, lineno); + return -1; + } + } + } else { + port = NULL; + } + + if ((secret = strchr(username, ':'))) { + *secret++ = '\0'; + + if ((auth_user = strchr(secret, ':'))) { + *auth_user++ = '\0'; + } + } else { + auth_user = NULL; + } + + if ((mailbox = strchr(hostname, '/'))) { + *mailbox++ = '\0'; + + if ((context = strchr(mailbox, '@'))) { + *context++ = '\0'; + } else { + context = "SIP_Remote"; + } + } + + if (ast_strlen_zero(username) || ast_strlen_zero(hostname) || ast_strlen_zero(mailbox)) { + ast_log(LOG_WARNING, + "Format for MWI subscription is [transport://]user[:secret[:authuser]]@host[:port]/mailbox[@context] at line %d\n", + lineno); + return -1; + } + + if (!(mwi = ao2_t_alloc(sizeof(*mwi), sip_mwi_subscription_destroy, "alloc mwi"))) { + return -1; + } + + mwi->resubscribe_sched_id = -1; + + if (ast_string_field_init(mwi, 256)) { + ao2_t_ref(mwi, -1, "drop mwi"); + return -1; + } + + ast_string_field_set(mwi, username, username); + + if (secret) { + ast_string_field_set(mwi, secret, secret); + } + + if (auth_user) { + ast_string_field_set(mwi, auth_user, auth_user); + } + + ast_string_field_set(mwi, hostname, hostname); + ast_string_field_set(mwi, mailbox, mailbox); + ast_string_field_set(mwi, context, context); + + mwi->port = sip_str2port(port, transport = AST_TRANSPORT_TLS ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + mwi->transport = transport; + + ao2_t_link(sip_mwi_subscriptions, mwi, "link mwi"); + ao2_t_ref(mwi, -1, "drop mwi"); + + return 0; +} + +/* Destroy MWI subscription object */ +static void sip_mwi_subscription_destroy(void *data) +{ + struct sip_mwi_subscription *mwi = (struct sip_mwi_subscription *) data; + + if (mwi->dialog) { + mwi->dialog->mwi = NULL; + ao2_t_cleanup(mwi->dialog, "drop dialog"); + } + + ast_string_field_free_memory(mwi); +} + +static int sip_mwi_subscription_unlink(void *data, void *arg, int flags) +{ + struct sip_mwi_subscription *mwi = (struct sip_mwi_subscription *) data; + + sip_mwi_subscription_stop(mwi); + + return CMP_MATCH; +} + +void sip_mwi_subscription_destroy_all(void) +{ + ao2_t_callback(sip_mwi_subscriptions, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, sip_mwi_subscription_unlink, NULL, "unlink mwi"); +} + +static void sip_mwi_subscription_dnsmgr_lookup(struct ast_sockaddr *old_address, struct ast_sockaddr *new_address, void *data) +{ + struct sip_mwi_subscription *mwi; + const char *old_host, *new_host; + + mwi = (struct sip_mwi_subscription *) data; + + /* This shouldn't happen, but just in case */ + if (ast_sockaddr_isnull(new_address)) { + ast_debug(1, "Empty sockaddr change, ignoring!\n"); + 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->hostname, old_host, new_host); + ast_sockaddr_copy(&mwi->address, new_address); +} + +/* Actually setup an MWI subscription or resubscribe */ +static int __sip_mwi_subscription_send(struct sip_mwi_subscription *mwi) +{ + /* If we have no DNS manager let's do a lookup */ + if (!mwi->dnsmgr) { + char transport[MAXHOSTNAMELEN]; + + ao2_t_ref(mwi, +1, "bump mwi"); + + mwi->address.ss.ss_family = AF_INET; + + snprintf(transport, sizeof(transport), "_%s._%s", sip_srv_service(mwi->transport), sip_srv_protocol(mwi->transport)); + + ast_dnsmgr_lookup_cb(mwi->hostname, &mwi->address, &mwi->dnsmgr, + sip_config.srv_lookup ? transport : NULL, sip_mwi_subscription_dnsmgr_lookup, mwi); + + if (!mwi->dnsmgr) { + ao2_t_ref(mwi, -1, "drop mwi"); + } + } + + /* If we already have a subscription up simply send a resubscription */ + if (mwi->dialog) { + sip_send_subscribe(mwi->dialog, SIP_INIT_NONE); + return 0; + } + + /* Create a dialog that we will use for the subscription */ + if (!(mwi->dialog = sip_dialog_alloc(NULL, NULL, FALSE, SIP_METHOD_SUBSCRIBE, NULL, 0))) { + return -1; + } + + sip_proxy_set(mwi->dialog, sip_proxy_get(mwi->dialog, NULL)); + + if (!ast_sockaddr_port(&mwi->address) && mwi->port) { + ast_sockaddr_set_port(&mwi->address, mwi->port); + } + + /* Setup the destination of our subscription */ + if (sip_dialog_build(mwi->dialog, mwi->hostname, &mwi->address, FALSE)) { + sip_dialog_unlink(mwi->dialog); + ao2_t_cleanup(mwi->dialog, "drop dialog"); + + mwi->dialog = NULL; + return 0; + } + + mwi->dialog->expiry = sip_config.mwi_expiry; + + if (!mwi->dnsmgr && mwi->port) { + ast_sockaddr_set_port(&mwi->dialog->address, mwi->port); + ast_sockaddr_set_port(&mwi->dialog->received_address, mwi->port); + } else { + mwi->port = ast_sockaddr_port(&mwi->dialog->address); + } + + /* Set various other information */ + if (!ast_strlen_zero(mwi->auth_user)) { + ast_string_field_set(mwi->dialog, peer_name, mwi->auth_user); + ast_string_field_set(mwi->dialog, auth_name, mwi->auth_user); + ast_string_field_set(mwi->dialog, from_user, mwi->auth_user); + } else { + ast_string_field_set(mwi->dialog, peer_name, mwi->username); + ast_string_field_set(mwi->dialog, auth_name, mwi->username); + ast_string_field_set(mwi->dialog, from_user, mwi->username); + } + + ast_string_field_set(mwi->dialog, username, mwi->username); + + if (!ast_strlen_zero(mwi->secret)) { + ast_string_field_set(mwi->dialog, peer_secret, mwi->secret); + } + + sip_socket_set_transport(&mwi->dialog->socket, mwi->transport); + sip_get_our_address(mwi->dialog->socket.transport, &mwi->dialog->address, &mwi->dialog->our_address); + + ast_set_flag(&mwi->dialog->flags[0], SIP_OUTGOING); + + /* Associate the call with us */ + mwi->dialog->mwi = ao2_t_bump(mwi, "bump mwi"); + mwi->dialog->subscribe_events = SIP_SUBSCRIBE_MESSAGE_SUMMARY; + + /* Actually send the packet */ + sip_send_subscribe(mwi->dialog, SIP_INIT_REQUEST); + + return 0; +} + +/* Send a subscription or resubscription for MWI */ +static int sip_mwi_subscription_send(const void *data) +{ + struct sip_mwi_subscription *mwi = (struct sip_mwi_subscription *) data; + + mwi->resubscribe_sched_id = -1; + + __sip_mwi_subscription_send(mwi); + ao2_t_ref(mwi, -1, "drop mwi"); + + return 0; +} + +/* Send all MWI subscriptions */ +void sip_mwi_subscription_send_all(void) +{ + struct ao2_iterator iter; + struct sip_mwi_subscription *mwi; + + iter = ao2_iterator_init(sip_mwi_subscriptions, 0); + + while ((mwi = ao2_t_iterator_next(&iter, "bump mwi"))) { + sip_mwi_subscription_start(mwi, 1); + ao2_t_ref(mwi, -1, "drop mwi"); + } + + 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; + int when; + + sched_data = (struct sip_sched_data *) data; + + mwi = sched_data->mwi; + when = sched_data->when; + + ast_free(sched_data); + + AST_SCHED_DEL_UNREF(sip_sched_context, mwi->resubscribe_sched_id, ao2_t_ref(mwi, -1, "drop mwi")); + + ao2_t_ref(mwi, +1, "bump mwi"); + + if ((mwi->resubscribe_sched_id = ast_sched_add(sip_sched_context, when, sip_mwi_subscription_send, mwi)) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(mwi, -1, "drop mwi"); + } + + ao2_t_ref(mwi, -1, "drop mwi"); + + return 0; +} + +void sip_mwi_subscription_start(struct sip_mwi_subscription *mwi, int when) +{ + struct sip_sched_data *sched_data; + + if (!(sched_data = ast_malloc(sizeof(*sched_data)))) { + /* Uh Oh. Expect bad behavior. */ + return; + } + + sched_data->mwi = mwi; + sched_data->when = when; + + ao2_t_ref(mwi, +1, "bump mwi"); + + if (ast_sched_add(sip_sched_context, 0, __sip_mwi_subscription_start, sched_data) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(mwi, -1, "drop mwi"); + ast_free(sched_data); + } +} + +/* Run by the sched thread. */ +static int __sip_mwi_subscription_stop(const void *data) +{ + struct sip_mwi_subscription *mwi = (struct sip_mwi_subscription *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, mwi->resubscribe_sched_id, ao2_t_ref(mwi, -1, "bump mwi")); + + if (mwi->dnsmgr) { + ast_dnsmgr_release(mwi->dnsmgr); + mwi->dnsmgr = NULL; + ao2_t_ref(mwi, -1, "drop mwi"); + } + + ao2_t_ref(mwi, -1, "drop mwi"); + + return 0; +} + +static void sip_mwi_subscription_stop(struct sip_mwi_subscription *mwi) +{ + ao2_t_ref(mwi, +1, "bump mwi"); + + if (ast_sched_add(sip_sched_context, 0, __sip_mwi_subscription_stop, mwi) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(mwi, -1, "drop mwi"); + } +} diff -durN asterisk-22.3.0.orig/channels/sip/netsock.c asterisk-22.3.0/channels/sip/netsock.c --- asterisk-22.3.0.orig/channels/sip/netsock.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/netsock.c 2025-04-17 11:11:22.815703169 +1200 @@ -0,0 +1,1327 @@ +/* + * 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/netsock.h" +#include "include/route.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/peers.h" +#include "include/config.h" +#include "include/utils.h" +#include "include/dialog.h" +#include "include/handlers.h" + +#define SIP_MAX_PACKET_SIZE 20480 /* Max SIP packet size */ +#define SIP_DEFAULT_TIMER_A 1000 /* How frequently to retransmit Default: 2 * 500 ms in RFC 3261 */ + +static int sip_socket_build(struct sip_dialog *dialog); + +static int sip_tcptls_thread_cmp_address(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); + +/* 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_read() 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_read_id; /* ID of IO entry for sip_socket_fd */ + +struct io_context *sip_io_context; /* The IO context */ +ast_mutex_t sip_netsock_lock; + +/* 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, +}; + +struct ast_tls_config sip_tls_session_config; /* Working TLS connection configuration */ + +/* 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; /* The table of TCP threads */ + +/* Read data from SIP UDP socket sip_socket_read locks the owner channel while we are processing the SIP message */ +int sip_socket_read(int *id, int fd, short events, void *ignore) +{ + struct sip_socket socket; + struct ast_sockaddr address; + static char data[65535]; + int res; + + res = ast_recvfrom(fd, data, sizeof(data) - 1, 0, &address); + + if (res < 0) { +#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'; + + socket.fd = fd; + socket.transport = AST_TRANSPORT_UDP; + socket.tcptls_session = NULL; + + ast_sockaddr_copy(&socket.address, &address); + + 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) { + socket->fd = -1; + socket->transport = transport; + + if (socket->tcptls_session) { + ao2_ref(socket->tcptls_session, -1); + socket->tcptls_session = NULL; + } + + ast_sockaddr_setnull(&socket->address); + } +} + +void sip_socket_copy(struct sip_socket *to_socket, const struct sip_socket *from_socket) +{ + if (to_socket->tcptls_session) { + ao2_t_ref(to_socket->tcptls_session, -1, "drop tcptls_session"); + } + + if (from_socket->tcptls_session) { + ao2_t_ref(from_socket->tcptls_session, +1, "bump tcptls_session"); + } + + to_socket->fd = from_socket->fd; + to_socket->transport = from_socket->transport; + to_socket->tcptls_session = from_socket->tcptls_session; + + ast_sockaddr_copy(&to_socket->address, &from_socket->address); +} + +/* 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; + struct ast_sockaddr address; + pthread_t threadid; + + /* Check to see if a socket is already active */ + if (dialog->socket.transport == AST_TRANSPORT_UDP && dialog->socket.fd != -1) { + return dialog->socket.fd; + } else if ((dialog->socket.transport & (AST_TRANSPORT_TCP | AST_TRANSPORT_TLS)) && + dialog->socket.tcptls_session && dialog->socket.tcptls_session->stream) { + return ast_iostream_get_fd(dialog->socket.tcptls_session->stream); + } + + 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 dialog->socket.fd; + } else if (!(dialog->socket.transport & (AST_TRANSPORT_TCP | AST_TRANSPORT_TLS))) { + 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. */ + /* 1. Check for existing threads */ + ast_sockaddr_copy(&address, sip_dialog_get_address(dialog)); + + if ((tcptls_session = sip_tcptls_session_find(&address))) { + dialog->socket.fd = ast_iostream_get_fd(tcptls_session->stream); + + if (dialog->socket.tcptls_session) { + ao2_ref(dialog->socket.tcptls_session, -1); + dialog->socket.tcptls_session = NULL; + } + + dialog->socket.tcptls_session = tcptls_session; + + return dialog->socket.fd; + /* 2. Thread not found, if tcptls_session already exists, it once had a thread and is now terminated */ + } else if (dialog->socket.tcptls_session) { + return dialog->socket.fd; /* XXX whether reconnection is ever necessary here needs to be investigated further */ + } + + /* 3. Create a new TCP/TLS client connection */ + /* 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_copy(&session_args->remote_address, &address); + + /* if type is TLS, we need to create a tls cfg for this session arg */ + if (dialog->socket.transport == AST_TRANSPORT_TLS) { + 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)); + } + } + + /* If a bind address has been specified, use it */ + if (dialog->socket.transport == AST_TRANSPORT_TLS && !ast_sockaddr_isnull(&sip_tls_session.local_address)) { + ast_sockaddr_copy(&session_args->local_address, &sip_tls_session.local_address); + } else if (dialog->socket.transport == AST_TRANSPORT_TCP && !ast_sockaddr_isnull(&sip_tcp_session.local_address)) { + ast_sockaddr_copy(&session_args->local_address, &sip_tcp_session.local_address); + } + + /* 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); + } + + /* 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)) { + ast_debug(1, "Unable to launch thread '%s'", session_args->name); + ao2_ref(dialog->socket.tcptls_session, -1); /* take away the thread ref we just gave it */ + + goto cleanup; + } + + ast_set_qos(dialog->socket.fd, sip_config.tos_sip, sip_config.cos_sip, "SIP"); + + return dialog->socket.fd; + +cleanup: + if (session_args) { + ao2_t_ref(session_args, -1, "drop 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_t_unlink(sip_tcptls_threads, thread, "unlink 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_cmp_address(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_t_ref(thread, -1, "drop thread"); + ast_log(LOG_ERROR, "Could not create alert pipe: %s\n", strerror(errno)); + + return NULL; + } + + ao2_t_ref(tcptls_session, +1, "bump tcptls_session"); + + thread->tcptls_session = tcptls_session; + + ao2_t_link(sip_tcptls_threads, thread, "link thread"); + ao2_t_ref(thread, -1, "drop thread"); + + 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; + + 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_t_ref(packet, -1, "drop packet"); + } + + if (thread->tcptls_session) { + ao2_t_ref(thread->tcptls_session, -1, "drop 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_cmp_address, address))) { + return NULL; + } + + ao2_t_ref(thread->tcptls_session, +1, "bump tcptls_session"); + tcptls_session = thread->tcptls_session; + ao2_t_ref(thread, -1, "drop thread"); + + 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) +{ + int res; + struct sip_tcptls_thread *thread; + struct sip_tcptls_packet *packet; + int alert; + + if (!tcptls_session) { + return -1; + } + + ao2_lock(tcptls_session); + + packet = NULL; + + if (!tcptls_session->stream || + !(packet = ao2_alloc(sizeof(*packet), sip_tcptls_packet_destroy)) || !(packet->data = ast_str_create(strlen(data)))) { + goto error; + } + + if (!(thread = ao2_t_find(sip_tcptls_threads, tcptls_session, OBJ_SEARCH_KEY, "bump thread"))) { + ast_log(LOG_ERROR, "Unable to locate tcptls_session helper thread\n"); + goto error; + } + + /* goto error should _NOT_ be used beyond this point */ + ast_str_set(&packet->data, 0, "%s", data); + + /* alert 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_t_ref(packet, -1, "drop packet"); + packet = NULL; + + res = -1; + } else { + /* 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); + + res = ast_str_strlen(packet->data); + } + + ao2_unlock(thread); + ao2_unlock(tcptls_session); + + ao2_t_ref(thread, -1, "drop thread"); + + return res; + +error: + if (thread) { + ao2_t_ref(thread, -1, "drop thread"); + } + + if (packet) { + ao2_t_ref(packet, -1, "drop 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 body_len, content_len; + char *boundary, *header; + + /* Important pieces to search for in a SIP request are \r\n\r\n. This marks either The division between the headers + * and content or the end of the SIP request */ + 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. */ + header = strcasestr(ast_str_buffer(*read_buf), "\nContent-Length:"); + content_len = -1; + + if (header && header < boundary) { + header += 16; + + /* Check that this is a complete header */ + if (!strchr(header, '\n') || sscanf(header, "%30d", &content_len) != 1) { + content_len = -1; + } + } + + body_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 (body_len > 0) { + ast_str_append(&tcptls_session->overflow_buf, 0, "%s", boundary); + ast_str_truncate(*read_buf, ast_str_strlen(*read_buf) - body_len); + } + + return TRUE; + } + + /* Positive content length. Let's see what sort of message boundary we're dealing with. */ + if (body_len < content_len) { + /* We don't have the full message boundary yet */ + return FALSE; + } + + if (body_len > content_len) { + /* We have the full message plus a fragment of a further message */ + ast_str_append(&tcptls_session->overflow_buf, 0, "%s", boundary + content_len); + ast_str_truncate(*read_buf, ast_str_strlen(*read_buf) - (body_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 < 0) { + 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 < 0) { + 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_MAX_PACKET_SIZE) { + ast_log(LOG_WARNING, "Rejecting TCP/TLS packet from '%s' because way too large: %zu\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 authenticated, timeout_enabled, keepalive; + struct sip_tcptls_thread *thread; + struct pollfd fds[2]; + struct ast_tcptls_session_args *session_args; + struct sip_socket socket; + time_t start; + RAII_VAR(struct ast_str *, read_buf, NULL, ast_free_ptr); + + tcptls_session = (struct ast_tcptls_session_instance *) data; + + thread = NULL; + session_args = 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_auth_limit) { + ast_verb(3, "Too many unauthenticated TCP/TLS clients %d, limit is %d\n", + unauthenticated_count, sip_config.tcp_auth_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_t_ref(thread, +1, "bump thread"); + } else { + /* Client, outgoing connction */ + if (!(session_args = tcptls_session->parent) || + !(thread = ao2_t_find(sip_tcptls_threads, tcptls_session, OBJ_SEARCH_KEY, "bump thread"))) { + goto cleanup; + } + + thread->threadid = pthread_self(); + + if (!(tcptls_session = ast_tcptls_client_start(tcptls_session))) { + goto cleanup; + } + } + + 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; + + ast_sockaddr_copy(&socket.address, &tcptls_session->remote_address); + + 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_auth_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; + + time(&start); + + if (!(read_buf = ast_str_create(4096))) { + goto cleanup; + } + + 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_auth_timeout - (time(NULL) - 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, 2, timeout); /* polls for both socket and alert_pipe */ + + if (res < 0) { + 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. */ + if (fds[0].revents || ast_str_strlen(tcptls_session->overflow_buf) > 0) { /* there is data on the socket to be read */ + 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); + } + + if (fds[1].revents) { /* alert_pipe indicates there is data in the send queue to be sent */ + 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, "Failure to write to TCP/TLS socket\n"); + } + + ao2_t_ref(packet, -1, "drop packet"); + } + } + + 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_t_unlink(sip_tcptls_threads, thread, "unlink thread"); + ao2_t_ref(thread, -1, "drop thread"); + } + + /* if client, we own the parent session arguments and must decrement ref */ + if (session_args) { + ao2_t_ref(session_args, -1, "drop 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); + tcptls_session = NULL; + } + + return NULL; +} + +/* Transmit SIP message */ +int sip_packet_send(struct sip_dialog *dialog, struct ast_str *data) +{ + const struct ast_sockaddr *address; + int res; + + address = sip_dialog_get_address(dialog); + + ast_debug(2, "Trying to send '%.32s...' on to %s socket destined for %s\n", + ast_str_buffer(data), ast_transport2str(dialog->socket.transport), ast_sockaddr_stringify(address)); + + if (sip_socket_build(dialog) == -1) { + ast_debug(1, "Faild to create socket\n"); + return -1; + } + + if (dialog->socket.transport == AST_TRANSPORT_UDP) { + res = ast_sendto(dialog->socket.fd, ast_str_buffer(data), ast_str_strlen(data), 0, address); + } else if ((dialog->socket.transport & (AST_TRANSPORT_TCP | AST_TRANSPORT_TLS)) && dialog->socket.tcptls_session) { + res = sip_tcptls_session_write(dialog->socket.tcptls_session, ast_str_buffer(data)); + } else { + ast_debug(1, "Unable to send packet as it has no transport\n"); + return -1; + } + + if (res == -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 can't be reached */ + case ENETDOWN: /* Interface down */ + case ENETUNREACH: /* Network failure */ + case ECONNREFUSED: /* ICMP port unreachable */ + res = -1; /* Don't bother with trying to transmit again */ + break; + } + } + + if (res != ast_str_strlen(data)) { + ast_debug(2, "Sending of '%.32s' to %s returned %d (%s)\n", + ast_str_buffer(data), ast_sockaddr_stringify(address), res, strerror(errno)); + + return -1; + } + + return 0; +} + +/* 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; + } + + /* copy data, add a terminator and save length */ + if (!(packet->data = ast_str_create(ast_str_strlen(data)))) { + ao2_t_ref(packet, -1, "drop packet"); + return -1; + } + + ast_str_set(&packet->data, 0, "%s", ast_str_buffer(data)); + + /* copy other parameters from the caller */ + packet->method = method; + packet->cseq = cseq; + + if (code) { + packet->response = TRUE; + packet->code = code; + } else { + packet->response = FALSE; + } + + packet->critical = critical; + packet->dialog = ao2_t_bump(dialog, "bump dialog"); + + /* The retransmission list owns a packet ref */ + AST_LIST_INSERT_TAIL(&dialog->packet_queue, packet, next); + + packet->timer_t1 = dialog->timer_t1; /* Set SIP timer T1 */ + + if (packet->timer_t1) { + timer_a = packet->timer_t1; + } else { + timer_a = SIP_DEFAULT_TIMER_A; + } + + packet->send_start = ast_tvnow(); /* time packet was sent */ + /* time in ms after packet->send_start to stop retransmission */ + packet->send_end = (packet->timer_t1 ? packet->timer_t1 : sip_config.timer_t1) * 64; + + if (!(dialog->socket.transport & AST_TRANSPORT_UDP)) { + /* 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; + } + + /* Schedule retransmission */ + ao2_t_ref(packet, +1, "bump packet"); + + if ((packet->resend_sched_id = ast_sched_add_variable(sip_sched_context, timer_a, sip_packet_resend, packet, 1)) == -1) { + ao2_t_ref(packet, -1, "drop packet"); + } + + ast_debug(3, "Starting resend timer on %s '%s' %s\n", + sip_methods[packet->method].name, packet->dialog->call_id, packet->response ? "response" : "request"); + + if (sip_packet_send(packet->dialog, packet->data) == -1) { /* Send packet */ + /* Serious network trouble, no need to try again */ + sip_history_append(packet->dialog, "XmitErr", "%s", packet->critical ? "(Critical)" : "(Non-critical)"); + ast_log(LOG_ERROR, "Unable to send packet\n"); + + /* Unlink and destroy the packet object. */ + AST_LIST_REMOVE(&dialog->packet_queue, packet, next); + + sip_packet_cancel_resend(packet); + ao2_t_ref(packet, -1, "drop packet"); + + return -1; + } + + /* 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 */ + int res; + + 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); + res = 0; + + if (!packet->timeout) { + int timer_a = SIP_DEFAULT_TIMER_A; + + packet->attempts++; + + if (!packet->timer_t1) { /* Re-schedule using timer_a and timer_t1 */ + ast_debug(4, "Not rescheduling %s: no t1 timer\n", packet->dialog->call_id); + } else { + ast_debug(4, "Rescheduling send attempt %d on '%s'\n", packet->attempts, packet->dialog->call_id); + + 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; + } + + /* Reschedule re-transmit */ + ast_debug(4, "Rescheduling send attempt %d to %dms (t1 timer %dms)\n", + packet->attempts + 1, timer_a, packet->timer_t1); + } + + if (sip_dialog_debug(packet->dialog)) { + const struct ast_sockaddr *address = sip_dialog_get_address(packet->dialog); + + ast_verb(3, "<--- SIP write to (attempt %d, %s) %s://%s --->\n%s<------------->\n", + packet->attempts, sip_nat_mode2str(packet->dialog->flags), + ast_transport2str(packet->dialog->socket.transport), + ast_sockaddr_stringify(address), ast_str_buffer(packet->data)); + } + + sip_history_append(packet->dialog, "ReTx", "%d %s", timer_a, ast_str_buffer(packet->data)); + + /* 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 ((res = 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 && res == 0) { + 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)\nPacket timed out after %dms with no response\n", + sip_methods[packet->method].name, + packet->dialog->call_id, + packet->critical ? "critical" : "non-critical", + packet->response ? "response" : "request", + (int) ast_tvdiff_ms(ast_tvnow(), packet->send_start)); + } + } else if (packet->method == SIP_METHOD_OPTIONS && sip_debug) { + ast_log(LOG_WARNING, "Cancelling resend of OPTION request: %s\n", packet->dialog->call_id); + } + + if (res == -1) { + ast_log(LOG_WARNING, "Cancelling resend on %s due to error\n", packet->dialog->call_id); + sip_history_append(packet->dialog, "XmitErr", "%s", packet->critical ? "(Critical)" : "(Non-critical)"); + } else { + sip_history_append(packet->dialog, "MaxRetries", "%s", packet->critical ? "(Critical)" : "(Non-critical)"); + } + + 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_methods[packet->method].name, packet->dialog->call_id, packet->response ? "response" : "request"); + + if (packet->response && packet->code >= 200 && packet->code < 300 && + packet->dialog->pending_invite_cseq && ast_test_flag(&packet->dialog->flags[1], SIP_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 for sip_qualify_peer_noanswer */ + 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); + + sip_history_append(packet->dialog, "DialogKill", "Killing this failed dialog immediately"); + } + } + } else if (packet->dialog->pending_invite_cseq == packet->cseq) { + ast_log(LOG_WARNING, "Timeout on '%s' on non-critical %s\n", packet->dialog->call_id, sip_methods[packet->method].name); + + 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"); + + sip_history_append(packet->dialog, "ByeFailure", "Remote peer doesn't respond to bye. Destroying call anyway."); + } + + /* Unlink and destoroy the packet object if it is in the queue */ + if (packet == AST_LIST_REMOVE(&packet->dialog->packet_queue, packet, next)) { + ao2_t_ref(packet, -1, "drop packet"); + } + + /* If the object 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_t_ref(packet, -1, "drop packet"); + + return 0; +} + +/* Run by the sched thread. */ +static int __sip_packet_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_t_ref(packet, -1, "drop packet")); + ao2_t_ref(packet, -1, "drop packet"); + + return 0; +} + +void sip_packet_cancel_resend(struct sip_packet *packet) +{ + ao2_t_ref(packet, +1, "bump packet"); + + if (ast_sched_add(sip_sched_context, 0, __sip_packet_cancel_resend, packet) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(packet, -1, "drop packet"); + } +} + +static void sip_packet_destroy(void *data) +{ + struct sip_packet *packet = (struct sip_packet *) data; + + if (packet->dialog) { + ao2_t_cleanup(packet->dialog, "drop 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 obforcing 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 (reply received) on %s\n", packet->dialog->call_id); + } + + /* Unlink and destroy the packet object. */ + AST_LIST_REMOVE_CURRENT(next); + + sip_packet_cancel_resend(packet); + acked = TRUE; + + ao2_t_ref(packet, -1, "drop packet"); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + ast_debug(1, "Stopping resend of '%s' of %s '%d %s', match %s\n", + dialog->call_id, response ? "response" : "request", cseq, sip_methods[method].name, 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; + + packet = NULL; + + while (!AST_LIST_EMPTY(&dialog->packet_queue)) { + if (packet == AST_LIST_FIRST(&dialog->packet_queue)) { + ast_log(LOG_WARNING, "Have a packet %s that doesn't want to give up!\n", packet->dialog->call_id); + return; + } + + packet = AST_LIST_FIRST(&dialog->packet_queue); + sip_packet_ack(dialog, packet->method, packet->cseq, packet->response); + } +} + +/* 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(4, "Cancelling resend of '%d %s', got response\n", packet->cseq, sip_methods[method].name); + } + + sip_packet_cancel_resend(packet); + acked = TRUE; + + break; + } + } + + ast_debug(1, "Stopping provisional resend of %s but retaining packet of %s '%d %s', match %s\n", + dialog->call_id, response ? "response" : "request", cseq, sip_methods[method].name, acked ? "found" : "not found"); + + return acked; +} diff -durN asterisk-22.3.0.orig/channels/sip/parking.c asterisk-22.3.0/channels/sip/parking.c --- asterisk-22.3.0.orig/channels/sip/parking.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/parking.c 2025-04-17 11:11:22.816703143 +1200 @@ -0,0 +1,350 @@ +/* + * 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/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/handlers.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(context); + 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); + +/* Handle remotecc park requests */ +int sip_remotecc_park(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data) +{ + pthread_t threadid; + struct sip_park_data *park_data; + + if (!(park_data = ast_calloc_with_stringfields(1, struct sip_park_data, 512))) { + return -1; + } + + ao2_t_bump(dialog, "bump dialog"); + park_data->dialog = dialog; + + sip_request_copy(&dialog->initial_request, request); + park_data->monitor = !strcmp(remotecc_data->soft_key_event, "ParkMonitor"); + + ast_string_field_set(park_data, context, peer->context); + ast_string_field_set(park_data, call_id, remotecc_data->dialog.call_id); + ast_string_field_set(park_data, local_tag, remotecc_data->dialog.remote_tag); + ast_string_field_set(park_data, remote_tag, remotecc_data->dialog.local_tag); + + if (ast_pthread_create_detached_background(&threadid, NULL, sip_park_thread, park_data)) { + ao2_t_cleanup(park_data->dialog, "drop dialog"); + + ast_string_field_free_memory(park_data); + ast_free(park_data); + + return -1; + } + + return 0; +} + +/* Handle remotecc parkmonitor requests */ +int sip_remotecc_parkmonitor(struct sip_dialog *dialog, struct sip_request *request, struct sip_peer *peer, + struct sip_remotecc_data *remotecc_data) +{ + return sip_remotecc_park(dialog, request, peer, remotecc_data); +} + +/* park call */ +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_t_cleanup(dialog, "drop dialog"); + + goto cleanup; + } + + ast_channel_ref(channel); + ao2_unlock(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + if (!(bridge_channel = ast_channel_bridge_peer(channel))) { + ast_debug(1, "No bridge channel"); + ast_channel_unref(channel); + + goto cleanup; + } + + /* needed so that comebacktoorigini=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->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_send_response(park_data->dialog, "202 Accepted", &park_data->dialog->initial_request); + res = 0; + +cleanup: + if (res) { + sip_send_response(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_t_cleanup(park_data->dialog, "drop dialog"); + + 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 (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_t_cleanup(park_data->dialog, "drop dialog"); + + 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_request 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 (!ast_test_flag(&park_data->dialog->flags[1], SIP_DIALOG_ESTABLISHED)) { + ast_set_flag(&park_data->dialog->flags[0], SIP_OUTGOING); + ast_set_flag(&park_data->dialog->flags[1], SIP_DIALOG_ESTABLISHED); + + park_data->dialog->subscribe_events = SIP_SUBSCRIBE_DIALOG_INFO_XML; + park_data->dialog->expiry = timeout; + + sip_request_init(&request, park_data->dialog, SIP_METHOD_NOTIFY, NULL); + } else { + sip_request_prepare(&request, park_data->dialog, SIP_METHOD_NOTIFY, 0, TRUE); + } + + park_data->dialog->dialog_version++; + sip_request_add_header(&request, "Event", "refer"); + + if (event_type == PARKED_CALL || event_type == PARKED_CALL_REMINDER) { + sip_request_build_header(&request, "Subscription-State", "active;expires=%d", park_data->dialog->expiry); + } else { + sip_request_add_header(&request, "Subscription-State", "terminated;reason=noresource"); + } + + sip_request_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)); + + ast_str_append(&content, 0, "\n"); + /* "parmams" is a typo in the the Cisco API, duh. */ + ast_str_append(&content, 0, + "\n", + park_data->dialog->dialog_version, 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"); + ast_str_append(&content, 0, "\n"); + + sip_request_add_content(&request, ast_str_buffer(content)); + sip_request_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, FALSE, SIP_METHOD_REFER, NULL, 0))) { + return; + } + + sip_dialog_copy(dialog, park_data->dialog); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "notify_display\n"); + ast_str_append(&content, 0, "\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); /* success */ + } else { + ast_str_append(&content, 0, "\200^\n"); /* failed */ + } + + ast_str_append(&content, 0, "10\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "1\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + ao2_lock(dialog); + sip_send_refer_with_content(dialog, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_unlock(dialog); + + ao2_t_cleanup(dialog, "drop dialog"); + } +} diff -durN asterisk-22.3.0.orig/channels/sip/peers.c asterisk-22.3.0/channels/sip/peers.c --- asterisk-22.3.0.orig/channels/sip/peers.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/peers.c 2025-04-17 11:11:22.817703116 +1200 @@ -0,0 +1,3156 @@ + /* + * 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 "include/sip.h" +#include "include/netsock.h" +#include "include/route.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/auth_realms.h" +#include "include/peers.h" +#include "include/realtime.h" +#include "include/registry.h" +#include "include/mwi_subscriptions.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/handlers.h" +#include "include/events.h" +#include "include/handlers.h" +#include "include/conference.h" +#include "include/callback.h" + +static void sip_peer_delete_sched(struct sip_peer *peer); +static int sip_peer_unlink(void *data, void *arg, int flags); +static void sip_peer_expire_aliases(struct sip_peer *peer); +static void sip_peer_mwi_event(void *data, struct stasis_subscription *sub, struct stasis_message *msg); +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_cached_mwi(struct sip_peer *peer, int *new_messages, int *old_messages); +static void sip_peer_set_defaults(struct sip_peer *peer); +static int sip_peer_keepalive(const void *data); + +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_mailbox_destroy_all(struct sip_peer *peer); + +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_alias_destroy_all(struct sip_peer *peer); + +static void sip_subscription_build(struct sip_peer *peer, const char *config, int lineno); +static void sip_subscription_destroy(struct sip_subscription *subscription); +static void sip_subscription_destroy_all(struct sip_peer *peer); + +/* The peer list */ +struct ao2_container *sip_peers; +struct ao2_container *sip_peers_by_address; + +/* These counters are not handled in a thread-safe way ast_atomic_fetchadd_int should be used to modify these values. */ +int sip_static_count = 0; /* Static peers */ +int sip_realtime_count = 0; /* Realtime peers */ + +/* A bogus peer, to be used when authentication should fail */ +struct sip_peer *sip_bogus_peer = NULL; + +/* The only member of the peer used here is the name field */ +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); +} + +/* The only member of the peer used here is the name field */ +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; +} + +/* Hash function based on the peer's ip address. For IPv6, we use the end of the address. */ +int sip_peer_hash_by_address(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; +} + +/* Match Peers by IP and Port number. The peer's addr struct provides to fields combined to make a key: the sin_addr.s_addr and sin_port + * fields transport is compared separately). */ +int sip_peer_cmp_by_address(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 devstate_only) +{ + struct sip_peer *peer = ao2_t_find(sip_peers, name, OBJ_SEARCH_KEY, "bump peer"); + + if (!peer && (realtime || devstate_only)) { + peer = sip_realtime_load(name, NULL, devstate_only); + } + + return peer; +} + +struct sip_peer *sip_peer_find_by_address(const struct ast_sockaddr *address, int transport, int realtime, int devstate_only) +{ + struct sip_peer *peer; + + if ((peer = ao2_t_find(sip_peers_by_address, address, OBJ_SEARCH_KEY, "bump peer"))) { + /* Check that transport matches */ + if (!(peer->transports & transport)) { + ao2_t_ref(peer, -1, "drop peer"); + return NULL; + } + } else { + peer = NULL; + } + + if (!peer && (realtime || devstate_only)) { + peer = sip_realtime_load(NULL, address, devstate_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; +} + +static void sip_peer_delete_sched(struct sip_peer *peer) +{ + if (peer->qualify_expire_sched_id != -1) { + AST_SCHED_DEL_UNREF(sip_sched_context, peer->qualify_expire_sched_id, ao2_t_cleanup(peer, "drop peer")); + } + + if (peer->register_expire_sched_id != -1) { + AST_SCHED_DEL_UNREF(sip_sched_context, peer->register_expire_sched_id, ao2_t_cleanup(peer, "drop peer")); + } + + if (peer->keepalive_sched_id != -1) { + AST_SCHED_DEL_UNREF(sip_sched_context, peer->keepalive_sched_id, ao2_t_cleanup(peer, "drop peer")); + } +} + +/* this func is used with ao2_callback to unlink/delete all marked or linked peers, depending on arg */ +static int sip_peer_unlink(void *data, void *arg, int flags) +{ + struct sip_peer *peer; + int removed_only; + + peer = (struct sip_peer *) data; + removed_only = *(int *) arg; + + if (!removed_only || peer->removed) { + sip_peer_delete_sched(peer); + + if (peer->dnsmgr) { + ast_dnsmgr_release(peer->dnsmgr); + peer->dnsmgr = NULL; + ao2_t_cleanup(peer, "drop peer"); + } + + return CMP_MATCH; + } + + return 0; +} + +void sip_peer_unlink_all(int marked_only) +{ + /* We must remove the ref outside of the peers container to prevent a deadlock condition when unsubscribing from + * stasis while it is invoking a subscription event callback. */ + ao2_t_callback(sip_peers, OBJ_UNLINK | OBJ_MULTIPLE | OBJ_NODATA, sip_peer_unlink, &marked_only, "unlink peer"); + ao2_t_callback(sip_peers_by_address, OBJ_UNLINK | OBJ_MULTIPLE | OBJ_NODATA, sip_peer_unlink, &marked_only, "unlink peer"); +} + +/* Destroy peer object from memory */ +static void sip_peer_destroy(void *data) +{ + struct sip_peer *peer = (struct sip_peer *) data; + + ast_debug(3, "Destroying SIP 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. */ + sip_mailbox_destroy_all(peer); + sip_alias_destroy_all(peer); + sip_subscription_destroy_all(peer); + sip_selected_destroy_all(peer); + + if (peer->proxy) { + ao2_t_ref(peer->proxy, -1, "drop proxy"); + peer->proxy = NULL; + } + + /* Delete it, it needs to disappear */ + if (peer->qualify_dialog) { + sip_dialog_unlink(peer->qualify_dialog); + ao2_t_cleanup(peer->qualify_dialog, "drop dialog"); + + peer->qualify_dialog = NULL; + } + + if (peer->mwi_dialog) { /* We have an active subscription, delete it */ + sip_dialog_unlink(peer->mwi_dialog); + ao2_t_cleanup(peer->mwi_dialog, "drop dialog"); + + peer->mwi_dialog = NULL; + } + + if (peer->feature_events_dialog) { /* We have an active subscription, delete it */ + sip_dialog_unlink(peer->feature_events_dialog); + ao2_t_cleanup(peer->feature_events_dialog, "drop dialog"); + + peer->feature_events_dialog = NULL; + } + + if (peer->callback) { + sip_callback_destroy(peer); + peer->callback = NULL; + } + + if (peer->channel_variables) { + ast_variables_destroy(peer->channel_variables); + peer->channel_variables = NULL; + } + + sip_route_clear(&peer->path); + + ast_free_acl_list(peer->acl); + ast_free_acl_list(peer->contact_acl); + ast_free_acl_list(peer->direct_media_acl); + + if (!ast_test_flag(&sip_config.flags[1], SIP_REALTIME_CACHE_PEERS) && peer->realtime) { + ast_atomic_fetchadd_int(&sip_realtime_count, -1); + ast_debug(3, "Realtime peer %s destroyed. Realtime peer objects: %d\n", peer->name, sip_realtime_count); + } else { + ast_atomic_fetchadd_int(&sip_static_count, -1); + } + + if (peer->auth_realms) { + ao2_t_ref(peer->auth_realms, -1, "drop auth_realms"); + peer->auth_realms = NULL; + } + + if (peer->socket.tcptls_session) { + ao2_ref(peer->socket.tcptls_session, -1); + peer->socket.tcptls_session = NULL; + } + + 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); + peer->endpoint = NULL; +} + +/* Update peer data in database (if used) */ +void sip_peer_update(struct sip_peer *peer, int expiry) +{ + if (sip_config.realtime_update_peer && (peer->realtime || ast_test_flag(&peer->flags[1], SIP_REALTIME_CACHE_PEERS))) { + sip_realtime_update(peer, expiry); + } +} + +/* Get cached MWI info */ +static int sip_peer_get_cached_mwi(struct sip_peer *peer, int *new_messages, int *old_messages) +{ + struct sip_mailbox *mailbox; + int in_cache; + + in_cache = 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; + + in_cache = TRUE; + } + + return in_cache; +} + +/* Receive MWI events that we have subscribed to */ +static void sip_peer_mwi_event(void *data, struct stasis_subscription *sub, struct stasis_message *msg) +{ + struct sip_peer *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(sub, msg) || ao2_ref(peer, 0) == 0) { + return; + } + + if (ast_mwi_state_type() == stasis_message_type(msg)) { + 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); + + if (ast_test_flag((&peer->flags[1]), SIP_SUBSCRIBE_MWI_ONLY) && !peer->mwi_dialog) { + sip_peer_set_messages(peer, 0, 0, TRUE); + ao2_unlock(peer); + + return -1; + } + + /* Do we have an IP address? If not, skip this peer */ + if (ast_sockaddr_isnull(&peer->address) && ast_sockaddr_isnull(&peer->default_address)) { + 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_cached_mwi(peer, &new_messages, &old_messages) && !cache_only) { + /* Fall back to manually checking the mailbox if not cache_only and sip_peer_get_cached_mwi 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); + + /* If there is no mailbox do nothing */ + if (!ast_str_strlen(mailboxes)) { + sip_peer_set_messages(peer, 0, 0, FALSE); + return 0; + } + + ast_app_inboxcount(ast_str_buffer(mailboxes), &new_messages, &old_messages); + ao2_lock(peer); + } + + if (peer->mwi_exten) { + mwi_exten = ast_strdupa(peer->mwi_exten); + } else { + mwi_exten = NULL; + } + + if (peer->mwi_dialog) { + /* Base message on subscription */ + dialog = ao2_t_bump(peer->mwi_dialog, "bump dialog"); + ao2_unlock(peer); + } else { + ao2_unlock(peer); + + /* Build temporary dialog for this message */ + if (!(dialog = sip_dialog_alloc(NULL, &peer->socket, FALSE, 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_t_cleanup(dialog, "drop dialog"); + + sip_peer_set_messages(peer, 0, 0, FALSE); + return -1; + } + + /* Recalculate our side, and recalculate Call ID */ + sip_get_our_address(dialog->socket.transport, &dialog->address, &dialog->our_address); + + ao2_lock(peer); + + if (!ast_strlen_zero(peer->mwi_from)) { + ast_string_field_set(dialog, mwi_from, peer->mwi_from); + } else if (!ast_strlen_zero(sip_config.mwi_from)) { + ast_string_field_set(dialog, mwi_from, sip_config.mwi_from); + } + + ao2_unlock(peer); + + /* Destroy this session after 32 secs */ + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + } + + /* We have multiple threads (mwi events and monitor retransmits) working with this PVT and as we modify the sip history if + * that's turned on, we really need to have a lock on it */ + ao2_lock(dialog); + + /* Send MWI */ + ast_set_flag(&dialog->flags[0], SIP_OUTGOING); + /* the following will decrement the refcount on dialog as it finishes */ + sip_send_notify_with_mwi(dialog, new_messages, old_messages, mwi_exten); + + ao2_unlock(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + sip_peer_set_messages(peer, new_messages, old_messages, FALSE); + + return 0; +} + +/* Expire bulk-register aliases */ +static void sip_peer_expire_aliases(struct sip_peer *peer) +{ + struct sip_alias *alias; + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (!alias->peer) { + continue; + } + + ast_verb(3, "Unregistered SIP peer alias '%s'\n", alias->name); + + alias->peer->last_qualify = 0; + ast_sockaddr_setnull(&alias->peer->address); + + if (alias->peer->socket.tcptls_session) { + ao2_ref(alias->peer->socket.tcptls_session, -1); + alias->peer->socket.tcptls_session = NULL; + } + + ast_string_field_set(alias->peer, full_contact, ""); + ast_string_field_set(alias->peer, username, ""); + ast_string_field_set(alias->peer, useragent, ""); + + 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); + } + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); + + ao2_t_cleanup(alias->peer, "drop peer"); + alias->peer = NULL; + } +} + +/* Update bulk-register aliases */ +void sip_peer_register_aliases(struct sip_peer *peer) +{ + struct sip_alias *alias; + char *scheme, *domain; + + if (ast_sockaddr_isnull(&peer->address) && ast_sockaddr_isnull(&peer->default_address)) { + return; + } + + scheme = ast_strdupa(peer->full_contact); + + if (!(domain = strchr(scheme, ':'))) { + return; + } + + *domain++ = '\0'; + + if (!(domain = strchr(domain, '@'))) { + 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 schedules that may have been created */ + sip_peer_delete_sched(alias->peer); + } + + /* These settings could have been overwritten by a reload */ + alias->peer->cisco_line_index = alias->line_index; + ast_string_field_set(alias->peer, username, alias->name); + + ast_string_field_set(alias->peer, cisco_auth_name, peer->name); + ast_string_field_set(alias->peer, secret, peer->secret); + ast_string_field_set(alias->peer, md5secret, peer->md5secret); + + ast_string_field_set(alias->peer, register_call_id, peer->register_call_id); + ast_string_field_set(alias->peer, cisco_device_name, peer->cisco_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->options = peer->options; + + ast_string_field_build(alias->peer, full_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 alias '%s' at %s\n", alias->peer->name, ast_sockaddr_stringify(&alias->peer->address)); + alias->peer->offhook = 0; + + sip_peer_update(alias->peer, sip_config.max_expiry); + sip_peer_set_messages(alias->peer, 0, 0, FALSE); + } +} + +/* Register all bulk-register aliases */ +void sip_peer_register_aliases_all(void) +{ + struct ao2_iterator iter; + struct sip_peer *peer; + struct sip_alias *alias; + + if (!sip_static_count) { + return; + } + + iter = ao2_iterator_init(sip_peers, 0); + + while ((peer = ao2_t_iterator_next(&iter, "bump peer"))) { + ao2_lock(peer); + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (alias->peer) { + ast_sockaddr_copy(&alias->peer->address, &peer->address); + } + } + + sip_peer_register_aliases(peer); + + ao2_unlock(peer); + ao2_t_cleanup(peer, "drop peer"); + } + + 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) && ast_sockaddr_isnull(&peer->default_address)) { + return 0; + } + + if (ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER)) { + 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, FALSE, 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 */ + ast_copy_flags(&dialog->flags[0], &peer->flags[0], SIP_FLAGS0_MASK); + ast_copy_flags(&dialog->flags[1], &peer->flags[1], SIP_FLAGS1_MASK); + ast_copy_flags(&dialog->flags[2], &peer->flags[2], SIP_FLAGS2_MASK); + + /* Recalculate our side, and recalculate Call ID */ + sip_get_our_address(dialog->socket.transport, &dialog->address, &dialog->our_address); + + 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, full_contact, peer->full_contact); + ast_string_field_set(dialog, username, peer->username); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", peer->do_not_disturb ? "enable" : "disable"); + ast_str_append(&content, 0, "\n", + ast_test_flag(&peer->flags[2], SIP_DND_BUSY) ? "callreject" : "ringeroff"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", peer->hunt_group ? "on" : "off"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 9, "\n"); + + new_messages = 0; + old_messages = 0; + + if (!sip_peer_get_cached_mwi(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->cisco_line_index); + ast_str_append(&content, 0, "%s\n", new_messages ? "yes" : "no"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n", new_messages, old_messages); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + 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"); + ast_str_append(&content, 0, "\n"); + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + int new_messages, old_messages; + + if (!alias->peer) { + continue; + } + + new_messages = 0; + old_messages = 0; + + if (!sip_peer_get_cached_mwi(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->cisco_line_index); + ast_str_append(&content, 0, "%s\n", new_messages ? "yes" : "no"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n", new_messages, old_messages); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%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"); + ast_str_append(&content, 0, "\n"); + } + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + sip_send_refer_with_content(dialog, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(dialog, "drop dialog"); + + ast_free(content); + } else if (peer->feature_events_dialog) { + struct sip_request request; + struct sip_dialog *dialog = peer->feature_events_dialog; + char boundary[32]; + + ast_set_flag(&dialog->flags[0], SIP_OUTGOING); + sip_request_prepare(&request, dialog, SIP_METHOD_NOTIFY, 0, TRUE); + + snprintf(boundary, sizeof(boundary), "%08lx%08lx%08lx", ast_random(), ast_random(), ast_random()); + + sip_request_add_header(&request, "Event", "as-feature-event"); + sip_request_add_header(&request, "Subscription-State", dialog->expiry ? "active" : "terminated;reason=timeout"); + sip_request_build_header(&request, "Content-Type", "multipart/mixed; boundary=%s", boundary); + + sip_request_build_content(&request, "--%s\r\n", boundary); + sip_request_add_content(&request, "Content-Type: application/x-as-feature-event+xml\r\n"); + sip_request_add_content(&request, "\r\n"); + sip_request_add_content(&request, "\n"); + sip_request_add_content(&request, "\n"); + sip_request_build_content(&request, "\n%s\n", + peer->do_not_disturb ? "true" : "false"); + sip_request_add_content(&request, "\n"); + sip_request_add_content(&request, "\r\n"); + + sip_request_build_content(&request, "--%s\r\n", boundary); + sip_request_add_content(&request, "Content-Type: application/x-as-feature-event+xml\r\n"); + sip_request_add_content(&request, "\r\n"); + sip_request_add_content(&request, "\n"); + sip_request_add_content(&request, "\n"); + sip_request_add_content(&request, "\nforwardImmediate\n"); + sip_request_build_content(&request, "%s\n%s\n", + !ast_strlen_zero(peer->call_forward) ? "true" : "false", peer->call_forward); + sip_request_add_content(&request, "\n"); + sip_request_add_content(&request, "\r\n"); + + sip_request_build_content(&request, "--%s--\r\n", boundary); + sip_request_send(dialog, &request, SIP_SEND_RELIABLE, 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) && ast_sockaddr_isnull(&peer->default_address)) { + return 0; + } + + if (ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER)) { + struct sip_dialog *dialog; + struct ast_str *content; + + if (!(dialog = sip_dialog_alloc(NULL, NULL, FALSE, SIP_METHOD_REFER, NULL, 0))) { + return -1; + } + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + return -1; + } + + sip_get_our_address(dialog->socket.transport, &dialog->address, &dialog->our_address); + + content = ast_str_alloca(2048); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", peer->do_not_disturb ? "enable" : "disable"); + ast_str_append(&content, 0, "\n", + ast_test_flag(&dialog->flags[2], SIP_DND_BUSY) ? "callreject" : "ringeroff"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + sip_send_refer_with_content(dialog, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_t_cleanup(dialog, "drop dialog"); + } else if (peer->feature_events_dialog) { + struct sip_request request; + + sip_request_prepare(&request, peer->feature_events_dialog, SIP_METHOD_NOTIFY, 0, TRUE); + + sip_request_add_header(&request, "Event", "as-feature-event"); + sip_request_add_header(&request, "Subscription-State", + peer->feature_events_dialog->expiry ? "active" : "terminated;reason=timeout"); + sip_request_add_header(&request, "Content-Type", "application/x-as-feature-event+xml"); + + sip_request_add_content(&request, "\n"); + sip_request_add_content(&request, "\n"); + sip_request_build_content(&request, "\n%s\n", + peer->do_not_disturb ? "true" : "false"); + sip_request_add_content(&request, "\n"); + + sip_request_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) && ast_sockaddr_isnull(&peer->default_address)) { + return 0; + } + + if (ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER)) { + struct sip_dialog *dialog; + struct ast_str *content; + + if (!(dialog = sip_dialog_alloc(NULL, NULL, FALSE, SIP_METHOD_REFER, NULL, 0))) { + return -1; + } + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + return -1; + } + + sip_get_our_address(dialog->socket.transport, &dialog->address, &dialog->our_address); + + content = ast_str_alloca(2048); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", peer->hunt_group ? "on" : "off"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + sip_send_refer_with_content(dialog, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_t_cleanup(dialog, "drop dialog"); + } + + 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) && ast_sockaddr_isnull(&peer->default_address)) { + return 0; + } + + if (ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER)) { + struct sip_dialog *dialog; + struct ast_str *content; + + if (!(dialog = sip_dialog_alloc(NULL, NULL, FALSE, SIP_METHOD_REFER, NULL, 0))) { + return -1; + } + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + return -1; + } + + sip_get_our_address(dialog->socket.transport, &dialog->address, &dialog->our_address); + + content = ast_str_alloca(2048); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%d\n", peer->cisco_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"); + ast_str_append(&content, 0, "\n"); + + sip_send_refer_with_content(dialog, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_t_cleanup(dialog, "drop dialog"); + } else if (peer->feature_events_dialog) { + struct sip_request request; + + sip_request_prepare(&request, peer->feature_events_dialog, SIP_METHOD_NOTIFY, 0, TRUE); + + sip_request_add_header(&request, "Event", "as-feature-event"); + sip_request_add_header(&request, "Subscription-State", peer->feature_events_dialog->expiry ? "active" : "terminated;reason=timeout"); + sip_request_add_header(&request, "Content-Type", "application/x-as-feature-event+xml"); + + sip_request_add_content(&request, "\n"); + sip_request_add_content(&request, "\n"); + sip_request_add_content(&request, "\nforwardImmediate\n"); + sip_request_build_content(&request, "%s\n", + !ast_strlen_zero(peer->call_forward) ? "true" : "false"); + sip_request_build_content(&request, "%s\n", + !ast_strlen_zero(peer->call_forward) ? "true" : "false", peer->call_forward); + sip_request_add_content(&request, "\n"); + + sip_request_send(peer->feature_events_dialog, &request, SIP_SEND_RELIABLE, peer->feature_events_dialog->outgoing_cseq); + } + + return 0; +} + +void sip_peer_send_qrt_url(struct sip_peer *peer) +{ + struct sip_dialog *dialog; + struct ast_str *url, *content; + + if (ast_strlen_zero(peer->cisco_qrt_url)) { + return; + } + + url = ast_str_alloca(2048); + + ast_str_set(&url, 0, "%s", peer->cisco_qrt_url); + ast_str_append(&url, 0, "%sname=%s", strchr(ast_str_buffer(url), '?') ? "&" : "?", peer->cisco_device_name); + + if (!((dialog = sip_dialog_alloc(NULL, NULL, FALSE, SIP_METHOD_REFER, NULL, 0)))) { + return; + } + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + return; + } + + content = ast_str_alloca(4096); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n", ast_str_buffer(url)); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + sip_send_refer_with_content(dialog, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(dialog, "drop dialog"); +} + +/* Expire registration of SIP peer */ +int sip_peer_expire_register(const void *data) +{ + struct sip_peer *peer; + struct sip_subscription *subscription; + + peer = (struct sip_peer *) data; + + peer->register_expire_sched_id = -1; + peer->port_in_uri = FALSE; + ast_string_field_set(peer, register_call_id, ""); + + sip_peer_astdb_delete(peer); /* remove registration data from storage */ + sip_socket_set_transport(&peer->socket, 0); + + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, next) { + if (subscription->dialog) { + sip_dialog_unlink(subscription->dialog); + ao2_t_cleanup(subscription->dialog, "drop dialog"); + + subscription->dialog = NULL; + } + } + + 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_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + + /* Do we need to release this peer from memory? Only for realtime peers */ + if (peer->realtime) { + ast_debug(3, "Realtime peer %s expired registration. Realtime peer objects now %d\n", peer->name, sip_realtime_count); + } + + if (ast_test_flag(&peer->flags[1], SIP_REALTIME_AUTO_CLEAR)) { + ao2_t_unlink(sip_peers, peer, "unlink peer"); + } + + if (!ast_sockaddr_isnull(&peer->address)) { + /* We still need to unlink the peer from the sip_peers_by_address table, otherwise we end up with multiple copies + * hanging around each time a registration expires and the peer re-registers. */ + ao2_t_unlink(sip_peers_by_address, peer, "unlink peer"); + } + + /* Only clear the addr after we check for destruction. The addr must remain + * in order to unlink from the sip_peers_by_address container correctly */ + ast_sockaddr_setnull(&peer->address); + ast_sockaddr_setnull(&peer->socket.address); + + sip_peer_expire_aliases(peer); + ao2_t_cleanup(peer, "drop peer"); + + return 0; +} + +/* Get registration details from Asterisk DB */ +static void sip_peer_astdb_load(struct sip_peer *peer) +{ + char data[1024], *host, *username, *full_contact, *parse; + int expiry; + + /* Cisco phones reboot when Asterisk restarts so there is no point trying to reconnect */ + if (ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER)) { + return; + } + + /* If read-only RT backend, then refresh from local DB cache */ + if (peer->realtime_from_contact && sip_config.realtime_update_peer) { + return; + } + + if (ast_db_get("SIP/Peer", peer->name, data, sizeof(data))) { + return; + } + + parse = data; + + host = strsep(&parse, " "); + expiry = atoi(strsep(&parse, " ")); + username = strsep(&parse, " "); + full_contact = strsep(&parse, " "); + + if (!ast_sockaddr_parse(&peer->address, host, PARSE_PORT_REQUIRE) || !expiry) { + ast_sockaddr_setnull(&peer->address); + return; + } + + if (!ast_strlen_zero(username)) { + ast_string_field_set(peer, username, username); + } + + if (!ast_strlen_zero(full_contact)) { + ast_string_field_set(peer, full_contact, full_contact); + } + + if (!ast_db_get("SIP/PeerPath", peer->name, data, sizeof(data))) { + sip_parse_path(NULL, peer, NULL, data); + } + + ast_debug(2, "Seeding peer '%s' at %s@%s for %ds\n", + peer->name, peer->username, ast_sockaddr_stringify_host(&peer->address), expiry); + + AST_SCHED_REPLACE_UNREF(peer->register_expire_sched_id, sip_sched_context, (expiry + 10) * 1000, sip_peer_expire_register, peer, + ao2_t_cleanup(_data, "drop peer"), + ao2_t_cleanup(peer, "drop peer"), + ao2_t_bump(peer, "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_expiry && sip_config.realtime_update_peer && peer->realtime_from_contact) { + ast_update_realtime("sippeers", "name", peer->name, "fullcontact", "", "ipaddr", "", "port", "0", + "regseconds", "0", "regserver", "", "useragent", "", "lastms", "0", SENTINEL); + } + } else { + ast_db_del("SIP/Peer", peer->name); + ast_db_del("SIP/PeerPath", peer->name); + } +} + +/* Send initial subscription state updates to peer */ +void sip_peer_update_subscriptions(struct sip_peer *peer) +{ + struct sip_subscription *subscription; + + if (peer->register_expire_sched_id == -1) { + ast_debug(1, "Not creating subscriptions as peer '%s' is not registered\n", peer->name); + return; + } + + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, next) { + struct sip_request request; + 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_t_cleanup(subscription->dialog, "drop dialog"); + subscription->dialog = NULL; + } + + if (!subscription->dialog) { + if (!(subscription->dialog = sip_dialog_alloc(NULL, &peer->socket, FALSE, SIP_METHOD_NOTIFY, NULL, 0))) { + return; + } + } + + /* 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, full_contact, peer->full_contact); + ast_string_field_set(subscription->dialog, username, peer->username); + ast_string_field_set(subscription->dialog, from_user, subscription->exten); + ast_string_field_set(subscription->dialog, from_name, ""); + + ast_string_field_set(subscription->dialog, context, subscription->context); + ast_string_field_set(subscription->dialog, exten, subscription->exten); + ast_string_field_build(subscription->dialog, subscribe_uri, "%s@%s", subscription->exten, subscription->context); + + ast_copy_flags(&subscription->dialog->flags[0], &peer->flags[0], SIP_FLAGS0_MASK); + ast_copy_flags(&subscription->dialog->flags[1], &peer->flags[1], SIP_FLAGS1_MASK); + ast_copy_flags(&subscription->dialog->flags[2], &peer->flags[2], SIP_FLAGS2_MASK); + + /* Notify is outgoing call */ + ast_set_flag(&subscription->dialog->flags[0], SIP_OUTGOING); + ast_set_flag(&subscription->dialog->flags[1], SIP_DIALOG_ESTABLISHED); + ast_set_flag(&subscription->dialog->flags[2], SIP_SUBSCRIPTION_STATE_ACTIVE); + + subscription->dialog->subscribe_events = SIP_SUBSCRIBE_PIDF_XML; /* Needs to be configurable */ + subscription->dialog->expiry = 0; + + sip_request_init(&request, subscription->dialog, SIP_METHOD_NOTIFY, NULL); + sip_request_copy(&(subscription->dialog->initial_request), &request); + + ast_debug(1, "Set initial SUBSCRIBE request for '%s'\n", subscription->dialog->call_id); + + /* Because we only use this req to initialize the dialog's initreq we have to manually deallocate it */ + sip_request_destroy(&request); + + subscription->dialog->extension_state_id = ast_extension_state_add_extended(subscription->dialog->context, + subscription->dialog->exten, sip_extension_state_event, subscription->dialog); + + if (subscription->dialog->extension_state_id == -1) { + sip_dialog_unlink(subscription->dialog); + ao2_t_cleanup(subscription->dialog, "drop dialog"); + 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); + } + } + + ast_set_flag(&subscription->dialog->flags[0], SIP_FORCE_STATE_CHANGE); + sip_extension_state_event(subscription->dialog->context, subscription->dialog->exten, &state_info, subscription->dialog); + } +} + +/* 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()); + } + } +} + +/* 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); + } +} + +/* Report Peer status in character string */ +int sip_peer_get_status(struct sip_peer *peer, struct ast_str **status) +{ + int res; + + if (peer->qualify_max) { + res = SIP_PEER_UNREACHABLE; + + if (peer->last_qualify < 0) { + ast_str_set(status, 0, "UNREACHABLE"); + } else if (peer->last_qualify > peer->qualify_max) { + ast_str_set(status, 0, "LAGGED (%dms)", peer->last_qualify); + res = SIP_PEER_REACHABLE; + } else if (peer->last_qualify) { + ast_str_set(status, 0, "OK (%dms)", peer->last_qualify); + res = SIP_PEER_REACHABLE; + } else { + ast_str_set(status, 0, "UNKNOWN"); + } + } else { + ast_str_set(status, 0, "UNMONITORED"); + /* Checking if port is 0 */ + res = SIP_PEER_UNMONITORED; + } + + return res; +} + +/* list peer mailboxes to CLI */ +void sip_peer_get_mailboxes(struct sip_peer *peer, struct ast_str **mailboxes) +{ + struct sip_mailbox *mailbox; + + AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, next) { + ast_str_append(mailboxes, 0, "%s%s", mailbox->name, AST_LIST_NEXT(mailbox, next) ? "," : ""); + } +} + +/* Create temporary peer */ +struct sip_peer *sip_peer_temp_alloc(const char *name) +{ + struct sip_peer *peer; + + if (!(peer = ao2_t_alloc(sizeof(*peer), sip_peer_destroy, "alloc peer"))) { + return NULL; + } + + if (ast_string_field_init(peer, 512)) { + ao2_t_ref(peer, -1, "drop peer"); + return NULL; + } + + if (!(peer->format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + ao2_t_ref(peer, -1, "drop peer"); + return NULL; + } + + ast_string_field_set(peer, name, name); + sip_peer_set_defaults(peer); + + peer->host_dynamic = TRUE; + peer->register_expire_sched_id = -1; + peer->qualify_expire_sched_id = -1; + peer->keepalive_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_expire_sched_id == -1) { + /* Don't reset expire or port time during reload if we have an active registration */ + sip_peer_delete_sched(peer); + sip_socket_set_transport(&peer->socket, AST_TRANSPORT_UDP); + + ast_sockaddr_setnull(&peer->address); + ast_sockaddr_setnull(&peer->default_address); + } + + ast_copy_flags(&peer->flags[0], &sip_config.flags[0], SIP_FLAGS0_MASK); + ast_copy_flags(&peer->flags[1], &sip_config.flags[1], SIP_FLAGS1_MASK); + ast_copy_flags(&peer->flags[2], &sip_config.flags[2], SIP_FLAGS2_MASK); + + ast_string_field_set(peer, context, sip_config.context); + ast_string_field_set(peer, message_context, sip_config.message_context); + ast_string_field_set(peer, subscribe_context, sip_config.subscribe_context); + 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, rtp_engine, sip_config.rtp_engine); + + ast_format_cap_append_from_cap(peer->format_cap, sip_config.format_cap, AST_MEDIA_TYPE_UNKNOWN); + peer->max_call_bitrate = sip_config.max_call_bitrate; + + peer->rtp_timeout = sip_config.rtp_timeout; + peer->rtp_hold_timeout = sip_config.rtp_hold_timeout; + peer->rtp_keepalive = sip_config.rtp_keepalive; + + peer->auto_framing = sip_config.auto_framing; + + peer->t38_max_datagram = -1; + peer->qualify_freq = sip_config.qualify_freq; + + if (sip_config.call_counter) { + peer->call_limit = INT_MAX; + } + + ast_string_field_set(peer, description, ""); + ast_string_field_set(peer, mwi_exten, sip_config.mwi_exten); + + ast_string_field_set(peer, secret, ""); + ast_string_field_set(peer, remote_secret, ""); + ast_string_field_set(peer, md5secret, ""); + + ast_string_field_set(peer, caller_number, ""); + ast_string_field_set(peer, caller_name, ""); + ast_string_field_set(peer, caller_tag, ""); + + ast_string_field_set(peer, from_domain, ""); + ast_string_field_set(peer, from_user, ""); + + peer->callgroup = 0; + peer->pickupgroup = 0; + + peer->qualify_max = sip_config.qualify_max; + peer->keepalive = sip_config.keepalive; + + ast_string_field_set(peer, zone, sip_config.zone); + + peer->stimer_mode = sip_config.stimer_mode; /* Session-Timers */ + peer->stimer_refresher = sip_config.stimer_refresher; + peer->stimer_min_se = sip_config.stimer_min_se; + peer->stimer_max_se = sip_config.stimer_max_se; + + peer->timer_t1 = sip_config.timer_t1; + peer->timer_b = sip_config.timer_b; + + peer->disallowed_methods = sip_config.disallowed_methods; + peer->transports = sip_config.transports; + peer->default_outgoing_transport = sip_config.primary_transport; + + if (peer->proxy) { + ao2_ref(peer->proxy, -1); + peer->proxy = NULL; + } + + peer->cisco_line_index = 1; + peer->cisco_pickup_notify_timer = 5; +} + +/* 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 devstate_only) +{ + struct ast_variable *variable; + struct sip_peer *peer; + struct ast_acl_list *old_acl, *old_contact_acl, *old_direct_media_acl; + int found, first_pass, port, timerb_set, timert1_set, subscribe_acl_change, header_count, line_index; + char *host; + time_t reg_seconds; + + if (!realtime || ast_test_flag(&sip_config.flags[1], SIP_REALTIME_CACHE_PEERS)) { + /* Note we do NOT use sip_peer_find here, to avoid realtime recursion */ + /* We also use a case-sensitive comparison (unlike sip_peer_find) so that case changes made to the peer name will be + * properly handled during reload */ + peer = ao2_t_find(sip_peers, name, OBJ_SEARCH_KEY | OBJ_UNLINK, "unlink+bump peer"); + } else { + peer = NULL; + } + + found = FALSE; + first_pass = TRUE; + + if (peer) { + /* Already in the list, remove it and it will be added back (or FREE'd) */ + found = TRUE; + + /* we've unlinked the peer from the peers container but not unlinked from the sip_peers_by_address container yet this leads + * to a wrong refcounter and the peer object is never destroyed */ + if (!ast_sockaddr_isnull(&peer->address)) { + ao2_t_unlink(sip_peers_by_address, peer, "unlink peer"); + } + + if (!peer->removed) { + first_pass = FALSE; + } else { + ast_format_cap_remove_by_type(peer->format_cap, AST_MEDIA_TYPE_UNKNOWN); + } + } else { + if (!(peer = ao2_t_alloc(sizeof(*peer), sip_peer_destroy, "alloc peer"))) { + return NULL; + } + + if (!(peer->endpoint = ast_endpoint_create("SIP", name))) { + ao2_t_ref(peer, -1, "drop peer"); + return NULL; + } + + if (!(peer->format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + ao2_t_ref(peer, -1, "drop peer"); + return NULL; + } + + if (ast_string_field_init(peer, 512)) { + ao2_t_ref(peer, -1, "drop peer"); + return NULL; + } + + if (realtime && !ast_test_flag(&sip_config.flags[1], SIP_REALTIME_CACHE_PEERS)) { + ast_atomic_fetchadd_int(&sip_realtime_count, 1); + ast_debug(3, "Realtime peer %s built. Realtime peer objects: %d\n", name, sip_realtime_count); + } else { + ast_atomic_fetchadd_int(&sip_static_count, 1); + } + + peer->register_expire_sched_id = -1; + peer->qualify_expire_sched_id = -1; + peer->keepalive_sched_id = -1; + } + + /* Note that our peer HAS had its reference count increased */ + if (first_pass) { + old_acl = peer->acl; + peer->acl = NULL; + + old_contact_acl = peer->contact_acl; + peer->contact_acl = NULL; + + old_direct_media_acl = peer->direct_media_acl; + peer->direct_media_acl = NULL; + + sip_peer_set_defaults(peer); /* Set peer defaults */ + } + + /* in case the case of the peer name has changed, update the name */ + ast_string_field_set(peer, name, name); + + /* If we have channel variables, remove them (reload) */ + if (peer->channel_variables) { + ast_variables_destroy(peer->channel_variables); + peer->channel_variables = NULL; + } + + if (found) { + peer->port_in_uri = FALSE; + } + + /* If we have realm authentication information, remove them (reload) */ + ao2_lock(peer); + + if (peer->auth_realms) { + ao2_t_ref(peer->auth_realms, -1, "drop auth_realms"); + peer->auth_realms = NULL; + } + + ao2_unlock(peer); + + /* clear the transport information. We will detect if a default value is required after parsing the config */ + peer->default_outgoing_transport = 0; + peer->transports = 0; + + if (!devstate_only) { + struct sip_mailbox *mailbox; + struct sip_alias *alias; + struct sip_subscription *subscription; + + 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; + } + } + + /* 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); + + host = NULL; + port = 0; + reg_seconds = 0; + + timerb_set = FALSE; + timert1_set = FALSE; + subscribe_acl_change = FALSE; + + old_acl = NULL; + old_contact_acl = NULL; + old_direct_media_acl = NULL; + + header_count = 0; + line_index = 2; /* First non-primary line */ + + for (variable = variables; variable; variable = variable->next) { + if (!devstate_only) { + if (!strcasecmp(variable->name, "type")) { + /* skip for now */ + } else if (!strcasecmp(variable->name, "transport")) { + char *transport, *option; + + peer->transports = 0; + peer->default_outgoing_transport = 0; + + transport = ast_strdupa(variable->value); + + while ((option = strsep(&transport, ","))) { + option = ast_skip_blanks(option); + + if (!strncasecmp(option, "udp", 3)) { + peer->transports |= AST_TRANSPORT_UDP; + } else if (sip_config.tcp_enabled && !strncasecmp(option, "tcp", 3)) { + peer->transports |= AST_TRANSPORT_TCP; + } else if (sip_tls_config.enabled && !strncasecmp(option, "tls", 3)) { + peer->transports |= AST_TRANSPORT_TLS; + } else if (!strncasecmp(option, "tcp", 3) || !strncasecmp(option, "tls", 3)) { + ast_log(LOG_WARNING, + "Invalid %s '%.3s' when %.3senable=no at line %d, if no other is specified, the defaults from general will be used\n", + variable->name, option, option, variable->lineno); + } else { + ast_log(LOG_NOTICE, + "Invalid %s '%.3s' at line %d, if no other is specified, the defaults from general will be used\n", + variable->name, option, variable->lineno); + } + + } + if (!peer->default_outgoing_transport) { /* The first transport listed should be default outbound */ + peer->default_outgoing_transport = peer->transports; + } + } else if (!strcasecmp(variable->name, "nat")) { + char *nat, *option; + + nat = ast_strdupa(variable->value); + + while ((option = strsep(&nat, ","))) { + if (ast_false(option)) { + ast_clear_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT); + ast_clear_flag(&peer->flags[1], SIP_SYMMETRIC_RTP); + ast_clear_flag(&peer->flags[2], SIP_NAT_AUTO_RPORT | SIP_NAT_AUTO_COMEDIA); + + break; /* It doesn't make sense to have no + something else */ + } else if (!strcasecmp(option, "yes")) { + ast_log(LOG_WARNING, + "Option %s of '%s' is no longer supported at line %d, use nat=force_rport,comedia instead\n", + variable->name, option, variable->lineno); + } else if (!strcasecmp(option, "force_rport") && !ast_test_flag(&peer->flags[2], SIP_NAT_AUTO_RPORT)) { + ast_set_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT); + } else if (!strcasecmp(option, "comedia") && !ast_test_flag(&peer->flags[2], SIP_NAT_AUTO_COMEDIA)) { + ast_set_flag(&peer->flags[1], SIP_SYMMETRIC_RTP); + } else if (!strcasecmp(option, "auto_force_rport")) { + ast_set_flag(&peer->flags[2], SIP_NAT_AUTO_RPORT); + /* In case someone did something dumb like nat=force_rport,auto_force_rport */ + ast_clear_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT); + } else if (!strcasecmp(option, "auto_comedia")) { + ast_set_flag(&peer->flags[2], SIP_NAT_AUTO_COMEDIA); + /* In case someone did something dumb like nat=comedia,auto_comedia*/ + ast_clear_flag(&peer->flags[1], SIP_SYMMETRIC_RTP); + } + } + } else if (realtime && !strcasecmp(variable->name, "regseconds")) { + ast_get_time_t(variable->value, ®_seconds, 0, NULL); + } 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 (!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, md5secret, variable->value); + } else if (!strcasecmp(variable->name, "auth")) { + sip_auth_realm_build(&peer->auth_realms, variable->value, variable->lineno); + } else if (!strcasecmp(variable->name, "callerid")) { + char caller_name[80], caller_number[80]; + + 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, "mwi_from")) { + ast_string_field_set(peer, mwi_from, variable->value); + } else if (!strcasecmp(variable->name, "cid_name")) { + ast_string_field_set(peer, caller_name, variable->value); + } else if (!strcasecmp(variable->name, "cid_number")) { + ast_string_field_set(peer, caller_number, variable->value); + } else if (!strcasecmp(variable->name, "cid_tag")) { + ast_string_field_set(peer, caller_tag, variable->value); + } else if (!strcasecmp(variable->name, "context")) { + ast_string_field_set(peer, context, variable->value); + ast_set_flag(&peer->flags[1], SIP_HAVE_PEER_CONTEXT); + } else if (!strcasecmp(variable->name, "outofcall_message_context")) { + ast_string_field_set(peer, message_context, variable->value); + } else if (!strcasecmp(variable->name, "subscribecontext")) { + ast_string_field_set(peer, subscribe_context, variable->value); + } else if (!strcasecmp(variable->name, "fromdomain")) { + char *port; + + ast_string_field_set(peer, from_domain, variable->value); + + if ((port = strchr(peer->from_domain, ':'))) { + *port++ = '\0'; + + if (!(peer->from_domain_port = sip_str2port(port, 0))) { + ast_log(LOG_NOTICE, "Invalid %s port '%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else { + peer->from_domain_port = SIP_STANDARD_PORT; + } + } else if (!strcasecmp(variable->name, "usereqphone")) { + ast_set2_flag(&peer->flags[0], ast_true(variable->value), SIP_USER_EQ_PHONE); + } else if (!strcasecmp(variable->name, "fromuser")) { + ast_string_field_set(peer, from_user, variable->value); + } else if (!strcasecmp(variable->name, "outboundproxy")) { + struct sip_proxy *proxy; + + if (!(proxy = sip_proxy_build(variable->value, variable->lineno, peer->proxy))) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", + variable->name, variable->value, variable->lineno); + continue; + } + + ao2_t_cleanup(peer->proxy, "drop proxy"); + peer->proxy = proxy; + } else if (!strcasecmp(variable->name, "host")) { + if (!strcasecmp(variable->value, "dynamic")) { + /* They'll register with us */ + if ((!found && !ast_test_flag(&sip_config.flags[1], SIP_REALTIME_CACHE_PEERS)) || + !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_expire_sched_id, + ao2_t_cleanup(peer, "drop peer")); + + peer->host_dynamic = FALSE; + host = ast_strdupa(variable->value); + } + } else if (!strcasecmp(variable->name, "defaultip")) { + peer->default_address.ss.ss_family = AST_AF_UNSPEC; + + if (!ast_strlen_zero(variable->value) && ast_get_ip(&peer->default_address, variable->value)) { + ao2_t_cleanup(peer, "drop peer"); + return NULL; + } + } 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->acl, &ha_error, &subscribe_acl_change); + + if (ha_error) { + ast_log(LOG_ERROR, "Invalid %s '%s' at line %d, removing peer\n", + variable->name, variable->value, variable->lineno); + ao2_t_cleanup(peer, "drop peer"); + + 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 %s '%s' at line %d, removing peer\n", + variable->name, variable->value, variable->lineno); + ao2_t_cleanup(peer, "drop peer"); + + 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 %s '%s' at line %d, removing peer\n", + variable->name, variable->value, variable->lineno); + ao2_t_cleanup(peer, "drop peer"); + + return NULL; + } + } else if (!strcasecmp(variable->name, "port")) { + peer->port_in_uri = TRUE; + + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &port, SIP_STANDARD_PORT, 0, USHRT_MAX)) { + if (realtime) { + /* If stored as integer, could be 0 for some DBs (notably MySQL) */ + peer->port_in_uri = FALSE; + } else { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } + } else if (!strcasecmp(variable->name, "callingpres")) { + if ((peer->caller_presentation = ast_parse_caller_presentation(variable->value)) == -1) { + peer->caller_presentation = atoi(variable->value); + } + } else if (!strcasecmp(variable->name, "defaultuser")) { /* "username" is deprecated */ + ast_string_field_set(peer, username, variable->value); + } else if (!strcasecmp(variable->name, "tonezone")) { + struct ast_tone_zone *zone; + + if (!(zone = ast_get_indication_zone(variable->value))) { + ast_log(LOG_ERROR, "Invalid %s '%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } else { + ast_tone_zone_unref(zone); + ast_string_field_set(peer, zone, variable->value); + } + } else if (!strcasecmp(variable->name, "language")) { + ast_string_field_set(peer, language, variable->value); + } 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, "amaflags")) { + if ((peer->amaflags = ast_channel_string2amaflag(variable->value)) < 0) { + ast_log(LOG_WARNING, "Invalid %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_DEFAULT | PARSE_IN_RANGE, + &peer->max_forwards, sip_config.max_forwards, 1, 255)) { + ast_log(LOG_WARNING, "Invalid %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, "rtp_engine")) { + ast_string_field_set(peer, rtp_engine, variable->value); + } else if (!strcasecmp(variable->name, "mailbox")) { + sip_mailbox_build(peer, variable->value, variable->lineno); + } else if (!strcasecmp(variable->name, "subscribemwi")) { + ast_set2_flag(&peer->flags[1], ast_true(variable->value), SIP_SUBSCRIBE_MWI_ONLY); + } else if (!strcasecmp(variable->name, "vmexten")) { + 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")) { + ast_set2_flag(&peer->flags[1], ast_true(variable->value), SIP_ALLOW_TRANSFER); + } else if (!strcasecmp(variable->name, "allowsubscribe")) { + ast_set2_flag(&peer->flags[1], ast_true(variable->value), SIP_ALLOW_SUBSCRIBE); + } 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 %s codec '%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 %s codec '%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "preferred_codec_only")) { + ast_set2_flag(&peer->flags[1], ast_true(variable->value), SIP_PREFERRED_CODEC_ONLY); + } 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_DEFAULT | PARSE_IN_RANGE, + &peer->rtp_timeout, sip_config.rtp_timeout, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %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_DEFAULT | PARSE_IN_RANGE, + &peer->rtp_hold_timeout, sip_config.rtp_hold_timeout, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %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_DEFAULT | PARSE_IN_RANGE, + &peer->rtp_keepalive, sip_config.rtp_keepalive, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %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_DEFAULT | PARSE_IN_RANGE, + &peer->timer_t1, &sip_config.timer_t1, 200, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + + timert1_set = TRUE; + } else if (!strcasecmp(variable->name, "timerb")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &peer->timer_b, &sip_config.timer_b, 200, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s'%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + + timerb_set = TRUE; + } else if (!strcasecmp(variable->name, "setvar")) { + sip_variable_build(peer, variable->value, variable->lineno); + } else if (!strcasecmp(variable->name, "header")) { + char header[2048]; + + snprintf(header, sizeof(header), "__SIPADDHEADERpre%2d=%s", ++header_count, variable->value); + sip_variable_build(peer, header, variable->lineno); + } else if (!strcasecmp(variable->name, "qualifyfreq")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &peer->qualify_freq, sip_config.qualify_freq, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } else { + peer->qualify_freq *= 1000; + } + } else if (!strcasecmp(variable->name, "maxcallbitrate")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &peer->max_call_bitrate, sip_config.max_call_bitrate, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "directmedia") || !strcasecmp(variable->name, "canreinvite")) { + ast_clear_flag(&peer->flags[0], SIP_REINVITE); + + if (ast_true(variable->value)) { + ast_set_flag(&peer->flags[0], SIP_DIRECT_MEDIA | SIP_DIRECT_MEDIA_NAT); + } else if (!ast_false(variable->value)) { + char *direct_media, *option; + + direct_media = ast_strdupa(variable->value); + + while ((option = strsep(&direct_media, ","))) { + if (!strcasecmp(option, "update")) { + ast_set_flag(&peer->flags[0], SIP_REINVITE_UPDATE | SIP_DIRECT_MEDIA); + } else if (!strcasecmp(option, "nonat")) { + ast_set_flag(&peer->flags[0], SIP_DIRECT_MEDIA); + ast_clear_flag(&peer->flags[0], SIP_DIRECT_MEDIA_NAT); + } else if (!strcasecmp(option, "outgoing")) { + ast_set_flag(&peer->flags[0], SIP_DIRECT_MEDIA | SIP_DIRECT_MEDIA_OUTGOING); + } else { + ast_log(LOG_WARNING, "Invalid %s option '%s' at line %d\n", + variable->name, option, variable->lineno); + } + } + } + } else if (!strcasecmp(variable->name, "session-timers")) { + if (!strcasecmp(variable->value, "accept")) { + peer->stimer_mode = SIP_STIMER_MODE_ACCEPT; + } else if (!strcasecmp(variable->value, "refuse")) { + peer->stimer_mode = SIP_STIMER_MODE_REFUSE; + } else if (!strcasecmp(variable->value, "originate")) { + peer->stimer_mode = SIP_STIMER_MODE_ORIGINATE; + } else { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "session-expires")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &peer->stimer_max_se, sip_config.stimer_max_se, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "session-minse")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &peer->stimer_min_se, sip_config.stimer_min_se, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + + if (peer->stimer_min_se < sip_config.stimer_min_se) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d, not allowed to be < %ds\n", + variable->name, variable->value, variable->lineno, sip_config.stimer_min_se); + peer->stimer_min_se = sip_config.stimer_min_se; + } + } else if (!strcasecmp(variable->name, "session-refresher")) { + if (!strcasecmp(variable->value, "uac")) { + peer->stimer_refresher = SIP_STIMER_REFRESHER_UAC; + } else if (!strcasecmp(variable->value, "uas")) { + peer->stimer_refresher = SIP_STIMER_REFRESHER_UAS; + } else { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "disallowed_methods")) { + sip_parse_methods(&peer->disallowed_methods, variable->value); + } else if (!strcasecmp(variable->name, "unsolicited_mailbox")) { + ast_string_field_set(peer, unsolicited_mailbox, variable->value); + } else if (!strcasecmp(variable->name, "use_q850_reason")) { + ast_set2_flag(&peer->flags[1], ast_true(variable->value), SIP_Q850_REASON); + } else if (!strcasecmp(variable->name, "encryption")) { + ast_set2_flag(&peer->flags[0], ast_true(variable->value), SIP_USE_SRTP); + } else if (!strcasecmp(variable->name, "encryption_taglen")) { + ast_set2_flag(&peer->flags[2], !strcasecmp(variable->value, "32"), SIP_SRTP_TAG_32); + } else if (!strcasecmp(variable->name, "avpf")) { + ast_set2_flag(&peer->flags[2], ast_true(variable->value), SIP_USE_AVPF); + } else if (!strcasecmp(variable->name, "icesupport")) { + ast_set2_flag(&peer->flags[2], ast_true(variable->value), SIP_ICE_SUPPORT); + } else if (!strcasecmp(variable->name, "ignore_requested_pref")) { + ast_set2_flag(&peer->flags[2], ast_true(variable->value), SIP_IGNORE_OUTGOING_FORMAT); + } else if (!strcasecmp(variable->name, "videosupport")) { + if (!strcasecmp(variable->value, "always")) { + ast_set_flag(&peer->flags[1], SIP_VIDEO_SUPPORT_ALWAYS); + } else { + ast_set2_flag(&peer->flags[1], ast_true(variable->value), SIP_VIDEO_SUPPORT); + } + } else if (!strcasecmp(variable->name, "textsupport")) { + ast_set2_flag(&peer->flags[1], ast_true(variable->value), SIP_TEXT_SUPPORT); + } 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_set2_flag(&peer->flags[2], ast_true(variable->value), SIP_FORCE_AVP); + } else if (!strcasecmp(variable->name, "trustrpid")) { + ast_set2_flag(&peer->flags[0], ast_true(variable->value), SIP_TRUST_REMOTE_PARTY_ID); + } else if (!strcasecmp(variable->name, "supportpath")) { + ast_set2_flag(&peer->flags[0], ast_true(variable->value), SIP_USE_PATH); + } else if (!strcasecmp(variable->name, "sendrpid")) { + if (!strcasecmp(variable->value, "rpid")) { + ast_set_flag(&peer->flags[0], SIP_SEND_REMOTE_PARTY_ID_YES); + } else if (!strcasecmp(variable->value, "pai")) { + ast_set_flag(&peer->flags[0], SIP_SEND_REMOTE_PARTY_ID_PAI); + } else { + ast_set2_flag(&peer->flags[0], ast_true(variable->value), SIP_SEND_REMOTE_PARTY_ID_YES); + } + } 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, "trust_id_outbound")) { + ast_clear_flag(&peer->flags[1], SIP_TRUST_ID_OUTBOUND); + + if (ast_true(variable->value)) { + ast_set_flag(&peer->flags[1], SIP_TRUST_ID_OUTBOUND_YES); + } else if (ast_false(variable->value)) { + ast_set_flag(&peer->flags[1], SIP_TRUST_ID_OUTBOUND_NO); + } else { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", + variable->name, variable->value, 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, "dtmfmode")) { + ast_clear_flag(&peer->flags[0], SIP_DTMF); + + if (!strcasecmp(variable->value, "inband")) { + ast_set_flag(&peer->flags[0], SIP_DTMF_INBAND); + } else if (!strcasecmp(variable->value, "rfc2833")) { + ast_set_flag(&peer->flags[0], SIP_DTMF_RFC2833); + } else if (!strcasecmp(variable->value, "auto")) { + ast_set_flag(&peer->flags[0], SIP_DTMF_AUTO); + } else { + ast_log(LOG_WARNING, "Invalid %s '%s' on line %d, using rfc2833\n", + variable->name, variable->value, variable->lineno); + ast_set_flag(&peer->flags[0], SIP_DTMF_RFC2833); + } + } else if (!strcasecmp(variable->name, "allowoverlap")) { + ast_clear_flag(&peer->flags[1], SIP_ALLOW_OVERLAP); + + if (ast_true(variable->value)) { + ast_set_flag(&peer->flags[1], SIP_ALLOW_OVERLAP_YES); + } else if (!strcasecmp(variable->value, "dtmf")){ + ast_set_flag(&peer->flags[1], SIP_ALLOW_OVERLAP_DTMF); + } + } else if (!strcasecmp(variable->name, "progressinband")) { + ast_clear_flag(&peer->flags[0], SIP_PROGRESS_INBAND); + + if (ast_true(variable->value)) { + ast_set_flag(&peer->flags[0], SIP_PROGRESS_INBAND_YES); + } else if (!strcasecmp(variable->value, "never")) { + ast_set_flag(&peer->flags[0], SIP_PROGRESS_INBAND_NEVER); + } + } else if (!strcasecmp(variable->name, "promiscredir")) { + ast_set2_flag(&peer->flags[0], ast_true(variable->value), SIP_PROMISCUOUS_REDIRECT); + } else if (!strcasecmp(variable->name, "ignoresdpversion")) { + ast_set2_flag(&peer->flags[1], ast_true(variable->value), SIP_IGNORE_SDP_VERSION); + } else if (!strcasecmp(variable->name, "faxdetect")) { + if (ast_true(variable->value)) { + ast_set_flag(&peer->flags[1], SIP_FAX_DETECT_CNG | SIP_FAX_DETECT_T38); + } else if (ast_false(variable->value)) { + ast_clear_flag(&peer->flags[1], SIP_FAX_DETECT_CNG | SIP_FAX_DETECT_T38); + } else { + char *fax_detect, *option; + + fax_detect = ast_strdupa(variable->value); + + while ((option = strsep(&fax_detect, ","))) { + if (!strcasecmp(option, "cng")) { + ast_set_flag(&peer->flags[1], SIP_FAX_DETECT_CNG); + } else if (!strcasecmp(option, "t38")) { + ast_set_flag(&peer->flags[1], SIP_FAX_DETECT_T38); + } else { + ast_log(LOG_WARNING, "Invalid %s option '%s' at line %d\n", + variable->name, option, variable->lineno); + } + } + } + } else if (!strcasecmp(variable->name, "register")) { + if (!strcasecmp(peer->name, variable->value)) { + ast_log(LOG_WARNING, "Invalid %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")) { + ast_set2_flag(&peer->flags[2], ast_true(variable->value), SIP_DND_BUSY); + } else if (!strcasecmp(variable->name, "huntgroup_default")) { + ast_set2_flag(&peer->flags[2], ast_true(variable->value), SIP_HUNTGROUP_DEFAULT); + } else if (!strcasecmp(variable->name, "cisco_usecallmanager")) { + ast_set2_flag(&peer->flags[1], ast_true(variable->value), SIP_CISCO_USECALLMANAGER); + } else if (!strcasecmp(variable->name, "cisco_keep_conference")) { + ast_set2_flag(&peer->flags[2], ast_true(variable->value), SIP_CISCO_KEEP_CONFERENCE); + } else if (!strcasecmp(variable->name, "cisco_multiadmin_conference")) { + ast_set2_flag(&peer->flags[2], ast_true(variable->value), SIP_CISCO_MULTI_ADMIN_CONFERENCE); + } else if (!strcasecmp(variable->name, "cisco_pickupnotify_alert")) { + char *alert, *option; + + alert = ast_strdupa(variable->value); + + ast_clear_flag(&peer->flags[2], + SIP_CISCO_PICKUP_NOTIFY_FROM | SIP_CISCO_PICKUP_NOTIFY_TO | SIP_CISCO_PICKUP_NOTIFY_BEEP); + + while ((option = strsep(&alert, ","))) { + if (!strcasecmp(option, "none")) { + ast_clear_flag(&peer->flags[2], + SIP_CISCO_PICKUP_NOTIFY_FROM | SIP_CISCO_PICKUP_NOTIFY_TO | SIP_CISCO_PICKUP_NOTIFY_BEEP); + } else if (!strcasecmp(option, "from")) { + ast_set_flag(&peer->flags[2], SIP_CISCO_PICKUP_NOTIFY_FROM); + } else if (!strcasecmp(option, "to")) { + ast_set_flag(&peer->flags[2], SIP_CISCO_PICKUP_NOTIFY_TO); + } else if (!strcasecmp(option, "beep")) { + ast_set_flag(&peer->flags[2], SIP_CISCO_PICKUP_NOTIFY_BEEP); + } else { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", + variable->name, option, variable->lineno); + } + } + } else if (!strcasecmp(variable->name, "cisco_pickupnotify_timer")) { + if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &peer->cisco_pickup_notify_timer, 5, 0, 60)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "cisco_qrt_url")) { + ast_string_field_set(peer, cisco_qrt_url, 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 (!strcasecmp(variable->name, "rfc2833compensate")) { + ast_set2_flag(&peer->flags[0], ast_true(variable->value), SIP_RFC2833_COMPENSATE); + } else if (!strcasecmp(variable->name, "rtcp_mux")) { + ast_set2_flag(&peer->flags[2], ast_true(variable->value), SIP_RTCP_MUX); + } else if (!strcasecmp(variable->name, "t38pt_udptl")) { + char *udptl, *option; + + udptl = ast_strdupa(variable->value); + + while ((option = strsep(&udptl, ","))) { + if (ast_true(option) || !strcasecmp(option, "fec")) { + ast_clear_flag(&peer->flags[1], SIP_T38_SUPPORT); + ast_set_flag(&peer->flags[1], SIP_T38_SUPPORT_UDPTL_FEC); + } else if (!strcasecmp(option, "redundancy")) { + ast_clear_flag(&peer->flags[1], SIP_T38_SUPPORT); + ast_set_flag(&peer->flags[1], SIP_T38_SUPPORT_UDPTL_REDUNDANCY); + } else if (!strcasecmp(option, "none")) { + ast_clear_flag(&peer->flags[1], SIP_T38_SUPPORT); + ast_set_flag(&peer->flags[1], SIP_T38_SUPPORT_UDPTL); + } else if (!strncasecmp(option, "maxdatagram=", 12)) { + option += 12; + + if (ast_parse_arg(option, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &peer->t38_max_datagram, -1, -1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", + variable->name, variable->value, variable->lineno); + } + } + } + } else if (!strcasecmp(variable->name, "t38pt_usertpsource")) { + ast_set2_flag(&peer->flags[1], ast_true(variable->value), SIP_UDPTL_DESTINATION); + } + } + + /* These apply to devstate lookups */ + if (realtime && !strcasecmp(variable->name, "lastms")) { + sscanf(variable->value, "%30d", &peer->last_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, "fullcontact")) { + ast_string_field_set(peer, full_contact, variable->value); + } else if (!strcasecmp(variable->name, "qualify")) { + 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_DEFAULT | PARSE_IN_RANGE, + &peer->qualify_max, 0, 0, UINT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d, should be 'yes', 'no', or a number of milliseconds\n", + variable->name, variable->value, variable->lineno); + } + + if (realtime && !ast_test_flag(&sip_config.flags[1], SIP_REALTIME_CACHE_PEERS) && 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 turn rtcachefriends on or turn qualify off on peer '%s'\n", + variable->name, variable->value, variable->lineno, peer->name); + peer->qualify_max = 0; + } + } else if (!strcasecmp(variable->name, "keepalive")) { + if (!strcasecmp(variable->value, "no")) { + peer->keepalive = 0; + } else if (!strcasecmp(variable->value, "yes")) { + peer->keepalive = sip_config.keepalive; + } else if (ast_parse_arg(variable->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, + &peer->keepalive, 0, 0, UINT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d, should be 'yes', 'no', or a number of milliseconds\n", + variable->name, variable->value, variable->lineno); + } + } else if (!strcasecmp(variable->name, "callcounter")) { + peer->call_limit = ast_true(variable->value) ? INT_MAX : 0; + } else if (!strcasecmp(variable->name, "call-limit")) { + peer->call_limit = atoi(variable->value); + + if (peer->call_limit < 0) { + peer->call_limit = 0; + } + } else if (!strcasecmp(variable->name, "busylevel")) { + peer->busy_level = atoi(variable->value); + + if (peer->busy_level < 0) { + peer->busy_level = 0; + } + } + } + + if (!devstate_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->aliases, alias, next) { + if (alias->removed) { + AST_LIST_REMOVE_CURRENT(next); + sip_alias_destroy(alias); + } + } + 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; + } + + /* 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) { + if (timerb_set && timert1_set) { + ast_log(LOG_WARNING, "Timer B has been set lower than recommended for peer %s (%d < 64 * timert1=%d)\n", + peer->name, peer->timer_b, peer->timer_t1); + } else if (timerb_set) { + if ((peer->timer_t1 = peer->timer_b / 64) < sip_config.t1_min) { + ast_log(LOG_WARNING, "Timer B has been set lower than recommended (%d < 64 * timert1=%d)\n", + peer->timer_b, peer->timer_t1); + + peer->timer_t1 = sip_config.t1_min; + peer->timer_b = peer->timer_t1 * 64; + } + + peer->timer_t1 = peer->timer_b / 64; + } else { + peer->timer_b = peer->timer_t1 * 64; + } + } + + if (!peer->default_outgoing_transport) { + /* Set default set of transports */ + peer->transports = sip_config.transports; + /* Set default primary transport */ + peer->default_outgoing_transport = sip_config.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_outgoing_transport && peer->register_expire_sched_id == -1) || + !(peer->socket.transport & peer->transports) || !peer->socket.transport) { + sip_socket_set_transport(&peer->socket, peer->default_outgoing_transport); + } + + if (realtime && !ast_strlen_zero(peer->full_contact)) { + peer->realtime_from_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 ((!ast_test_flag(&peer->flags[2], SIP_NAT_AUTO_RPORT) && !ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT)) || + ast_sockaddr_isnull(&peer->address)) { + sip_get_address(peer->full_contact, &peer->address); + } + } + + if (!ast_strlen_zero(host) && peer->dnsmgr == NULL) { + char transport[MAXHOSTNAMELEN]; + + peer->address.ss.ss_family = AF_INET; + + snprintf(transport, sizeof(transport), "_%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 ? transport : NULL, + sip_peer_dnsmgr_lookup, ao2_t_bump(peer, "bump peer"))) { + ast_log(LOG_ERROR, "SRV lookup failed for host '%s' on peer %s, removing peer\n", host, peer->name); + + ao2_t_cleanup(peer, "drop peer"); + ao2_t_cleanup(peer, "drop peer"); + + return NULL; + } + + if (!peer->dnsmgr) { + /* dnsmgr refresh disabled, release reference */ + ao2_t_cleanup(peer, "drop peer"); + } + + ast_string_field_set(peer, host, host); + + if (sip_config.dynamic_exclude_static && !ast_sockaddr_isnull(&peer->address)) { + int ha_error = 0; + + ast_append_acl("deny", ast_sockaddr_stringify_addr(&peer->address), &sip_config.contact_acl, &ha_error, NULL); + + if (ha_error) { + ast_log(LOG_ERROR, "Invalid or unresolved host/IP entry in configuration for peer %s, cannot add to contact ACL\n", + peer->name); + } + } + } 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 (port && !realtime && peer->host_dynamic) { + ast_sockaddr_set_port(&peer->default_address, port); + } else 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_port(&peer->default_address)) { + ast_sockaddr_set_port(&peer->default_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 = !!ast_test_flag(&peer->flags[2], SIP_HUNTGROUP_DEFAULT); + } + } + + if (!devstate_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_expiry && peer->host_dynamic && (time(NULL) - reg_seconds) > 0) { + sip_peer_astdb_delete(peer); + ast_sockaddr_setnull(&peer->address); + + peer->last_qualify = -1; + ast_debug(1, "Peer '%s' 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. */ + AST_SCHED_REPLACE_UNREF(peer->qualify_expire_sched_id, sip_sched_context, 0, /* Poke the peer ASAP */ + sip_peer_qualify_now, peer, + ao2_t_cleanup(_data, "drop peer"), + ao2_t_cleanup(peer, "drop peer"), + ao2_t_bump(peer, "bump peer")); + } + } else { + /* Don't qualify peer immediately, just schedule it within qualifyfreq */ + AST_SCHED_REPLACE_UNREF(peer->qualify_expire_sched_id, sip_sched_context, + ast_random() % (peer->qualify_freq ? peer->qualify_freq : sip_config.qualify_freq) + 1, sip_peer_qualify, peer, + ao2_t_cleanup(_data, "drop peer"), + ao2_t_cleanup(peer, "drop peer"), + ao2_t_bump(peer, "bump peer")); + } + } + + /* If they didn't request that MWI is sent *only* on subscribe, go ahead and subscribe to it now. */ + if (!devstate_only && !ast_test_flag(&peer->flags[1], SIP_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 (!devstate_only && ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER) && !AST_LIST_EMPTY(&peer->subscriptions)) { + sip_peer_update_subscriptions(peer); + } + + peer->removed = FALSE; + + ast_free_acl_list(old_acl); + ast_free_acl_list(old_contact_acl); + ast_free_acl_list(old_direct_media_acl); + + /* If an ACL change subscription is needed and doesn't exist, we need one. */ + if (subscribe_acl_change) { + sip_acl_change_subscribe(); + } + + return peer; +} + +/* Determine if a correct transport is being used to contact a peer */ +int sip_peer_check_transport(struct sip_peer *peer, int transport) +{ + if (peer->socket.transport == transport) { + return 0; + } + + if (!(peer->transports & transport)) { + ast_log(LOG_ERROR, "'%s' is not a valid transport for '%s'. we only use '%s', hanging up call\n", + ast_transport2str(transport), peer->name, sip_transports2str(peer->transports)); + return -1; + } + + if (peer->socket.transport & AST_TRANSPORT_TLS) { + ast_log(LOG_WARNING, "Peer '%s' has not used TLS in favor of '%s' but this was allowed\n", + peer->name, ast_transport2str(transport)); + } else { + ast_debug(1, "peer '%s' has contacted us over %s even though we prefer %s\n", + peer->name, ast_transport2str(transport), ast_transport2str(peer->socket.transport)); + } + + return 0; +} + +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)) { + ast_debug(1, "Empty sockaddr change, ignorig\n"); + return; + } + + if (!ast_sockaddr_isnull(&peer->address)) { + ao2_unlink(sip_peers_by_address, 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_peers_by_address, peer); +} + +/* 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 10th second 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_expire_sched_id, ao2_t_cleanup(peer, "drop peer")); + + peer->last_qualify = 0; + + if (peer->qualify_dialog) { + ao2_t_cleanup(peer->qualify_dialog, "drop dialog"); + 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_t_cleanup(peer->qualify_dialog, "drop dialog"); + + peer->qualify_dialog = NULL; + } + + if (!(dialog = sip_dialog_alloc(NULL, &peer->socket, FALSE, SIP_METHOD_OPTIONS, NULL, 0))) { + return -1; + } + + /* No socket transport means that the peer may have been seeded from database */ + peer->qualify_dialog = ao2_t_bump(dialog, "bump dialog"); + + ast_copy_flags(&dialog->flags[0], &peer->flags[0], SIP_FLAGS0_MASK); + ast_copy_flags(&dialog->flags[1], &peer->flags[1], SIP_FLAGS1_MASK); + ast_copy_flags(&dialog->flags[2], &peer->flags[2], SIP_FLAGS2_MASK); + + 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_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->full_contact)) { + ast_string_field_set(dialog, full_contact, peer->full_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_get_our_address(dialog->socket.transport, &dialog->address, &dialog->our_address); + + AST_SCHED_DEL_UNREF(sip_sched_context, peer->qualify_expire_sched_id, ao2_t_cleanup(peer, "drop peer")); + + if (dialog->peer) { + ao2_t_cleanup(dialog->peer, "drop peer"); + } + + dialog->peer = ao2_t_bump(peer, "bump peer"); + + ast_set_flag(&dialog->flags[0], SIP_OUTGOING); + peer->qualify_start = ast_tvnow(); + + /* sinks the dialog refcount */ + if (sip_send_options(dialog)) { + /* Immediately unreachable, network problems */ + sip_peer_qualify_timeout(ao2_t_bump(peer, "bump peer")); + } else if (!force) { + AST_SCHED_REPLACE_UNREF(peer->qualify_expire_sched_id, sip_sched_context, peer->qualify_max * 2, + sip_peer_qualify_timeout, peer, + ao2_t_cleanup(_data, "drop peer"), + ao2_t_cleanup(peer, "drop peer"), + ao2_t_bump(peer, "bump peer")); + } + + ao2_t_cleanup(dialog, "drop dialog"); + + 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_expire_sched_id = -1; + + if (peer->last_qualify > -1) { + ast_verb(3, "SIP peer '%s' is now unreachable (%dms)\n", peer->name, peer->last_qualify); + + if (sip_config.realtime_update_peer) { + ast_update_realtime("sippeers", "name", peer->name, "lastms", "-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_t_cleanup(peer->qualify_dialog, "drop dialog"); + + peer->qualify_dialog = NULL; + } + + /* Don't send a devstate change if nothing changed. */ + if (peer->last_qualify > -1) { + peer->last_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->last_qualify == -1) { + continue; + } + + ast_log(LOG_NOTICE, "SIP peer '%s' is now unreachable (%dms)\n", alias->peer->name, alias->peer->last_qualify); + + manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", + "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unreachable\r\nTime: %d\r\n", alias->peer->name, -1); + + alias->peer->last_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_expire_sched_id, sip_sched_context, SIP_QUALIFY_FREQ_NOT_OK, sip_peer_qualify, peer, + ao2_t_cleanup(_data, "drop peer"), + ao2_t_cleanup(peer, "drop peer"), + ao2_t_bump(peer, "bump peer")); + + /* Release the ref held by the running scheduler entry */ + ao2_t_cleanup(peer, "drop peer"); + + 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, *qualify_peer; + + peer = (struct sip_peer *) data; + peer->qualify_expire_sched_id = -1; + + if (!(qualify_peer = ao2_t_find(sip_peers, peer, OBJ_SEARCH_OBJECT, "bump peer"))) { + ao2_t_cleanup(peer, "drop peer"); + return 0; + } + + if (qualify_peer->name != peer->name) { + ao2_t_cleanup(qualify_peer, "drop peer"); + ao2_t_cleanup(peer, "drop peer"); + + return 0; + } + + ao2_t_cleanup(qualify_peer, "drop peer"); + sip_peer_send_qualify(peer, FALSE); + ao2_t_cleanup(peer, "drop peer"); + + 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_static_count) { /* No peers, just give up */ + return; + } + + count = 0; + when = 0; + + iter = ao2_iterator_init(sip_peers, 0); + + while ((peer = ao2_t_iterator_next(&iter, "bump peer"))) { + ao2_lock(peer); + + /* Only qualify the primary line */ + if (peer->cisco_line_index > 1) { + ao2_unlock(peer); + continue; + } + + /* Don't schedule poking on a peer without qualify */ + 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_expire_sched_id, sip_sched_context, when, sip_peer_qualify, peer, + ao2_t_cleanup(_data, "drop peer"), + ao2_t_cleanup(peer, "drop peer"), + ao2_t_bump(peer, "bump peer")); + } + + ao2_unlock(peer); + ao2_t_cleanup(peer, "drop peer"); + } + + ao2_iterator_destroy(&iter); +} + +int sip_peer_qualify_now(const void *data) +{ + struct sip_peer *peer = (struct sip_peer *) data; + + peer->qualify_expire_sched_id = -1; + + sip_peer_send_qualify(peer, FALSE); + ao2_t_cleanup(peer, "drop peer"); + + return 0; +} + +/* Send keep alive packet to peer */ +static int sip_peer_keepalive(const void *data) +{ + struct sip_peer *peer; + int res; + + peer = (struct sip_peer *) data; + peer->keepalive_sched_id = -1; + + if (!peer->keepalive || ast_sockaddr_isnull(&peer->address)) { + ao2_t_cleanup(peer, "drop peer"); + return 0; + } + + res = 0; + + /* Send the packet out using the proper method for this peer */ + if ((peer->socket.fd != -1) && (peer->socket.transport == AST_TRANSPORT_UDP)) { + res = ast_sendto(peer->socket.fd, "\r\n", 2, 0, &peer->address); + } else if ((peer->socket.transport & (AST_TRANSPORT_TCP | AST_TRANSPORT_TLS)) && peer->socket.tcptls_session) { + res = sip_tcptls_session_write(peer->socket.tcptls_session, "\r\n"); + + if (res < -1) { + return 0; + } + } + + if (res == -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 can't be reached */ + case ENETDOWN: /* Interface down */ + case ENETUNREACH: /* Network failure */ + case ECONNREFUSED: /* ICMP port unreachable */ + res = -1; /* Don't bother with trying to transmit again */ + break; + } + } + + if (res != 2) { + ast_log(LOG_WARNING, "Sendto %s returned %d: %s\n", ast_sockaddr_stringify(&peer->address), res, strerror(errno)); + } + + AST_SCHED_REPLACE_UNREF(peer->keepalive_sched_id, sip_sched_context, peer->keepalive * 1000, sip_peer_keepalive, peer, + ao2_t_cleanup(_data, "drop peer"), + ao2_t_cleanup(peer, "drop peer"), + ao2_t_bump(peer, "bump peer")); + + ao2_t_cleanup(peer, "drop peer"); + + return 0; +} + +/* Send a keepalive to all known peers */ +void sip_peer_keepalive_all(void) +{ + struct ao2_iterator iter; + struct sip_peer *peer; + + if (!sip_static_count) { /* No peers, just give up */ + return; + } + + iter = ao2_iterator_init(sip_peers, 0); + + while ((peer = ao2_t_iterator_next(&iter, "bump peer"))) { + ao2_lock(peer); + + AST_SCHED_REPLACE_UNREF(peer->keepalive_sched_id, sip_sched_context, 0, sip_peer_keepalive, peer, + ao2_t_cleanup(_data, "drop peer"), + ao2_t_cleanup(peer, "drop peer"), + ao2_t_bump(peer, "bump peer")); + + ao2_unlock(peer); + ao2_t_cleanup(peer, "drop peer"); + } + + ao2_iterator_destroy(&iter); +} + +/* 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 *next_name, *name; + struct sip_mailbox *mailbox; + + next_name = ast_strdupa(config); + + while ((name = strsep(&next_name, ","))) { + /* 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); +} + +/* Destroy all peer-related mailbox subscriptions */ +static void sip_mailbox_destroy_all(struct sip_peer *peer) +{ + struct sip_mailbox *mailbox; + + /* Lock the peer while accessing/updating the linked list but NOT while destroying the mailbox */ + while (!AST_LIST_EMPTY(&peer->mailboxes)) { + ao2_lock(peer); + mailbox = AST_LIST_REMOVE_HEAD(&peer->mailboxes, next); + ao2_unlock(peer); + + sip_mailbox_destroy(mailbox); + } +} + +static void sip_alias_build(struct sip_peer *peer, const char *config, int lineno, int *line_index) +{ + char *next_alias, *name; + struct sip_alias *alias; + + next_alias = ast_strdupa(config); + + while ((name = strsep(&next_alias, ","))) { + name = ast_strip(name); + + if (ast_strlen_zero(name)) { + 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->last_qualify = 0; + + if (alias->peer->socket.tcptls_session) { + ao2_ref(alias->peer->socket.tcptls_session, -1); + } + + ast_string_field_set(alias->peer, full_contact, ""); + ast_string_field_set(alias->peer, username, ""); + ast_string_field_set(alias->peer, useragent, ""); + + if (!ast_sockaddr_isnull(&alias->peer->address)) { + manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", + "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", + alias->peer->name); + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); + + ast_sockaddr_setnull(&alias->peer->address); + } + + ao2_t_cleanup(alias->peer, "drop peer"); + } + + ast_free(alias->name); + ast_free(alias); +} + +static void sip_alias_destroy_all(struct sip_peer *peer) +{ + struct sip_alias *alias; + + while ((alias = AST_LIST_REMOVE_HEAD(&peer->aliases, next))) { + sip_alias_destroy(alias); + } +} + +static void sip_subscription_build(struct sip_peer *peer, const char *config, int lineno) +{ + char *next_exten, *exten, *context; + struct sip_subscription *subscription; + + next_exten = ast_strdupa(config); + + while ((exten = strsep(&next_exten, ","))) { + if ((context = strchr(exten, '@'))) { + *context++ = '\0'; + } else { + context = ast_strdupa(S_OR(peer->subscribe_context, peer->context)); + } + + exten = ast_strip(exten); + + if (ast_strlen_zero(exten) || ast_strlen_zero(context)) { + 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_t_cleanup(subscription->dialog, "drop dialog"); + } + + ast_string_field_free_memory(subscription); + ast_free(subscription); +} + +/* Destroy all peer-related extension state subscriptions */ +static void sip_subscription_destroy_all(struct sip_peer *peer) +{ + struct sip_subscription *subscription; + + while ((subscription = AST_LIST_REMOVE_HEAD(&peer->subscriptions, next))) { + sip_subscription_destroy(subscription); + } +} diff -durN asterisk-22.3.0.orig/channels/sip/pickup.c asterisk-22.3.0/channels/sip/pickup.c --- asterisk-22.3.0.orig/channels/sip/pickup.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/pickup.c 2025-04-17 11:11:22.818703089 +1200 @@ -0,0 +1,346 @@ +/* + * 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/request.h" +#include "include/stimer.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; + time_t now; +}; + +static void *sip_pickup_thread(void *data); +static int sip_pickup_notify_peer(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 id 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.notify_callerid == SIP_NOTIFY_CALLERID_IGNORE_CONTEXT ? "PICKUPMARK" : context); + 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_peer(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 (!ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER) || + !ast_test_flag(&peer->flags[2], + SIP_CISCO_PICKUP_NOTIFY_FROM | SIP_CISCO_PICKUP_NOTIFY_TO | SIP_CISCO_PICKUP_NOTIFY_BEEP)) { + 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 (pickup_args->now - peer->cisco_pickup_notify_sent > peer->cisco_pickup_notify_timer) { + peer->cisco_pickup_notify_sent = pickup_args->now; + ao2_unlock(peer); + + return CMP_MATCH; + } + } + + ao2_unlock(peer); + + return 0; +} + +static void *sip_pickup_notify_thread(void *data) +{ + char *device, name[AST_CHANNEL_NAME], *caller_number, *connected_number; + struct ast_channel_iterator *channel_iter; + struct ast_channel *pickup_channel, *channel; + struct timeval creation; + 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 = (char * ) data; + + snprintf(name, sizeof(name), "%s-", (char *) device); + channel_iter = ast_channel_iterator_by_name_new(name, strlen(name)); + + ast_free(device); + + pickup_channel = NULL; + creation = 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) > 0) { + if (pickup_channel) { + ast_channel_unref(pickup_channel); + } + + pickup_channel = ast_channel_ref(channel); + creation = 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.now = time(NULL); + 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_peer, &pickup_args))) { + ast_log(LOG_ERROR, "Unable to create iterator for peers container in sip_pickup_notify_thread\n"); + 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, FALSE, SIP_METHOD_REFER, NULL, 0)))) { + ao2_t_cleanup(peer, "drop peer"); + continue; + } + + sip_socket_set_transport(&dialog->socket, 0); + + if (sip_dialog_build_from_peer(dialog, peer)) { + sip_dialog_unlink(dialog); + + ao2_t_cleanup(dialog, "drop dialog"); + ao2_t_cleanup(peer, "drop peer"); + + continue; + } + + ast_str_reset(content); + + if (ast_test_flag(&peer->flags[2], SIP_CISCO_PICKUP_NOTIFY_FROM | SIP_CISCO_PICKUP_NOTIFY_TO)) { + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "notify_display\n"); + ast_str_append(&content, 0, ""); + + if (ast_test_flag(&peer->flags[2], SIP_CISCO_PICKUP_NOTIFY_FROM)) { + ast_str_append(&content, 0, "From %s", connected_number); + } + + if (ast_test_flag(&peer->flags[2], SIP_CISCO_PICKUP_NOTIFY_TO)) { + ast_str_append(&content, 0, "%s %s", + ast_test_flag(&peer->flags[2], SIP_CISCO_PICKUP_NOTIFY_FROM) ? " to" : "To", caller_number); + } + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%d\n", peer->cisco_pickup_notify_timer); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "1\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + } + + if (ast_test_flag(&peer->flags[2], SIP_CISCO_PICKUP_NOTIFY_BEEP)) { + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "DtZipZip\n"); + ast_str_append(&content, 0, "all\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + } + + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + sip_send_refer_with_content(dialog, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + + ao2_t_cleanup(dialog, "drop dialog"); + ao2_t_cleanup(peer, "drop peer"); + } + + 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 *sub, struct stasis_message *msg) +{ + struct ast_device_state_message *device_state; + char *device; + pthread_t threadid; + + if (stasis_message_type(msg) != ast_device_state_message_type()) { + return; + } + + device_state = stasis_message_data(msg); + + if (device_state->state != AST_DEVICE_RINGING) { + return; + } + + if (!(device = ast_strdup(device_state->device))) { + return; + } + + if (ast_pthread_create_detached_background(&threadid, NULL, sip_pickup_notify_thread, device)) { + ast_free(device); + } +} diff -durN asterisk-22.3.0.orig/channels/sip/proxy.c asterisk-22.3.0/channels/sip/proxy.c --- asterisk-22.3.0.orig/channels/sip/proxy.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/proxy.c 2025-04-17 11:11:22.818703089 +1200 @@ -0,0 +1,169 @@ +/* + * 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/request.h" +#include "include/proxy.h" +#include "include/stimer.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 *host, *port, *sep; + int force; + + if (ast_strlen_zero(config)) { + ast_log(LOG_WARNING, "Empty outboundproxy on line %d\n", lineno); + return NULL; + } + + /* Format is: [transport://]host[:port][,force] */ + host = ast_strdupa(config); + + if ((sep = strchr(host, ','))) { + *sep++ = '\0'; + force = !strcasecmp(sep, "force"); + } else { + force = FALSE; + } + + if (!strncasecmp(host, "tcp://", 5)) { + transport = AST_TRANSPORT_TCP; + } else if (!strncasecmp(host, "tls://", 5)) { + transport = AST_TRANSPORT_TLS; + } else if (!strncasecmp(host, "udp://", 5)) { + transport = AST_TRANSPORT_UDP; + } else { + if (strstr(host, "://")) { + ast_log(LOG_NOTICE, "Invalid transport for '%s' on line %d\n", config, lineno); + return NULL; + } + + transport = AST_TRANSPORT_UDP; + } + + if (!ast_sockaddr_split_hostport(host, &host, &port, 0)) { + ast_log(LOG_WARNING, "Invalid host '%s' on line %d\n", config, lineno); + return NULL; + } + + if (!ast_strlen_zero(port) && !atoi(port)) { + ast_log(LOG_WARNING, "Invalid port '%s' on line %d\n", config, lineno); + return NULL; + } + + if (!proxy) { + if (!(proxy = ao2_t_alloc(sizeof(*proxy), NULL, "alloc proxy"))) { + return NULL; + } + } + + proxy->transport = transport; + ast_copy_string(proxy->host, host, sizeof(proxy->host)); + proxy->port = sip_str2port(port, transport = AST_TRANSPORT_TLS ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + proxy->force = force; + + /* 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)) { + /* Ok, not an IP address, then let's check if it's a domain or host */ + /* XXX Todo - if we have proxy port, don't do SRV */ + proxy->address.ss.ss_family = AF_INET; + + if (ast_get_ip_or_srv(&proxy->address, proxy->host, sip_config.srv_lookup ? "_sip._udp" : NULL) < 0) { + ast_log(LOG_WARNING, "Unable to locate host '%s'\n", proxy->host); + return FALSE; + } + } + + ast_sockaddr_set_port(&proxy->address, proxy->port); + proxy->last_dns_update = time(NULL); + + 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 outboundproxy + * 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"); + sip_history_append(dialog, "OBproxy", "Using dialplan obproxy %s", dialog->proxy->host); + + return dialog->proxy; + } + + if (peer && peer->proxy) { + ast_debug(1, "Applying peer outbound proxy to this call\n"); + sip_history_append(dialog, "OBproxy", "Using peer obproxy %s", peer->proxy->host); + + return peer->proxy; + } + + if (!ast_strlen_zero(sip_config.proxy.host)) { + ast_debug(1, "Applying global outbound proxy to this call\n"); + sip_history_append(dialog, "OBproxy", "Using global outbound proxy %s", sip_config.proxy.host); + + return &sip_config.proxy; + } + + ast_debug(1, "Not applying outbound proxy to this call\n"); + + return NULL; +} diff -durN asterisk-22.3.0.orig/channels/sip/realtime.c asterisk-22.3.0/channels/sip/realtime.c --- asterisk-22.3.0.orig/channels/sip/realtime.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/realtime.c 2025-04-17 11:11:22.819703063 +1200 @@ -0,0 +1,219 @@ +/* + * 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/netsock.h" +#include "include/route.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/stimer.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)); + + /* We're not finding this peer by this name anymore. Reset it. */ + *name = NULL; + + /* 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 newpeername 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 devstate_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, devstate_only))) { + ast_variables_destroy(variables); + return NULL; + } + + ast_debug(3, "Realtime peer %s loaded from database to memory. Realtime peer objects: %d\n", peer->name, sip_realtime_count); + + if (ast_test_flag(&sip_config.flags[1], SIP_REALTIME_CACHE_PEERS) && !devstate_only) { + /* Cache peer */ + ast_copy_flags(&peer->flags[1], &sip_config.flags[1], SIP_REALTIME_AUTO_CLEAR | SIP_REALTIME_CACHE_PEERS); + + if (ast_test_flag(&sip_config.flags[1], SIP_REALTIME_AUTO_CLEAR)) { + AST_SCHED_REPLACE_UNREF(peer->register_expire_sched_id, sip_sched_context, + sip_config.realtime_auto_clear * 1000, sip_peer_expire_register, peer, + ao2_t_cleanup(_data, "drop peer"), + ao2_t_cleanup(peer, "drop peer"), + ao2_t_bump(peer, "bump peer")); + } + + ao2_t_link(sip_peers, peer, "link peer"); + + if (!ast_sockaddr_isnull(&peer->address)) { + ao2_t_link(sip_peers_by_address, peer, "link 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, int expiry) +{ + char port[6], address[INET6_ADDRSTRLEN], last_qualify[20], reg_seconds[20], *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(last_qualify, sizeof(last_qualify), "%d", peer->last_qualify); + snprintf(reg_seconds, sizeof(reg_seconds), "%d", (int) (time(NULL) + expiry)); /* Expiration time */ + + 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 = "regserver"; + } + + ast_update_realtime("sippeers", "name", peer->name, "ipaddr", address, "port", port, + "regseconds", reg_seconds, "useragent", peer->useragent, "lastms", last_qualify, "defaultuser", peer->username, + system_label, system_name, SENTINEL); + + if (peer->full_contact) { + ast_update_realtime("sippeers", "name", peer->name, "fullcontact", peer->full_contact, SENTINEL); + } + + if (sip_config.realtime_save_path) { + struct ast_str *path; + + path = sip_route_list(&peer->path, FALSE, 0); + + if (path) { + ast_update_realtime("sippeers", "name", peer->name, "path", ast_str_buffer(path), SENTINEL); + ast_free(path); + } + } +} diff -durN asterisk-22.3.0.orig/channels/sip/recording.c asterisk-22.3.0/channels/sip/recording.c --- asterisk-22.3.0.orig/channels/sip/recording.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/recording.c 2025-04-17 11:11:22.819703063 +1200 @@ -0,0 +1,250 @@ +/* + * 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/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/stimer.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 (!ast_test_flag(&dialog->flags[2], SIP_CISCO_RECORDING)) { + 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_t_cleanup(target_dialog, "drop dialog"); + + 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_t_cleanup(target_dialog, "drop dialog"); + + 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); + + ast_set_flag(&dialog->flags[1], SIP_ONHOLD_INACTIVE); + ast_set_flag(&dialog->flags[2], recording_data->outgoing ? SIP_RELAY_NEAREND : SIP_RELAY_FAREND); + + /* We are abusing the onhold flags to set the inactive attribute in the SDP, bump the onhold counter because + * when recording starts the reinvite code will decrement onhold when those flags are cleared */ + if (dialog->peer) { + ast_atomic_fetchadd_int(&dialog->peer->onhold, +1); + } + + 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->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_t_cleanup(target_dialog, "drop dialog"); + + 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_t_cleanup(target_dialog, "drop dialog"); + + goto cleanup; + } + + ao2_lock(target_dialog); + + if (recording_data->outgoing) { + target_dialog->record_outgoing_dialog = ao2_t_bump(dialog, "bump dialog"); + } else { + target_dialog->record_incoming_dialog = ao2_t_bump(dialog, "bump dialog"); + } + + ao2_unlock(target_dialog); + ao2_t_cleanup(target_dialog, "drop dialog"); + + 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_t_cleanup(target_dialog->record_outgoing_dialog, "drop dialog"); + 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_t_cleanup(target_dialog->record_incoming_dialog, "drop dialog"); + target_dialog->record_incoming_dialog = NULL; + + ao2_unlock(target_dialog); + } + + ao2_t_cleanup(target_dialog, "drop dialog"); + + return 0; +} diff -durN asterisk-22.3.0.orig/channels/sip/registry.c asterisk-22.3.0/channels/sip/registry.c --- asterisk-22.3.0.orig/channels/sip/registry.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/registry.c 2025-04-17 11:11:22.820703036 +1200 @@ -0,0 +1,682 @@ +/* + * 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/netsock.h" +#include "include/route.h" +#include "include/request.h" +#include "include/proxy.h" +#include "include/registry.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" + +/* The register list: Other SIP proxies we register with and receive calls from */ +struct ao2_container *sip_registry; + +static void sip_registry_destroy(void *data); +static int __sip_registry_expires(const void *data); +static int __sip_registry_stop_timeout(const void *data); +static int __sip_registry_start_timeout(const void *data); +static int __sip_registry_unlink(const void *data); +static int sip_registry_unlink(void *data, void *arg, int flags); + +/* Convert registration state status to string */ +const char *sip_registry_state2str(int state) +{ + switch (state) { + case SIP_REGISTRY_FAILED: + return "Failed"; + case SIP_REGISTRY_UNREGISTERED: + return "Unregistered"; + case SIP_REGISTRY_REQUEST_SENT: + return "Request Sent"; + case SIP_REGISTRY_AUTHORIZATION_SENT: + return "Auth. Sent"; + case SIP_REGISTRY_REGISTERED: + return "Registered"; + case SIP_REGISTRY_REJECTED: + return "Rejected"; + case SIP_REGISTRY_TIMEOUT: + return "Registered"; /* Hidden state. We are renewing registration. */ + case SIP_REGISTRY_AUTHORIZATION_FAILED: + return "No Authentication"; + default: + break; + } + + return "Unknown"; +} + +int sip_registry_hash(const void *data, int flags) +{ + const char *config; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + struct sip_registry *registry = (struct sip_registry *) data; + + config = registry->config; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + config = (char *) data; + } else { + return 0; + } + + return ast_str_hash(config); +} + +int sip_registry_cmp(void *data, void *arg, int flags) +{ + struct sip_registry *registry; + const char *config; + + registry = (struct sip_registry *) data; + + if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_OBJECT) { + struct sip_registry *registry = (struct sip_registry *) arg; + + config = registry->config; + } else if ((flags & OBJ_SEARCH_MASK) == OBJ_SEARCH_KEY) { + config = (const char *) arg; + } else { + return 0; + } + + if (!strcmp(registry->config, config)) { + return CMP_MATCH | CMP_STOP; + } + + return 0; +} + +/* Create sip_registry object from register= line and link into registry container */ +int sip_registry_build(const char *config, int lineno) +{ + struct sip_registry *registry; + int port, port_len, domain_port, expiry; + enum ast_transport transport = AST_TRANSPORT_UDP; + char *parse, *sep, *peer_name, *username, *domain, *hostname, *secret, *auth_user, *exten; + + /* register => [peer_name?][transport://]user[@domain][:secret[:auth_user]]@host[:port][/exten][~expiry] */ + parse = ast_strdupa(config); + + /* first split on the hostname */ + if (!(sep = strrchr(parse, '@'))) { + ast_log(LOG_WARNING, "Missing host for '%s' at line %d\n", config, lineno); + return -1; + } + + *sep++ = '\0'; + hostname = sep; + + /* peer name and username */ + if ((sep = strchr(parse, '?'))) { + peer_name = parse; + *sep++ = '\0'; + username = sep; + } else { + username = parse; + peer_name = NULL; + } + + /* transport */ + if (!strncmp(username, "udp://", 6)) { + transport = AST_TRANSPORT_UDP; + username += 6; + } else if (!strncmp(username, "tcp://", 6)) { + transport = AST_TRANSPORT_TCP; + username += 6; + } else if (!strncmp(username, "tls://", 6)) { + transport = AST_TRANSPORT_TLS; + username += 6; + } else if (strstr(username, "://")) { + ast_log(LOG_WARNING, "Invalid transport for '%s' at line %d\n", config, lineno); + return -1; + } else { + transport = AST_TRANSPORT_UDP; + } + + /* domain */ + if ((sep = strchr(username, '@'))) { + *sep++ = '\0'; + domain = sep; + + /* domain port */ + if (sep && (sep = strchr(sep, ':'))) { + *sep++ = '\0'; + + /* This will break if the secret is all digits, user would have to specify the port */ + if (sscanf(sep, "%30d%n", &domain_port, &port_len) == 1) { + sep += port_len; + } + } + } else { + sep = username; + domain = NULL; + domain_port = 0; + } + + /* secret */ + if (sep && (sep = strchr(sep, ':'))) { + *sep++ = '\0'; + secret = sep; + } else { + secret = NULL; + } + + /* auth user */ + if (sep && (sep = strchr(sep, ':'))) { + *sep++ = '\0'; + auth_user = sep; + } else { + auth_user = NULL; + } + + /* hostname port */ + if ((sep = strchr(hostname, ':'))) { + *sep++ = '\0'; + + if (sscanf(sep, "%30d%n", &port, &port_len) != 1) { + ast_log(LOG_WARNING, "Invalid port for '%s' at line %d\n", config, lineno); + return -1; + } + + sep += port_len; + } else { + sep = hostname; + port = 0; + } + + /* exten */ + if ((sep = strchr(sep, '/'))) { + *sep++ = '\0'; + exten = sep; + } else { + sep = hostname; + exten = NULL; + } + + /* expiry */ + if ((sep = strchr(sep, '~'))) { + *sep++ = '\0'; + + if (sscanf(sep, "%30d", &expiry) != 1) { + ast_log(LOG_WARNING, "Invalid expiry for '%s' at line %d\n", sep, lineno); + return -1; + } + } else { + expiry = 0; + } + + if (ast_strlen_zero(username) || ast_strlen_zero(hostname)) { + ast_log(LOG_WARNING, + "Format for registration is [peer_name?][transport://]user[@domain][:secret[:auth_user]]@host[:port][/exten][~expiry] at line %d\n", + lineno); + return -1; + } + + if ((registry = ao2_t_find(sip_registry, config, OBJ_SEARCH_KEY, "bump registry"))) { + ao2_t_ref(registry, -1, "drop registry"); + return 0; + } + + if (!(registry = ao2_t_alloc(sizeof(*registry), sip_registry_destroy, "alloc registry"))) { + return -1; + } + + if (ast_string_field_init(registry, 256)) { + ao2_t_ref(registry, -1, "drop registry"); + return -1; + } + + if (!port) { + if (transport == AST_TRANSPORT_TLS) { + port = SIP_STANDARD_TLS_PORT; + } else { + port = SIP_STANDARD_PORT; + } + } + + ast_string_field_set(registry, config, config); + ast_string_field_set(registry, peer_name, peer_name); + ast_string_field_set(registry, username, username); + ast_string_field_set(registry, domain, domain); + ast_string_field_set(registry, auth_user, auth_user); + ast_string_field_set(registry, secret, secret); + ast_string_field_set(registry, hostname, hostname); + ast_string_field_set(registry, exten, exten); + + registry->transport = transport; + registry->port = port; + registry->domain_port = domain_port; + + registry->expiry = expiry ? expiry : sip_config.default_expiry; + registry->default_expiry = registry->expiry; + registry->outgoing_expiry = registry->expiry; + + registry->expire_sched_id = -1; + registry->timeout_sched_id = -1; + + registry->valid_call_id = FALSE; + registry->outgoing_cseq = SIP_INITIAL_CSEQ; + + ao2_t_link(sip_registry, registry, "link registry"); + ao2_t_ref(registry, -1, "drop registry"); + + return 0; +} + +/* Run by the sched thread. */ +static int __sip_registry_unlink(const void *data) +{ + struct sip_registry *registry = (struct sip_registry *) data; + + ao2_lock(registry); + + if (registry->dialog) { + ast_debug(3, "Destroying active SIP dialog for registry %s@%s\n", registry->username, registry->hostname); + + /* This will also remove references to the registry */ + sip_dialog_unlink(registry->dialog); + ao2_t_cleanup(registry->dialog, "drop dialog"); + + registry->dialog = NULL; + } + + AST_SCHED_DEL_UNREF(sip_sched_context, registry->expire_sched_id, ao2_t_ref(registry, -1, "drop registry")); + AST_SCHED_DEL_UNREF(sip_sched_context, registry->timeout_sched_id, ao2_t_ref(registry, -1, "drop registry")); + + if (registry->dnsmgr) { + ast_dnsmgr_release(registry->dnsmgr); + registry->dnsmgr = NULL; + + ao2_t_ref(registry, -1, "drop registry"); + } + + ao2_unlock(registry); + ao2_t_ref(registry, -1, "drop registry"); + + return 0; +} + +/* Destroy registry object created with the register= statement in static configuration */ +static void sip_registry_destroy(void *data) +{ + struct sip_registry *registry = (struct sip_registry *) data; + + /* Really delete */ + ast_debug(3, "Destroying registry entry for %s@%s\n", registry->username, registry->hostname); + + if (registry->dialog) { + /* Clear registry before destroying to ensure we don't get reentered trying to grab the registry lock */ + ao2_t_replace(registry->dialog->registry, NULL, "drop registry"); + ast_debug(3, "Destroying active SIP dialog for registry %s@%s\n", registry->username, registry->hostname); + + sip_dialog_unlink(registry->dialog); + ao2_t_cleanup(registry->dialog, "drop dialog"); + } + + ast_string_field_free_memory(registry); +} + +static int sip_registry_unlink(void *data, void *arg, int flags) +{ + struct sip_registry *registry = (struct sip_registry *) data; + + ao2_t_ref(registry, +1, "bump registry"); + + if (ast_sched_add(sip_sched_context, 0, __sip_registry_unlink, registry) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(registry, -1, "drop registry"); + } + + return CMP_MATCH; +} + +void sip_registry_destroy_all(void) +{ + ao2_t_callback(sip_registry, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, sip_registry_unlink, NULL, "unlink registry"); +} + +/* Build SIP Call-ID value for a REGISTER transaction */ +void sip_registry_build_call_id(struct sip_registry *registry) +{ + ast_string_field_build(registry, call_id, "%08lx%08lx%08lx%08lx@%s", + (unsigned long) ast_random(), (unsigned long) ast_random(), (unsigned long) ast_random(), (unsigned long) ast_random(), + ast_sockaddr_stringify_host_remote(&sip_our_address)); +} + +/* Build SIP From tag value for REGISTER */ +void sip_registry_build_local_tag(struct sip_registry *registry) +{ + ast_string_field_build(registry, local_tag, "%08lx%08lx", (unsigned long) ast_random(), (unsigned long) ast_random()); +} + +/* Update registration with SIP Proxy. Called from the scheduler when the previous registration expires, so we don't have + * to cancel the pending event. We assume the reference so the sip_registry is valid, since it is stored in the scheduled + * event anyways. */ +int sip_registry_send(const void *data) +{ + struct sip_registry *registry = (struct sip_registry *) data; + + if (registry->dialog && registry->dialog->record_history) { + sip_history_append(registry->dialog, "RegistryRenew", "Account: %s@%s", registry->username, registry->hostname); + } + + /* Since registry's are only added/removed by the monitor thread, this + may be overkill to reference/dereference at all here */ + ast_debug(2, "Re-registration for %s@%s\n", registry->username, registry->hostname); + + registry->expire_sched_id = -1; + registry->expiry = registry->default_expiry; + + switch (registry->state) { + case SIP_REGISTRY_UNREGISTERED: + case SIP_REGISTRY_REQUEST_SENT: + case SIP_REGISTRY_AUTHORIZATION_SENT: + break; + case SIP_REGISTRY_REJECTED: + case SIP_REGISTRY_AUTHORIZATION_FAILED: + case SIP_REGISTRY_FAILED: + /* Restarting registration as unregistered */ + registry->state = SIP_REGISTRY_UNREGISTERED; + break; + case SIP_REGISTRY_TIMEOUT: + case SIP_REGISTRY_REGISTERED: + /* Registration needs to be renewed. */ + registry->state = SIP_REGISTRY_TIMEOUT; + break; + } + + sip_send_register(registry, FALSE); + ao2_t_ref(registry, -1, "drop registry"); + + return 0; +} + +void sip_registry_dnsmgr_lookup(struct ast_sockaddr *old_address, struct ast_sockaddr *new_address, void *data) +{ + struct sip_registry *registry; + const char *old_host, *new_host; + + registry = (struct sip_registry *) data; + + /* This shouldn't happen, but just in case */ + if (ast_sockaddr_isnull(new_address)) { + ast_debug(1, "Empty sockaddr change...ignoring!\n"); + return; + } + + if (!ast_sockaddr_port(new_address)) { + ast_sockaddr_set_port(new_address, registry->port); + } + + old_host = ast_strdupa(ast_sockaddr_stringify(old_address)); + new_host = ast_strdupa(ast_sockaddr_stringify(new_address)); + + ast_debug(1, "Changing registry %s from %s to %s\n", S_OR(registry->peer_name, registry->hostname), old_host, new_host); + ast_sockaddr_copy(®istry->address, new_address); +} + +/* Authenticate for outbound registration */ +int sip_registry_handle_authentication(struct sip_dialog *dialog, struct sip_request *response) +{ + dialog->authentication_attempts++; + + if (dialog->authentication_attempts == SIP_MAX_AUTHENTICATION_ATTEMPTS) { + return -1; + } + + if (sip_dialog_parse_authorization(dialog, response, SIP_METHOD_REGISTER)) { + /* There's nothing to use for authentication. No digest challenge in request */ + if (dialog->registry) { + ast_debug(1, "No authentication challenge, sending blank registration to domain/host name %s\n", + dialog->registry->hostname); + } + + return -1; + } + + dialog->authorization_code = response->code; + + if (dialog->record_history) { + sip_history_append(dialog, "RegistryAuth", "Try: %d", dialog->authentication_attempts); + } + + if (dialog->registry) { + ast_debug(1, "Responding to challenge, registration to domain/host name %s\n", dialog->registry->hostname); + } + + return sip_send_register(dialog->registry, TRUE); +} + +/* Send all known registrations */ +void sip_registry_send_all(void) +{ + int when, gap; + struct ao2_iterator iter; + struct sip_registry *registry; + + if (!ao2_container_count(sip_registry)) { + return; + } + + gap = MIN((sip_config.default_expiry * 1000) / ao2_container_count(sip_registry), 100); + when = gap; + + iter = ao2_iterator_init(sip_registry, 0); + + while ((registry = ao2_t_iterator_next(&iter, "bump registry"))) { + ao2_lock(registry); + + when += gap; + sip_registry_expires(registry, when); + + ao2_unlock(registry); + ao2_t_ref(registry, -1, "drop registry"); + } + + ao2_iterator_destroy(&iter); +} + +/* Run by the sched thread. */ +static int __sip_registry_expires(const void *data) +{ + struct sip_sched_data *sched_data; + struct sip_registry *registry; + int when; + + sched_data = (struct sip_sched_data *) data; + + registry = sched_data->registry; + when = sched_data->when; + + ast_free(sched_data); + + AST_SCHED_DEL_UNREF(sip_sched_context, registry->expire_sched_id, ao2_t_ref(registry, -1, "drop registry")); + + ao2_t_ref(registry, +1, "bump registry"); + + if ((registry->expire_sched_id = ast_sched_add(sip_sched_context, when, sip_registry_send, registry)) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(registry, -1, "drop registry"); + } + + ao2_t_ref(registry, -1, "drop registry"); + + return 0; +} + +void sip_registry_expires(struct sip_registry *registry, int when) +{ + struct sip_sched_data *sched_data; + + if (!(sched_data = ast_malloc(sizeof(*sched_data)))) { + /* Uh Oh. Expect bad behavior. */ + return; + } + + sched_data->registry = registry; + sched_data->when = when; + + ao2_t_ref(registry, +1, "bump registry"); + + if (ast_sched_add(sip_sched_context, 0, __sip_registry_expires, sched_data) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(registry, -1, "drop registry"); + ast_free(sched_data); + } +} + +/* Registration request timeout, register again. Registered as a timeout handler during sip_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_registry_timeout(const void *data) +{ + struct sip_registry *registry = (struct sip_registry *) data; /* the ref count should have been bumped when the sched item was added */ + + switch (registry->state) { + case SIP_REGISTRY_UNREGISTERED: + case SIP_REGISTRY_REQUEST_SENT: + case SIP_REGISTRY_AUTHORIZATION_SENT: + case SIP_REGISTRY_TIMEOUT: + break; + default: + /* + * Registration completed because we got a request response + * and we couldn't stop the scheduled entry in time. + */ + registry->timeout_sched_id = -1; + ao2_t_ref(registry, -1, "drop registry"); + + return 0; + } + + if (registry->dnsmgr) { + /* If the registration has timed out, maybe the IP changed. Force a refresh. */ + ast_dnsmgr_refresh(registry->dnsmgr); + } + + /* If the initial tranmission failed, we may not have an existing dialog, so it is possible that registry->dialog == NULL. + * Otherwise destroy it, as we have a timeout so we don't want it. */ + if (registry->dialog) { + /* Unlink us, destroy old call. Locking is not relevant here because all this happens + in the single SIP manager thread. */ + ao2_lock(registry->dialog); + sip_dialog_set_need_destroy(registry->dialog, "registration timeout"); + + /* Pretend to ACK anything just in case */ + sip_packet_pretend_ack(registry->dialog); + ao2_unlock(registry->dialog); + + /* decouple the two objects */ + /* dialog->registry == registry, so registry has 2 refs, and the unref won't take the object away */ + ao2_t_replace(registry->dialog->registry, NULL, "drop registry"); + ao2_t_cleanup(registry->dialog, "drop dialog"); + + registry->dialog = NULL; + } + + /* If we have a limit, stop registration and give up */ + registry->timeout_sched_id = -1; + + if (sip_config.register_max_attempts && registry->attempts >= sip_config.register_max_attempts) { + /* Ok, enough is enough. Don't try any more. We could add an external notification here. */ + ast_log(LOG_NOTICE, "Last Registration Attempt #%d failed, Giving up forever trying to register '%s@%s'\n", + registry->attempts, registry->username, registry->hostname); + + registry->state = SIP_REGISTRY_FAILED; + } else { + registry->state = SIP_REGISTRY_UNREGISTERED; + sip_send_register(registry, FALSE); + + ast_log(LOG_NOTICE, "Registration for '%s@%s' timed out, trying again (attempt %d)\n", + registry->username, registry->hostname, registry->attempts); + } + + ast_system_publish_registry("SIP", registry->username, registry->hostname, sip_registry_state2str(registry->state), NULL); + ao2_t_ref(registry, -1, "drop registry"); + + return 0; +} + +/* Run by the sched thread. */ +static int __sip_registry_start_timeout(const void *data) +{ + struct sip_registry *registry = (struct sip_registry *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, registry->timeout_sched_id, ao2_t_ref(registry, -1, "drop registry")); + + ao2_t_ref(registry, +1, "bump registry"); + + if ((registry->timeout_sched_id = ast_sched_add(sip_sched_context, sip_config.register_timeout * 1000, + sip_registry_timeout, registry)) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(registry, -1, "drop registry"); + } + + ast_debug(1, "Scheduled a registration timeout for %s\n", registry->hostname); + ao2_t_ref(registry, -1, "drop registry"); + + return 0; +} + +void sip_registry_start_timeout(struct sip_registry *registry) +{ + ao2_t_ref(registry, +1, "bump registry"); + + if (ast_sched_add(sip_sched_context, 0, __sip_registry_start_timeout, registry) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(registry, -1, "drop registry"); + } +} + +/* Run by the sched thread. */ +static int __sip_registry_stop_timeout(const void *data) +{ + struct sip_registry *registry = (struct sip_registry *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, registry->timeout_sched_id, ao2_t_ref(registry, -1, "drop registry")); + ao2_t_ref(registry, -1, "drop registry"); + + return 0; +} + +void sip_registry_stop_timeout(struct sip_registry *registry) +{ + ao2_t_ref(registry, +1, "bump registry"); + + if (ast_sched_add(sip_sched_context, 0, __sip_registry_stop_timeout, registry) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(registry, -1, "drop registry"); + } +} diff -durN asterisk-22.3.0.orig/channels/sip/request.c asterisk-22.3.0/channels/sip/request.c --- asterisk-22.3.0.orig/channels/sip/request.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/request.c 2025-04-17 11:11:22.821703009 +1200 @@ -0,0 +1,2552 @@ +/* + * 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/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/peers.h" +#include "include/registry.h" +#include "include/dialog.h" +#include "include/sdp.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/handlers.h" + +enum { + SIP_REQUEST_MATCH = 0, + SIP_REQUEST_NOT_MATCH, + SIP_REQUEST_LOOP_DETECTED, /* multiple incoming requests with same call-id but different branch parameters have been detected */ + SIP_REQUEST_FORKED, /* An outgoing request has been forked as result of receiving two differing 200 OK responses. */ +}; + +static int sip_request_cmp_dialog(struct sip_dialog *dialog, struct sip_request *request, char *from_tag, char *to_tag, + int has_authorization); +static void sip_request_fork_dialog(struct sip_request *request, struct sip_socket *socket, struct sip_dialog *orig_dialog, + const char *remote_tag); + +AST_THREADSTORAGE(sip_content_buf); + +/* Allocate a SIP request */ +int sip_request_alloc(struct sip_request *request, int method, const char *uri) +{ + /* Initialize a request */ + memset(request, 0, sizeof(*request)); + + request->method = method; + request->uri = ast_strdup(uri); + + return 0; +} + +/* Free a SIP response/request */ +void sip_request_destroy(struct sip_request *request) +{ + int i; + + ast_free((char *) request->uri); + request->uri = NULL; + + ast_free((char *) request->status_line); + request->status_line = NULL; + + for (i = 0; i < request->header_count; i++) { + ast_free((char *) request->header[i].name); + request->header[i].name = NULL; + + ast_free((char *) request->header[i].value); + request->header[i].value = NULL; + } + + request->header_count = 0; + + for (i = 0; i < request->line_count; i++) { + ast_free((char *) request->line[i]); + request->line[i] = NULL; + } + + request->line_count = 0; + + ast_free((char *) request->via_sent_by); + request->via_sent_by = NULL; + + ast_free((char *) request->via_branch); + request->via_branch = NULL; + + ast_free((char *) request->via_maddr); + request->via_maddr = NULL; +} + +/* Initiate new SIP request to peer/user */ +void sip_request_init(struct sip_request *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; + + ast_string_field_build(dialog, last_message, "Init: %s", sip_methods[method].name); + + 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.number.valid) { + user = connected_id.number.str; + } + + if (connected_id.name.valid) { + name = connected_id.name.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"; + } + } + + if (ast_strlen_zero(user)) { + if (method == SIP_METHOD_NOTIFY && !ast_strlen_zero(dialog->mwi_from) && + dialog->subscribe_events == SIP_SUBSCRIBE_MESSAGE_SUMMARY) { + user = dialog->mwi_from; + } else { + user = sip_config.callerid; + } + } + + /* 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 user 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 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); + } + + ast_string_field_set(dialog, exten, 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 fullcontact to dial to the peer */ + if (!ast_strlen_zero(dialog->full_contact)) { + /* If we have full contact, trust it */ + ast_str_append(&uri, 0, "%s", dialog->full_contact); + } else { + /* Otherwise, use the username while waiting for registration */ + ast_str_append(&uri, 0, "sip:"); + + if (!ast_strlen_zero(dialog->username)) { + const char *user = dialog->username; + + if (sip_config.pedantic_checking) { + ast_uri_encode(user, encoded_user, sizeof(encoded_user), ast_uri_sip_user); + user = encoded_user; + } + + ast_str_append(&uri, 0, "%s@", 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 (ast_test_flag(&dialog->flags[0], SIP_USER_EQ_PHONE) && ast_check_digits(dialog->username)) { + 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 there is enough room. */ + if (!ast_strlen_zero(name)) { + char escaped_name[256]; + + if (sip_config.pedantic_checking) { + ast_escape_quoted(name, escaped_name, sizeof(escaped_name)); + name = escaped_name; + } + + ast_str_set(&from, 0, "\"%s\" ", name); + } + + if (sip_config.pedantic_checking) { + ast_uri_encode(user, encoded_user, sizeof(encoded_user), ast_uri_sip_user); + user = encoded_user; + } + + port = dialog->from_domain_port && + (dialog->from_domain_port != SIP_STANDARD_PORT) ? dialog->from_domain_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", user, domain, port, dialog->local_tag); + } else { + ast_str_append(&from, 0, ";tag=%s", user, domain, dialog->local_tag); + } + + to = ast_str_alloca(512); + + if (!ast_strlen_zero(dialog->to_dnid)) { + if (!strchr(dialog->to_dnid, '@')) { + /* We have no domain in the dnid */ + ast_str_set(&to, 0, "%s%s", + dialog->to_dnid, dialog->to_host, ast_strlen_zero(dialog->remote_tag) ? "" : ";tag=", dialog->remote_tag); + } else { + ast_str_set(&to, 0, "%s%s", + dialog->to_dnid, ast_strlen_zero(dialog->remote_tag) ? "" : ";tag=", dialog->remote_tag); + } + } else { + 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); + } + } + + sip_dialog_build_contact(dialog, NULL); + + sip_request_alloc(request, method, dialog->uri); + sip_request_add_via(request, dialog); + + sip_request_add_header(request, "From", ast_str_buffer(from)); + sip_request_add_header(request, "To", ast_str_buffer(to)); + + sip_request_add_header(request, "Contact", dialog->our_contact); + + sip_request_add_header(request, "Call-ID", dialog->call_id); + sip_request_build_header(request, "CSeq", "%u %s", ++dialog->outgoing_cseq, sip_methods[method].name); + + sip_request_add_max_forwards(request, dialog); + + /* 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_request_add_route(request, &dialog->route, 0); + + if (!ast_strlen_zero(sip_config.useragent)) { + sip_request_add_header(request, "User-Agent", sip_config.useragent); + } +} + +/* Initialize a SIP request message (not the initial one in a dialog) */ +int sip_request_prepare(struct sip_request *request, struct sip_dialog *dialog, int method, uint32_t cseq, int new_branch) +{ + const char *uri; + char *to, *from; + int strict, outgoing; + + ast_string_field_build(dialog, last_message, "Tx: %s", sip_methods[method].name); + + 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); + } + + outgoing = !!ast_test_flag(&dialog->flags[0], SIP_OUTGOING); /* Session direction */ + + 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 contacturi on INVITEs) */ + if (!ast_strlen_zero(dialog->ok_contact_uri)) { + uri = strict ? sip_route_first_uri(&dialog->route) : dialog->ok_contact_uri; + } else { + uri = dialog->initial_request.uri; + } + } else if (!ast_strlen_zero(dialog->ok_contact_uri)) { + /* Use for BYE or REINVITE */ + uri = strict ? sip_route_first_uri(&dialog->route) : dialog->ok_contact_uri; + } else if (!ast_strlen_zero(dialog->uri)) { + uri = dialog->uri; + } else { + char *contact = ast_strdupa(sip_request_get_header(&dialog->initial_request, outgoing ? "To" : "From")); + + /* We have no URI, use To: or From: header as URI (depending on direction) */ + uri = sip_remove_uri_parameters(sip_get_in_brackets(contact)); + } + + sip_request_alloc(request, method, uri); + sip_request_add_via(request, dialog); + + from = ast_strdupa(sip_request_get_header(&dialog->initial_request, "From")); + to = ast_strdupa(sip_request_get_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 (!strcasestr(to, "tag=") && method != SIP_METHOD_CANCEL) { + struct ast_str *new_to = ast_str_alloca(512); + + /* 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 (outgoing && !ast_strlen_zero(dialog->remote_tag)) { + ast_str_set(&new_to, 0, "%s;tag=%s", to, dialog->remote_tag); + } else if (!outgoing) { + ast_str_set(&new_to, 0, "%s;tag=%s", to, dialog->local_tag); + } else { + ast_str_set(&new_to, 0, "%s", to); + } + + to = ast_strdupa(ast_str_buffer(new_to)); + } + + if (outgoing) { + sip_request_add_header(request, "From", from); + sip_request_add_header(request, "To", to); + } else { + sip_request_add_header(request, "From", to); + sip_request_add_header(request, "To", from); + } + + /* Do not add Contact for MESSAGE, BYE and Cancel requests */ + if (method != SIP_METHOD_BYE && method != SIP_METHOD_CANCEL && method != SIP_METHOD_MESSAGE) { + sip_request_add_header(request, "Contact", dialog->our_contact); + } + + sip_request_copy_header(request, &dialog->initial_request, "Call-ID"); + sip_request_build_header(request, "CSeq", "%u %s", cseq, sip_methods[method].name); + + /* 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 (ast_test_flag(&dialog->flags[0], SIP_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_dialog_set_address(dialog, sip_route_first_uri(&dialog->route)); + } + + sip_request_add_route(request, &dialog->route, strict); + } + + if (!ast_strlen_zero(sip_config.useragent)) { + sip_request_add_header(request, "User-Agent", sip_config.useragent); + } + + sip_request_add_max_forwards(request, dialog); + + if (!ast_strlen_zero(dialog->html_url)) { + sip_request_add_header(request, "Access-URL", dialog->html_url); + ast_string_field_set(dialog, html_url, NULL); + } + + /* 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->stimer && dialog->stimer->active && dialog->stimer->remote_active && + (method == SIP_METHOD_INVITE || method == SIP_METHOD_UPDATE)) { + sip_request_build_header(request, "Session-Expires", "%d;refresher=%s", dialog->stimer->interval, + dialog->stimer->refresher == SIP_STIMER_REFRESHER_UAC ? "uac" : "uas"); + + sip_request_build_header(request, "Min-SE", "%d", sip_stimer_get_expiry(dialog, FALSE)); + } + + return 0; +} + +/* Parse a SIP message */ +int sip_request_parse(struct sip_request *request, char *data) +{ + char *line; + const char *cseq; + int cseq_len, headers; + + if (!strncmp(data, "SIP/2.0 ", 8)) { /* We have a response */ + char *status_line; + + data += 8; + + status_line = ast_skip_blanks(strsep(&data, "\n")); + ast_trim_blanks(status_line); + + if (strlen(status_line) < 3) { /* status code is 3 digits */ + return -1; + } + + ast_debug(3, "Response status line: %s\n", status_line); + + if (sscanf(status_line, "%30d", &request->code) != 1) { + ast_log(LOG_WARNING, "Invalid response code '%s'\n", status_line); + return -1; + } + + request->response = TRUE; + request->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 request URI '%s'\n", uri); + return -1; + } + + protocol = ast_skip_blanks(strsep(&data, "\n")); + ast_trim_blanks(protocol); + + ast_debug(3, "Request method: %s, uri: %s, protocol: %s\n", method, uri, protocol); + + if (strcasecmp(protocol, "SIP/2.0")) { + ast_log(LOG_WARNING, "Invalid request protocol '%s'\n", protocol); + return -1; + } + + request->method = sip_find_method(method); + request->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 (request->header_count == SIP_MAX_HEADERS) { + ast_log(LOG_WARNING, "Too many headers, skipping: %s\n", line); + return -1; + } + + 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", request->header_count, name, value); + + request->header[request->header_count].name = ast_strdup(name); + request->header[request->header_count].value = ast_strdup(value); + + request->header_count++; + } else { + if (request->line_count == SIP_MAX_LINES) { + ast_log(LOG_WARNING, "Too many lines, skipping: %s\n", line); + break; + } + + ast_debug(3, "Line %d: %s\n", request->line_count, line); + + request->line[request->line_count] = ast_strdup(line); + request->line_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 */ + request->call_id = sip_request_get_header(request, "Call-ID"); + + if (ast_strlen_zero(request->call_id)) { + ast_log(LOG_ERROR, "Missing 'Call-ID' header\n"); + return -1; + } + + cseq = sip_request_get_header(request, "CSeq"); + + if (sscanf(cseq, "%30u %n", &request->cseq, &cseq_len) != 1) { + ast_log(LOG_ERROR, "Invalid CSeq '%s'\n", cseq); + return -1; + } + + /* If this is a response find the actual method in CSeq */ + if (request->response) { + request->method = sip_find_method(cseq + cseq_len); + } + + if (ast_strlen_zero(sip_request_get_header(request, "From"))) { + ast_log(LOG_WARNING, "Missing 'From' header\n"); + return -1; + } + + if (ast_strlen_zero(sip_request_get_header(request, "To"))) { + ast_log(LOG_WARNING, "Missing 'To' header\n"); + return -1; + } + + if (ast_strlen_zero(sip_request_get_header(request, "Via"))) { + ast_log(LOG_WARNING, "Missing 'Via' header\n"); + return -1; + } + + return 0; +} + +/* copy SIP request (mostly used to save request for responses) */ +void sip_request_copy(struct sip_request *to_request, const struct sip_request *from_request) +{ + int i; + + sip_request_destroy(to_request); + + to_request->method = from_request->method; + to_request->uri = ast_strdup(from_request->uri); + to_request->cseq = from_request->cseq; + to_request->code = from_request->code; + to_request->status_line = ast_strdup(from_request->status_line); + + to_request->header_count = 0; + + for (i = 0; i < from_request->header_count; i++) { + to_request->header[i].name = ast_strdup(from_request->header[i].name); + to_request->header[i].value = ast_strdup(from_request->header[i].value); + to_request->header_count++; + } + + to_request->line_count = 0; + + for (i = 0; i < from_request->line_count; i++) { + to_request->line[i] = ast_strdup(from_request->line[i]); + to_request->line_count++; + } + + to_request->via_sent_by = ast_strdup(from_request->via_sent_by); + to_request->via_branch = ast_strdup(from_request->via_branch); + to_request->via_maddr = ast_strdup(from_request->via_maddr); + + to_request->options = from_request->options; + to_request->sdp_start = from_request->sdp_start; + to_request->sdp_end = from_request->sdp_end; +} + +/* 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_request_find_dialog(struct sip_request *request, struct sip_socket *socket) +{ + char to_tag[128], from_tag[128]; + struct sip_dialog *dialog; + struct ast_str *tag; + + if (request->method == SIP_METHOD_UNKNOWN) { + ast_debug(3, "Got a request with an unknown method\n"); + sip_send_response_using_temp(socket, "501 Method Not Implemented", request); + + return NULL; + } + + tag = ast_str_alloca(128); + + to_tag[0] = '\0'; + from_tag[0] = '\0'; + + if (sip_config.pedantic_checking) { + /* In principle Call-ID's uniquely identify a call, but with a forking SIP proxy we need more to identify + * a branch - so we have to check branch, from and to-tags to identify a call leg. For Asterisk to behave + * correctly, you need to turn on pedantic_checking */ + sip_get_tag(request, "From", &tag); + ast_copy_string(from_tag, ast_str_buffer(tag), sizeof(from_tag)); + + sip_get_tag(request, "To", &tag); + ast_copy_string(to_tag, ast_str_buffer(tag), sizeof(to_tag)); + + if (!ast_strlen_zero(to_tag)) { + request->has_to_tag = TRUE; /* Used in sip_handle_request/response */ + } + + ast_debug(5, "Looking for Call-ID: '%s', checking %s: From: tag='%s' To: tag='%s'\n", + request->call_id, request->response ? "To" : "From", from_tag, to_tag); + + /* All messages must always have From: tag */ + if (ast_strlen_zero(from_tag)) { + ast_debug(5, "%s request has no From: tag, dropping Call-ID: '%s' From: '%s'\n", + sip_methods[request->method].name, request->call_id, sip_request_get_header(request, "From")); + return NULL; + } + + /* reject requests that must always have a To: tag */ + if (ast_strlen_zero(to_tag) && (request->method == SIP_METHOD_ACK || + request->method == SIP_METHOD_BYE || request->method == SIP_METHOD_INFO)) { + if (request->method != SIP_METHOD_ACK) { + sip_send_response_using_temp(socket, "481 Call/Transaction Does Not Exist", request); + } + + ast_debug(5, "%s must have a To: tag, dropping Call-ID: '%s' From: '%s'\n", + sip_methods[request->method].name, request->call_id, sip_request_get_header(request, "To")); + return NULL; + } + } + + /* Match on call-id only for REGISTERs */ + if (!sip_config.pedantic_checking || request->method == SIP_METHOD_REGISTER) { + if ((dialog = ao2_t_find(sip_dialogs, (char *) request->call_id, OBJ_SEARCH_KEY, "bump dialog"))) { + /* Found the call */ + return dialog; + } + } else { /* In pedantic mode! -- do the fancy search */ + /* if a Outbound forked Request is detected, this dialog will point to the dialog the Request is forking off of. */ + struct sip_dialog *fork_dialog; + struct ao2_iterator *iter; + int has_authorization; + + /* determine if this is a Request with authentication credentials. */ + if (!ast_strlen_zero(sip_request_get_header(request, "Authorization")) || + !ast_strlen_zero(sip_request_get_header(request, "Proxy-Authorization"))) { + has_authorization = TRUE; + } else { + has_authorization = FALSE; + } + + fork_dialog = NULL; + + iter = ao2_t_callback(sip_dialogs, OBJ_SEARCH_KEY | OBJ_MULTIPLE, sip_dialog_cmp, (char *) request->call_id, "bump dialog"); + + /* Iterate a list of dialogs already matched by Call-ID */ + while (iter && (dialog = ao2_iterator_next(iter))) { + int match; + + ao2_lock(dialog); + match = sip_request_cmp_dialog(dialog, request, from_tag, to_tag, has_authorization); + ao2_unlock(dialog); + + switch (match) { + case SIP_REQUEST_MATCH: + ao2_lock(dialog); + + if (!request->response && has_authorization && strcmp(from_tag, dialog->remote_tag)) { + /* If we have a request 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, from_tag); + } + + ao2_unlock(dialog); + ao2_iterator_destroy(iter); + + ao2_t_cleanup(fork_dialog, "drop dialog"); + + return dialog; /* return dialog with ref */ + case SIP_REQUEST_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 requests by sending a 482 response. */ + sip_send_response_using_temp(socket, "482 Loop Detected", request); + + ao2_t_ref(dialog, -1, "drop dialog"); + ao2_iterator_destroy(iter); + + ao2_t_cleanup(fork_dialog, "drop dialog"); + + return NULL; + case SIP_REQUEST_FORKED: + ao2_t_cleanup(fork_dialog, "drop dialog"); + fork_dialog = ao2_t_bump(dialog, "bump dialog\n"); + /* fall through */ + case SIP_REQUEST_NOT_MATCH: + default: + ao2_t_ref(dialog, -1, "drop dialog"); + break; + } + } + + if (iter) { + ao2_iterator_destroy(iter); + } + + /* Handle any possible forked requests. This must be done only after transaction matching is complete. */ + if (fork_dialog) { + /* XXX right now we only support handling forked INVITE Requests. Any other forked request type must be + * added here. */ + if (fork_dialog->method == SIP_METHOD_INVITE) { + sip_request_fork_dialog(request, socket, fork_dialog, to_tag); + ao2_t_cleanup(fork_dialog, "drop dialog"); + + return NULL; + } + + ao2_t_cleanup(fork_dialog, "drop dialog"); + fork_dialog = NULL; + } + } + + /* End of pedantic mode Request/Reponse to Dialog matching. See if the method is capable of creating a dialog */ + if (request->response) { + /* We do not respond to responses for dialogs that we don't know about, we just drop the session quickly */ + ast_debug(2, "Got a response for '%s' which we don't know about\n", request->call_id); + } else if (sip_methods[request->method].can_create) { + struct sip_dialog *dialog; + ast_callid logger_callid; + + if (request->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(request->call_id, socket, TRUE, request->method, request, 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. */ + ast_debug(4, "Failed allocating SIP dialog, giving up\n"); + sip_send_response_using_temp(socket, "500 Internal Server Error", request); + } + + return dialog; /* can be NULL */ + } else if (request->method == SIP_METHOD_PING) { + /* A method we do not support, let's take it on the volley */ + ast_debug(2, "Got a request with unsupported SIP method\n"); + sip_send_response_using_temp(socket, "501 Method Not Implemented", request); + } else if (request->method != SIP_METHOD_ACK) { + /* This is a request outside of a dialog that we don't know about */ + ast_debug(2, "Got a request for '%s' which we don't know about\n", request->call_id); + sip_send_response_using_temp(socket, "481 Call/Transaction Does Not Exist", request); + } + + 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_request_cmp_dialog(struct sip_dialog *dialog, struct sip_request *request, char *from_tag, char *to_tag, + int has_authorization) +{ + if (request->response) { + /* Verify from tag of response matches the tag we gave them. */ + if (strcmp(from_tag, dialog->local_tag)) { + /* from tag from response does not match our tag */ + return SIP_REQUEST_NOT_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) && ast_test_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED)) { + if (ast_strlen_zero(to_tag)) { + /* missing to-tag when they already gave us one earlier */ + return SIP_REQUEST_NOT_MATCH; + } + + /* compare the to-tag of response with the tag we have stored for them */ + if (strcmp(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 (request->code == 200 && + !ast_strlen_zero(dialog->invite_branch) && !ast_strlen_zero(request->via_branch) && + !strcmp(dialog->invite_branch, request->via_branch)) { + return SIP_REQUEST_FORKED; + } + + /* The to-tag did not match the one we had stored, and this is not a Forked Request. */ + return SIP_REQUEST_NOT_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(from_tag, dialog->remote_tag)) { + /* their tag does not match the one was have stored for them */ + return SIP_REQUEST_NOT_MATCH; + } + + /* Verify if to-tag is present in Request, that it matches what we gave them as our tag earlier */ + if (!ast_strlen_zero(to_tag) && strcmp(to_tag, dialog->local_tag)) { + /* to-tag from Request does not match our tag */ + return SIP_REQUEST_NOT_MATCH; + } + } + + /* Compare incoming request against initial transaction. This is a best effort attempt at distinguishing forked + * requests 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 request uri + * associated with it */ + if (!request->response && ast_strlen_zero(to_tag) && + dialog->initial_incoming_cseq == request->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(request->via_branch) || strcmp(request->via_branch, dialog->via_branch) || + ast_strlen_zero(request->via_sent_by) || strcmp(request->via_sent_by, dialog->via_sent_by)) { + /* At this point, this request does not match this Dialog.if methods are different this is just + * a mismatch */ + if (dialog->method != request->method) { + return SIP_REQUEST_NOT_MATCH; + } + + /* If RUIs are different, this is a forked request to a separate URI. Returning a mismatch allows + * this Request to be processed separately. */ + if (sip_cmp_uri(dialog->initial_request.uri, request->uri)) { + /* not a match, request uris are different */ + return SIP_REQUEST_NOT_MATCH; + } + + /* Loop/Merge Detected + * ---Current Matches to Initial Request--- + * request 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 request in which we have already received part of the fork. */ + return SIP_REQUEST_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 (!request->response && ast_strlen_zero(to_tag) && has_authorization && sip_cmp_uri(dialog->initial_request.uri, request->uri)) { + /* Authentication was provided, but the Request URI did not match the last one on this dialog. */ + return SIP_REQUEST_NOT_MATCH; + } + + return SIP_REQUEST_MATCH; +} + +/* This function creates a dialog to handle a forked request. This dialog + * exists only to properly terminiate the forked request immediately. */ +static void sip_request_fork_dialog(struct sip_request *request, struct sip_socket *socket, struct sip_dialog *orig_dialog, + const char *remote_tag) +{ + struct sip_dialog *dialog; + const char *call_id; + ast_callid logger_callid; + + ao2_lock(orig_dialog); + + call_id = ast_strdupa(orig_dialog->call_id); + logger_callid = orig_dialog->logger_callid; + + ao2_unlock(orig_dialog); + + if (!(dialog = sip_dialog_alloc(call_id, socket, TRUE, SIP_METHOD_INVITE, request, logger_callid))) { + return; /* alloc error */ + } + + /* Lock dialog and orig_dialog private structures. */ + ao2_lock(dialog); + + while (ao2_trylock(orig_dialog)) { + /* Can't use DEADLOCK_AVOIDANCE since dialog is an ao2 object */ + ao2_unlock(dialog); + sched_yield(); + ao2_lock(dialog); + } + + dialog->invite_state = SIP_INVITE_TERMINATED; + dialog->outgoing_cseq = orig_dialog->outgoing_cseq; + + memcpy(&dialog->flags, &orig_dialog->flags, sizeof(dialog->flags)); + sip_request_copy(&dialog->initial_request, &orig_dialog->initial_request); + + ast_string_field_set(dialog, remote_tag, remote_tag); + ast_string_field_set(dialog, local_tag, orig_dialog->local_tag); + ast_string_field_set(dialog, uri, orig_dialog->uri); + ast_string_field_set(dialog, our_contact, orig_dialog->our_contact); + ast_string_field_set(dialog, full_contact, orig_dialog->full_contact); + ast_string_field_set(dialog, branch, orig_dialog->branch); + + ao2_unlock(orig_dialog); + + sip_parse_ok_contact(dialog, request); + sip_dialog_build_route(dialog, request, TRUE); + + sip_send_ack(dialog, dialog->outgoing_cseq, TRUE); + sip_send_bye(dialog); + + sip_dialog_set_need_destroy(dialog, "forked request"); /* This dialog will terminate once the BYE is responed to or times out. */ + ao2_unlock(dialog); + + ao2_t_cleanup(dialog, "drop dialog"); +} + +const char *sip_request_next_header(const struct sip_request *request, const char *name, int *iter) +{ + int i; + + for (i = *iter; i < request->header_count; i++) { + if (!strcasecmp(request->header[i].name, name)) { + *iter = i + 1; + + return request->header[i].value; + } + } + + /* Don't return NULL, so sip_request_get_header is always a valid pointer */ + return ""; +} + +/* Get header from SIP request */ +const char *sip_request_get_header(const struct sip_request *request, const char *name) +{ + int iter = 0; + + return sip_request_next_header(request, name, &iter); +} + +/* Get message body content */ +char *sip_request_get_content(struct sip_request *request, int line_start, int line_end) +{ + struct ast_str *content; + int i; + + if (!(content = ast_str_thread_get(&sip_content_buf, 128))) { + return NULL; + } + + ast_str_reset(content); + + for (i = line_start; i < request->line_count && i <= line_end; i++) { + if (ast_str_append(&content, 0, "%s\n", request->line[i]) < 0) { + return NULL; + } + } + + return ast_str_buffer(content); +} + +/* find the content boundary */ +int sip_request_find_boundary(struct sip_request *request, const char *boundary, int line_start, int *done) +{ + int i, boundary_len; + + *done = FALSE; + boundary_len = strlen(boundary); + + for (i = line_start; i < request->line_count; i++) { + if (strncmp(request->line[i], "--", 2)) { + continue; + } + + if (!strncmp(boundary, request->line[i] + 2, boundary_len)) { + if (!strcmp(request->line[i] + 2 + boundary_len, "--")) { + *done = TRUE; + } else if (strcmp(request->line[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_request_find_content_type(struct sip_request *request) +{ + const char *content_type; + char *boundary; + int iter_start, iter, done; + + content_type = sip_request_get_header(request, "Content-Type"); + + if (ast_strlen_zero(content_type) || strncasecmp(content_type, "multipart/mixed", 15)) { + return content_type; + } + + if ((boundary = strcasestr(content_type, ";boundary="))) { + boundary += 10; + } else if ((boundary = strcasestr(content_type, "; boundary="))) { + boundary += 11; + } else { + return ""; + } + + boundary = ast_strdupa(boundary); + + if (*boundary == '"') { + boundary++; + boundary = strsep(&boundary, "\""); + } else { + boundary = strsep(&boundary, ";"); + } + + if ((iter_start = sip_request_find_boundary(request, boundary, 0, &done)) == -1) { + return ""; + } + + for (iter = iter_start + 1; iter < request->line_count; iter++) { + if (!strncasecmp(request->line[iter], "Content-Type:", 13)) { + return ast_skip_blanks(request->line[iter] + 13); + } else if (!strcasecmp(request->line[iter], "")) { + break; + } + } + + return ""; +} + +/* Send SIP Request to the other part of the dialog */ +int sip_request_send(struct sip_dialog *dialog, struct sip_request *request, int reliable, uint32_t cseq) +{ + int res; + struct ast_str *data; + + if (!(data = sip_request_build(request))) { + return -1; + } + + /* If we have an outbound proxy, reset peer address. Only do this once. */ + if (dialog->proxy) { + ast_sockaddr_copy(&dialog->address, &dialog->proxy->address); + } + + if (sip_dialog_debug(dialog)) { + const struct ast_sockaddr *address = sip_dialog_get_address(dialog); + + ast_verbose("<--- SIP write to (%s%s) %s://%s --->\n%s<------------->\n", + sip_nat_mode2str(dialog->flags), reliable ? ", reliably" : "", + ast_transport2str(dialog->socket.transport), ast_sockaddr_stringify(address), ast_str_buffer(data)); + } + + if (dialog->record_history) { + sip_history_append(dialog, reliable ? "TxReqRel" : "TxReq", "%d %s", cseq, sip_methods[request->method].name); + } + + dialog->method = request->method; + + if (reliable) { + res = sip_packet_send_reliable(dialog, request->method, cseq, 0, data, reliable == SIP_SEND_CRITICAL); + } else { + res = sip_packet_send(dialog, data); + } + + ast_free(data); + sip_request_destroy(request); + + return res; +} + +/* Add header to SIP message */ +void sip_request_add_header(struct sip_request *request, const char *name, const char *value) +{ + if (request->header_count == SIP_MAX_HEADERS) { + ast_log(LOG_WARNING, "Unable to add header '%s: %s', too many headers\n", name, value); + return; + } + + request->header[request->header_count].name = ast_strdup(name); + request->header[request->header_count].value = ast_strdup(value); + + if (!strcasecmp(name, "Call-ID")) { + request->call_id = request->header[request->header_count].value; + } + + request->header_count++; +} + +void sip_request_build_header(struct sip_request *request, 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_request_add_header(request, name, value); +} + +/* Copy one header field from one request to another */ +void sip_request_copy_header(struct sip_request *request, const struct sip_request *orig_request, const char *name) +{ + const char *value = sip_request_get_header(orig_request, name); + + if (!ast_strlen_zero(value)) { /* Add what we're responding to */ + sip_request_add_header(request, name, value); + } else { + ast_log(LOG_NOTICE, "No header '%s' present to copy\n", name); + } +} + +/* Add date header to SIP message */ +void sip_request_add_date(struct sip_request *request) +{ + 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_request_add_header(request, "Date", date); +} + +/* Add Expires header to SIP message */ +void sip_request_add_expires(struct sip_request *request, int expires) +{ + sip_request_build_header(request, "Expires", "%d", expires); +} + +/* Add Authorization or Proxy-Authorization to SIP message */ +void sip_request_add_authorization(struct sip_request *request, struct sip_dialog *dialog) +{ + if (!dialog->authorization_code || ast_strlen_zero(dialog->authorization)) { + return; + } + + sip_request_add_header(request, + dialog->authorization_code == 401 ? "Authorization" : "Proxy-Authorization", dialog->authorization); +} + +/* if dialog->channel exists, it must be locked Add Remote-Party-ID header to SIP message */ +void sip_request_add_remote_party_id(struct sip_request *request, struct sip_dialog *dialog) +{ + struct ast_str *remote_party_id; + char *connected_number, *connected_name; + int connected_presentation, connected_source; + const char *from_domain, *privacy, *screen, *callback_number; + struct ast_party_id connected_line; + + if (!ast_test_flag(&dialog->flags[0], SIP_SEND_REMOTE_PARTY_ID) || !dialog->channel) { + return; + } + + connected_source = ast_channel_connected(dialog->channel)->source; + connected_line = ast_channel_connected_effective_id(dialog->channel); + + 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 && + ast_test_flag(&dialog->flags[1], SIP_TRUST_ID_OUTBOUND) == SIP_TRUST_ID_OUTBOUND_NO) { + /* If pres is not allowed and we don't trust the peer, we don't apply an RPID header */ + return; + } + + from_domain = dialog->from_domain; + + if (!from_domain || + ((ast_test_flag(&dialog->flags[1], SIP_TRUST_ID_OUTBOUND) == SIP_TRUST_ID_OUTBOUND_YES) && + !strcmp("anonymous.invalid", from_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 */ + from_domain = ast_sockaddr_stringify_host_remote(&dialog->our_address); + } + + remote_party_id = ast_str_alloca(512); + + if (!ast_strlen_zero(connected_name)) { + char encoded_name[512]; + + if (ast_test_flag(&dialog->flags[1], SIP_CISCO_USECALLMANAGER)) { + 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"; + } + } + + ast_escape_quoted(connected_name, encoded_name, sizeof(encoded_name)); + ast_str_append(&remote_party_id, 0, "\"%s\" ", encoded_name); + } + + ast_str_append(&remote_party_id, 0, "channel, "CISCO_CALLBACK_NUMBER"); + + if (!ast_strlen_zero(callback_number)) { + char encoded_number[512]; + + ast_uri_encode(callback_number, encoded_number, sizeof(encoded_number), ast_uri_sip_user); + ast_str_append(&remote_party_id, 0, ";x-cisco-callback-number=%s", encoded_number); + } + + ast_str_append(&remote_party_id, 0, ">"); + + if (ast_test_flag(&dialog->flags[0], SIP_SEND_REMOTE_PARTY_ID_PAI)) { + sip_request_add_header(request, "P-Asserted-Identity", ast_str_buffer(remote_party_id)); + + /* trust_id_outbound = yes - Always give full information even if it's private, but append a privacy header + * When private data is included */ + if ((connected_presentation & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + sip_request_add_header(request, "Privacy", "id"); + } + } else { + ast_str_append(&remote_party_id, 0, ";party=%s", dialog->outgoing_call ? "calling" : "called"); + + if ((connected_presentation & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + privacy = "full"; + } else { + privacy = "off"; + } + + if (connected_presentation & AST_PRES_USER_NUMBER_PASSED_SCREEN) { + screen = "yes"; + } else { + screen = "no"; + } + + ast_str_append(&remote_party_id, 0, ";privacy=%s;screen=%s", privacy, screen); + sip_request_add_header(request, "Remote-Party-ID", ast_str_buffer(remote_party_id)); + } +} + +void sip_request_add_call_info(struct sip_request *request, struct sip_dialog *dialog) +{ + struct ast_str *call_info; + + if (!ast_test_flag(&dialog->flags[1], SIP_CISCO_USECALLMANAGER)) { + return; + } + + call_info = ast_str_alloca(512); + + ast_str_set(&call_info, 0, "; orientation=%s; security=", dialog->outgoing_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 (ast_test_flag(&dialog->flags[0], SIP_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[256]; + + 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)) { + ast_uri_encode(name, encoded, sizeof(encoded), ast_uri_sip_user); + ast_str_append(&call_info, 0, "%%%02X%s%%%02X ", (unsigned char) '"', encoded, (unsigned char) '"'); + } + + ast_uri_encode(number, encoded, sizeof(encoded), ast_uri_sip_user); + ast_str_append(&call_info, 0, "\"", + encoded, ast_sockaddr_stringify_host_remote(&dialog->our_address)); + } + } + } + + sip_request_add_header(request, "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_request_add_supported(struct sip_request *request, struct sip_dialog *dialog) +{ + struct ast_str *supported = ast_str_alloca(512); + + ast_str_append(&supported, 0, "replaces"); + + if (sip_stimer_get_mode(dialog, FALSE) != SIP_STIMER_MODE_REFUSE) { + ast_str_append(&supported, 0, ",timer"); + } + + if (ast_test_flag(&dialog->flags[0], SIP_USE_PATH)) { + ast_str_append(&supported, 0, ",path"); + } + + if (ast_test_flag(&dialog->flags[1], SIP_CISCO_USECALLMANAGER)) { + ast_str_append(&supported, 0, ",X-cisco-sis-10.0.0"); + } + + sip_request_add_header(request, "Supported", ast_str_buffer(supported)); +} + +void sip_request_add_require(struct sip_request *request) +{ + char *require = sip_options2str(request->options); + + if (!ast_strlen_zero(require)) { + sip_request_add_header(request, "Require", require); + } +} + +/* 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_request_add_diversion(struct sip_request *request, struct sip_dialog *dialog) +{ + struct ast_party_id redirecting_from; + const char *reason, *quote; + struct ast_str *diversion; + char encoded_user[256]; + int i; + + /* We skip this entirely if the configuration doesn't allow diversion headers */ + if (!sip_config.send_diversion || !dialog->channel) { + 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); + + if (sip_config.pedantic_checking) { + ast_uri_encode(redirecting_from.number.str, encoded_user, sizeof(encoded_user), ast_uri_sip_user); + } else { + ast_copy_string(encoded_user, redirecting_from.number.str, sizeof(encoded_user)); + } + + if (redirecting_from.name.valid && !ast_strlen_zero(redirecting_from.name.str)) { + char escaped_name[256]; + + if (sip_config.pedantic_checking) { + ast_escape_quoted(redirecting_from.name.str, escaped_name, sizeof(escaped_name)); + } else { + ast_copy_string(escaped_name, redirecting_from.name.str, 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 (ast_test_flag(&dialog->flags[1], SIP_CISCO_USECALLMANAGER)) { + 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_request_add_header(request, "Diversion", ast_str_buffer(diversion)); +} + +void sip_request_add_join(struct sip_request *request, struct sip_dialog *dialog) +{ + if (!ast_test_flag(&dialog->flags[2], SIP_RELAY_NEAREND) && !ast_test_flag(&dialog->flags[2], SIP_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_request_build_header(request, "Join", "%s;from-tag=%s;to-tag=%s", + dialog->join_call_id, dialog->join_local_tag, dialog->join_remote_tag); +} + +void sip_request_add_via(struct sip_request *request, struct sip_dialog *dialog) +{ + sip_request_build_header(request, "Via", "SIP/2.0/%s %s;branch=%s", + ast_transport2str(dialog->socket.transport), ast_sockaddr_stringify_remote(&dialog->our_address), dialog->branch); +} + +/* dialog assumed to be locked while calling this function Add 'Max-Forwards' header to SIP message */ +void sip_request_add_max_forwards(struct sip_request *request, struct sip_dialog *dialog) +{ + sip_request_build_header(request, "Max-Forwards", "%d", dialog->max_forwards); +} + +/* Add route header into request per learned route */ +void sip_request_add_route(struct sip_request *request, struct sip_route *route, int skip) +{ + struct ast_str *path; + + if (sip_route_empty(route)) { + return; + } + + if ((path = sip_route_list(route, FALSE, skip))) { + if (ast_str_strlen(path)) { + sip_request_add_header(request, "Route", ast_str_buffer(path)); + } + + ast_free(path); + } +} + +/* Add content (not header) to SIP message */ +void sip_request_add_content(struct sip_request *request, const char *line) +{ + if (request->line_count == SIP_MAX_LINES) { + ast_log(LOG_WARNING, "Unable to add line '%s', too many lines\n", line); + return; + } + + /* Lines need to be trimmed because sip_request_build will re-add them */ + request->line[request->line_count] = ast_trim_blanks(ast_strdup(line)); + request->line_count++; +} + +void sip_request_build_content(struct sip_request *request, const char *format, ...) +{ + va_list args; + char line[4096]; + + va_start(args, format); + vsnprintf(line, sizeof(line), format, args); + va_end(args); + + sip_request_add_content(request, line); +} + +/* Stringify a request so that it can be sent */ +struct ast_str *sip_request_build(struct sip_request *request) +{ + struct ast_str *data; + int i, content_len; + + if (!(data = ast_str_create(4096))) { + return NULL; + } + + if (request->response) { + ast_str_append(&data, 0, "SIP/2.0 %s\r\n", request->status_line); + } else { + ast_str_append(&data, 0, "%s %s SIP/2.0\r\n", sip_methods[request->method].name, request->uri); + } + + for (i = 0; i < request->header_count; i++) { + ast_str_append(&data, 0, "%s: %s\r\n", request->header[i].name, request->header[i].value); + } + + content_len = 0; + + for (i = 0; i < request->line_count; i++) { + content_len += strlen(request->line[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"); + + if (request->line_count) { + for (i = 0; i < request->line_count; i++) { + ast_str_append(&data, 0, "%s\r\n", request->line[i]); + } + } + + return data; +} + +/* Build REFER/INVITE/OPTIONS/NOTIFY/SUBSCRIBE message and transmit it */ +int sip_send_request(struct sip_dialog *dialog, int method, int add_sdp, int init, const char *explicit_uri) +{ + struct sip_request 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_request_init(&request, dialog, method, explicit_uri); + } else { + sip_request_prepare(&request, dialog, method, 0, init == SIP_INIT_BRANCH); + } + + sip_request_add_authorization(&request, dialog); + sip_request_add_date(&request); + + if (method == SIP_METHOD_INVITE) { + /* 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_request_add_header(&request, "Replaces", dialog->replaces); + sip_request_add_header(&request, "Require", "replaces"); + } + } else if (method == SIP_METHOD_REFER) { /* Call transfer */ + if (!ast_strlen_zero(dialog->require)) { + sip_request_add_header(&request, "Require", dialog->require); + } + + if (!ast_strlen_zero(dialog->refer_to)) { + sip_request_add_header(&request, "Refer-To", dialog->refer_to); + } + + if (!ast_strlen_zero(dialog->referred_by)) { + sip_request_add_header(&request, "Referred-By", dialog->referred_by); + } + + if (!ast_strlen_zero(dialog->content_id)) { + sip_request_add_header(&request, "Content-Id", dialog->content_id); + } + + if (!ast_strlen_zero(dialog->content_type)) { + sip_request_add_header(&request, "Content-Type", dialog->content_type); + } + + sip_request_add_expires(&request, dialog->expiry); + } else if (method == SIP_METHOD_SUBSCRIBE) { + if (dialog->subscribe_events == SIP_SUBSCRIBE_MESSAGE_SUMMARY) { + sip_request_add_header(&request, "Event", "message-summary"); + sip_request_add_header(&request, "Accept", "application/simple-message-summary"); + } + + sip_request_add_expires(&request, dialog->expiry); + } else if (method == SIP_METHOD_NOTIFY) { + struct ast_variable *header; + + for (header = dialog->notify_headers; header; header = header->next) { + sip_request_add_header(&request, header->name, header->value); + } + } + + /* Add Session-Timers related headers if not already there */ + if (method == SIP_METHOD_INVITE || method == SIP_METHOD_UPDATE) { + if (sip_stimer_get_mode(dialog, FALSE) == SIP_STIMER_MODE_ORIGINATE || + (sip_stimer_get_mode(dialog, FALSE) == SIP_STIMER_MODE_ACCEPT && + sip_stimer_get_expiry(dialog, FALSE) != sip_config.stimer_min_se)) { + sip_stimer_alloc(dialog); + + if (!dialog->stimer->interval) { + dialog->stimer->interval = sip_stimer_get_expiry(dialog, TRUE); + } + + dialog->stimer->active = TRUE; + + if (sip_stimer_get_mode(dialog, FALSE) == SIP_STIMER_MODE_ORIGINATE) { + sip_request_build_header(&request, "Session-Expires", "%d", dialog->stimer->interval); + } + + sip_request_build_header(&request, "Min-SE", "%d", sip_stimer_get_expiry(dialog, FALSE)); + } + } + + sip_request_add_header(&request, "Allow", SIP_ALLOWED_METHODS); + sip_request_add_supported(&request, dialog); + + 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 of the starting and ending " (if it's there) */ + + if ((value = strchr(name, ':'))) { + *value++ = '\0'; + value = ast_skip_blanks(value); /* Skip white space */ + + sip_request_add_header(&request, name, value); + + ast_debug(3, "Adding SIP Header \"%s\" with content \"%s\"\n", name, value); + } + } + + ast_channel_unlock(dialog->channel); + } + + if ((method == SIP_METHOD_INVITE || method == SIP_METHOD_UPDATE) && ast_test_flag(&dialog->flags[0], SIP_SEND_REMOTE_PARTY_ID)) { + sip_request_add_remote_party_id(&request, dialog); + } + + if (method == SIP_METHOD_INVITE) { + sip_request_add_diversion(&request, dialog); + sip_request_add_join(&request, dialog); + sip_request_add_call_info(&request, dialog); + } + + if (add_sdp) { + sip_sdp_media_destroy(dialog); + + if (dialog->udptl && dialog->t38_state == SIP_T38_LOCAL_REINVITE) { + ast_debug(1, "T38 is in state %u on channel %s\n", + dialog->t38_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); + } + } else if (method == SIP_METHOD_NOTIFY) { + if (!ast_strlen_zero(dialog->notify_content)) { + sip_request_add_content(&request, dialog->notify_content); + } + } else if (method == SIP_METHOD_REFER) { + if (!ast_strlen_zero(dialog->refer_content)) { + sip_request_add_content(&request, dialog->refer_content); + } + } + + if (!dialog->initial_request.uri) { + ast_debug(1, "Set initial %s request for '%s'\n", sip_methods[request.method].name, dialog->call_id); + + sip_request_copy(&dialog->initial_request, &request); + } + + if (method == SIP_METHOD_INVITE || method == SIP_METHOD_SUBSCRIBE) { + dialog->last_invite_cseq = dialog->outgoing_cseq; + } + + return sip_request_send(dialog, &request, init != SIP_INIT_NONE ? SIP_SEND_CRITICAL : SIP_SEND_RELIABLE, dialog->outgoing_cseq); +} + +int sip_send_invite(struct sip_dialog *dialog, int add_sdp, int init, const char *explicit_uri) +{ + return sip_send_request(dialog, SIP_METHOD_INVITE, add_sdp, init, explicit_uri); +} + +/* Transmit reinvite with 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_send_reinvite_with_sdp(struct sip_dialog *dialog, int old_sdp, int add_image) +{ + struct sip_request 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_request_prepare(&request, dialog, + ast_test_flag(&dialog->flags[0], SIP_REINVITE_UPDATE) ? SIP_METHOD_UPDATE : SIP_METHOD_INVITE, 0, TRUE); + + sip_request_add_header(&request, "Allow", SIP_ALLOWED_METHODS); + sip_request_add_supported(&request, dialog); + + if (ast_test_flag(&dialog->flags[0], SIP_SEND_REMOTE_PARTY_ID)) { + sip_request_add_remote_party_id(&request, dialog); + } + + if (dialog->record_history) { + sip_history_append(dialog, "ReInv", "Re-invite sent"); + } + + 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, !add_image, add_image); + + /* Use this as the basis */ + sip_request_copy(&dialog->initial_request, &request); + + ast_debug(1, "Set initial %s request '%s'\n", sip_methods[request.method].name, dialog->call_id); + + dialog->last_invite_cseq = dialog->outgoing_cseq; + dialog->ongoing_reinvite = TRUE; + + ast_set_flag(&dialog->flags[0], SIP_OUTGOING); /* Change direction of this dialog */ + + return sip_request_send(dialog, &request, SIP_SEND_CRITICAL, dialog->outgoing_cseq); +} + +int sip_send_update(struct sip_dialog *dialog) +{ + struct sip_request request; + + sip_request_prepare(&request, dialog, SIP_METHOD_UPDATE, 0, TRUE); + + if (ast_test_flag(&dialog->flags[0], SIP_SEND_REMOTE_PARTY_ID)) { + sip_request_add_remote_party_id(&request, dialog); + } + + return sip_request_send(dialog, &request, SIP_SEND_CRITICAL, dialog->outgoing_cseq); +} + +int sip_send_options(struct sip_dialog *dialog) +{ + return sip_send_request(dialog, SIP_METHOD_OPTIONS, FALSE, SIP_INIT_REQUEST, NULL); +} + +/* Send an ACK to a response to an INVITE */ +int sip_send_ack(struct sip_dialog *dialog, uint32_t cseq, int new_branch) +{ + struct sip_request request; + + dialog->invite_state = SIP_INVITE_CONFIRMED; + + sip_request_prepare(&request, dialog, SIP_METHOD_ACK, cseq, new_branch); + + if (ast_test_flag(&dialog->flags[2], SIP_SDP_ACK)) { + sip_sdp_build(dialog, &request, FALSE, TRUE, FALSE); + } + + return sip_request_send(dialog, &request, SIP_SEND_UNRELIABLE, cseq); +} + +/* Send a CANCEL to an outgoing INVITE */ +int sip_send_cancel(struct sip_dialog *dialog) +{ + struct sip_request request; + + dialog->invite_state = SIP_INVITE_CANCELLED; + + sip_request_prepare(&request, dialog, SIP_METHOD_CANCEL, dialog->last_invite_cseq, FALSE); + sip_request_add_header(&request, "Reason", "SIP;cause=200;text=\"Call completed elsewhere\""); + + return sip_request_send(dialog, &request, SIP_SEND_RELIABLE, dialog->last_invite_cseq); +} + +/* Send a BYE */ +int sip_send_bye(struct sip_dialog *dialog) +{ + struct sip_request request; + + sip_request_prepare(&request, dialog, SIP_METHOD_BYE, 0, TRUE); + + if (!ast_strlen_zero(dialog->realm)) { + if (!sip_dialog_build_authorization(dialog, SIP_METHOD_BYE)) { + if (!dialog->authorization_code) { + dialog->authorization_code = 401; + } + + sip_request_add_authorization(&request, dialog); + } else { + ast_log(LOG_WARNING, "No authentication available for '%s'\n", dialog->call_id); + } + } + + if (ast_test_flag(&dialog->flags[1], SIP_Q850_REASON) && dialog->hangupcause) { + sip_request_build_header(&request, "Reason", "Q.850;cause=%d", dialog->hangupcause & 0x7f); + } + + return sip_request_send(dialog, &request, SIP_SEND_RELIABLE, dialog->outgoing_cseq); +} + +/* Transmit register to SIP proxy or UA. auth = NULL on the initial registration */ +int sip_send_register(struct sip_registry *registry, int add_authorization) +{ + struct sip_request request; + struct ast_str *from, *to, *uri; + struct sip_dialog *dialog; + int res, port = 0; + + /* exit if we are already in process with this registrar ?*/ + if (!add_authorization && (registry->state == SIP_REGISTRY_REQUEST_SENT || registry->state == SIP_REGISTRY_AUTHORIZATION_SENT)) { + ast_log(LOG_NOTICE, "Trying to register %s@%s when registration already pending\n", + registry->username, registry->hostname); + return 0; + } + + if (!registry->dnsmgr) { + char transport[MAXHOSTNAMELEN]; + RAII_VAR(struct sip_peer *, peer, NULL, ao2_cleanup); + + peer = sip_peer_find(registry->hostname, TRUE, FALSE); + + /* 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 registry->dialog or peer 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(registry->dialog, peer)) { + ao2_t_ref(registry, +1, "bump registry"); + + registry->address.ss.ss_family = AF_INET; /* Filter address family */ + + /* have to use static sip_get_transport function */ + snprintf(transport, sizeof(transport), "_%s._%s", + sip_srv_service(registry->transport), sip_srv_protocol(registry->transport)); + + ast_dnsmgr_lookup_cb(peer ? peer->host : registry->hostname, ®istry->address, ®istry->dnsmgr, + sip_config.srv_lookup ? transport : NULL, sip_registry_dnsmgr_lookup, registry); + + if (!registry->dnsmgr) { + /*dnsmgr refresh disabled, no reference added! */ + ao2_t_ref(registry, -1, "drop registry"); + } + } + } + + if (registry->dialog) { /* We have a registration */ + if (!add_authorization) { + ast_log(LOG_WARNING, "Already have a REGISTER going on to %s@%s\n", registry->username, registry->hostname); + return 0; + } + + dialog = ao2_t_bump(registry->dialog, "bump dialog"); + ast_string_field_set(dialog, remote_tag, NULL); /* forget their old tag, so we don't match tags when getting response */ + } else { + /* Build call-id for registration if we haven't registered before */ + if (!registry->valid_call_id) { + sip_registry_build_call_id(registry); + sip_registry_build_local_tag(registry); + + registry->valid_call_id = TRUE; + } + + /* Allocate SIP dialog for registration */ + if (!(dialog = sip_dialog_alloc(registry->call_id, NULL, FALSE, SIP_METHOD_REGISTER, NULL, 0))) { + ast_log(LOG_WARNING, "Unable to allocate registration dialog (memory or socket error)\n"); + return 0; + } + + /* reset tag to consistent value from registry */ + ast_string_field_set(dialog, local_tag, registry->local_tag); + + if (dialog->record_history) { + sip_history_append(dialog, "RegistryInit", "Account: %s@%s", registry->username, registry->hostname); + } + + dialog->socket.transport = registry->transport; + + /* Use port number specified if no SRV record was found */ + if (!ast_sockaddr_isnull(®istry->address)) { + RAII_VAR(struct sip_peer *, peer, NULL, ao2_cleanup); + + if (!ast_sockaddr_port(®istry->address) && registry->port) { + ast_sockaddr_set_port(®istry->address, registry->port); + } + + /* It is possible that DNS was unavailable at the time the peer was created. Here, if we've updated + * the address in the registry via manually calling ast_dnsmgr_lookup_cb() above, then we call the same + * function that dnsmgr would call if it was updating a peer's address */ + if ((peer = sip_peer_find(S_OR(registry->peer_name, registry->hostname), TRUE, FALSE))) { + if (ast_sockaddr_cmp(&peer->address, ®istry->address)) { + sip_peer_dnsmgr_lookup(&peer->address, ®istry->address, peer); + } + } + } + + /* Find address to hostname */ + if (sip_dialog_build(dialog, S_OR(registry->peer_name, registry->hostname), ®istry->address, FALSE)) { + /* we have what we hope is a temporary network error, probably DNS. We need to reschedule a registration try */ + sip_dialog_unlink(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + ast_log(LOG_WARNING, "Probably a DNS error for registration to %s@%s, trying REGISTER again (after %ds)\n", + registry->username, registry->hostname, sip_config.register_timeout); + + sip_registry_start_timeout(registry); + registry->attempts++; + + return 0; + } + + /* Copy back Call-ID in case sip_dialog_build changed it */ + ast_string_field_set(registry, call_id, dialog->call_id); + + if (!registry->dnsmgr && registry->port) { + ast_sockaddr_set_port(&dialog->address, registry->port); + ast_sockaddr_set_port(&dialog->received_address, registry->port); + } + + if (!ast_strlen_zero(dialog->from_domain)) { + port = dialog->from_domain_port ? dialog->from_domain_port : SIP_STANDARD_PORT; + } else if (!ast_strlen_zero(registry->domain)) { + port = registry->domain_port ? registry->domain_port : SIP_STANDARD_PORT; + } else { + port = ast_sockaddr_port(&dialog->address); + } + + ast_set_flag(&dialog->flags[0], SIP_OUTGOING); /* Registration is outgoing call */ + registry->dialog = ao2_t_bump(dialog, "bump dialog"); /* Save pointer to SIP dialog */ + + dialog->registry = ao2_t_bump(registry, "bump registry"); /* Add pointer to registry in packet */ + + if (!ast_strlen_zero(registry->secret)) { /* Secret (password) */ + ast_string_field_set(dialog, peer_secret, registry->secret); + } + + if (!ast_strlen_zero(registry->md5secret)) { + ast_string_field_set(dialog, peer_md5secret, registry->md5secret); + } + + /* User name in this realm - if authuser is set, use that, otherwise use username */ + if (!ast_strlen_zero(registry->auth_user)) { + ast_string_field_set(dialog, peer_name, registry->auth_user); + ast_string_field_set(dialog, auth_name, registry->auth_user); + } else if (!ast_strlen_zero(registry->username)) { + ast_string_field_set(dialog, peer_name, registry->username); + ast_string_field_set(dialog, auth_name, registry->username); + ast_string_field_set(dialog, from_user, registry->username); + } + + if (!ast_strlen_zero(registry->username)) { + ast_string_field_set(dialog, username, registry->username); + } + + /* Save extension in packet */ + if (!ast_strlen_zero(registry->exten)) { + ast_string_field_set(dialog, exten, registry->exten); + } + + /* Set transport so the correct contact is built */ + sip_socket_set_transport(&dialog->socket, registry->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_get_our_address(dialog->socket.transport, &dialog->address, &dialog->our_address); + } + + /* set up a timeout */ + if (!add_authorization) { + sip_registry_start_timeout(registry); + } + + uri = ast_str_alloca(512); + + /* Fromdomain is what we are registering to, regardless of actual host name from SRV */ + ast_str_set(&uri, 0, "sip:%s", S_OR(dialog->from_domain, S_OR(registry->domain, registry->hostname))); + + if (port && port != SIP_STANDARD_PORT) { + ast_str_append(&uri, 0, ":%d", port); + } + + ast_string_field_set(dialog, uri, ast_str_buffer(uri)); + + from = ast_str_alloca(512); + + ast_str_set(&from, 0, ";tag=%s", registry->username, S_OR(registry->domain, dialog->to_host), dialog->local_tag); + + to = ast_str_alloca(512); + + ast_str_set(&to, 0, "", registry->username, S_OR(registry->domain, dialog->to_host)); + + if (!ast_strlen_zero(dialog->remote_tag)) { + ast_str_append(&to, 0, ";tag=%s", dialog->remote_tag); + } + + ast_string_field_build(dialog, branch, "%s%08x", SIP_MAGIC_COOKIE, (unsigned int) ast_random()); + sip_dialog_build_contact(dialog, NULL); + + sip_request_alloc(&request, SIP_METHOD_REGISTER, ast_str_buffer(uri)); + sip_request_add_via(&request, dialog); + + sip_request_add_header(&request, "From", ast_str_buffer(from)); + sip_request_add_header(&request, "To", ast_str_buffer(to)); + + sip_request_add_header(&request, "Call-ID", dialog->call_id); + sip_request_build_header(&request, "CSeq", "%u %s", ++registry->outgoing_cseq, sip_methods[SIP_METHOD_REGISTER].name); + + sip_request_add_header(&request, "Contact", dialog->our_contact); + + sip_request_add_max_forwards(&request, dialog); + + sip_request_add_header(&request, "Allow", SIP_ALLOWED_METHODS); + sip_request_add_supported(&request, dialog); + + sip_request_add_expires(&request, registry->expiry); + + if (!ast_strlen_zero(sip_config.useragent)) { + sip_request_add_header(&request, "User-Agent", sip_config.useragent); + } + + if (add_authorization) { + /* We have auth data to reuse, build a authorization header. Note, this is not always useful because some + * parties do not like nonces to be reused (for good reasons!) so they will challenge us anyways. */ + ast_debug(1, "Re-using authorization data for %s@%s\n", registry->username, registry->hostname); + + ast_string_field_set(dialog, realm, registry->realm); + ast_string_field_set(dialog, nonce, registry->nonce); + ast_string_field_set(dialog, domain, registry->domain); + ast_string_field_set(dialog, opaque, registry->opaque); + ast_string_field_set(dialog, qop, registry->qop); + + dialog->nonce_count = ++registry->nonce_count; + + if (!sip_dialog_build_authorization(dialog, SIP_METHOD_REGISTER)) { + sip_request_add_authorization(&request, dialog); + } else { + ast_log(LOG_NOTICE, "No authorization available for authentication of registration to %s@%s\n", + registry->username, registry->hostname); + } + } + + sip_request_copy(&dialog->initial_request, &request); + + ast_debug(1, "Set initial %s request for '%s'\n", sip_methods[SIP_METHOD_REGISTER].name, dialog->call_id); + + registry->state = add_authorization ? SIP_REGISTRY_AUTHORIZATION_SENT : SIP_REGISTRY_REQUEST_SENT; + registry->attempts++; /* Another attempt */ + + ast_debug(4, "REGISTER attempt %d to %s@%s\n", registry->attempts, registry->username, registry->hostname); + + dialog->outgoing_cseq = registry->outgoing_cseq; + res = sip_request_send(dialog, &request, SIP_SEND_CRITICAL, dialog->outgoing_cseq); + + ao2_t_cleanup(dialog, "drop dialog"); + + return res; +} + +int sip_send_subscribe(struct sip_dialog *dialog, int init) +{ + return sip_send_request(dialog, SIP_METHOD_SUBSCRIBE, FALSE, init, NULL); +} + +int sip_send_notify(struct sip_dialog *dialog, int init) +{ + return sip_send_request(dialog, SIP_METHOD_NOTIFY, FALSE, init, NULL); +} + +/* Notify a transferring party of the status of transfer (RFC3515) */ +int sip_send_notify_with_sipfrag(struct sip_dialog *dialog, uint32_t cseq, char *message, int terminate) +{ + struct sip_request request; + + sip_request_prepare(&request, dialog, SIP_METHOD_NOTIFY, 0, TRUE); + sip_request_build_header(&request, "Event", "refer;id=%d", cseq); + + sip_request_add_header(&request, "Subscription-state", terminate ? "terminated;reason=noresource" : "active"); + sip_request_add_header(&request, "Content-Type", "message/sipfrag;version=2.0"); + + sip_request_add_header(&request, "Allow", SIP_ALLOWED_METHODS); + sip_request_add_supported(&request, dialog); + + sip_request_build_content(&request, "SIP/2.0 %s\r\n", message); + + if (!dialog->initial_request.uri) { + sip_request_copy(&dialog->initial_request, &request); + + ast_debug(1, "Set initial %s request for '%s'\n", sip_methods[request.method].name, dialog->call_id); + } + + return sip_request_send(dialog, &request, SIP_SEND_RELIABLE, dialog->outgoing_cseq); +} + +/* Used in the SUBSCRIBE notification subsystem (RFC3265) */ +int sip_send_notify_with_extension_state(struct sip_dialog *dialog, struct ast_state_cb_info *state_info, int timeout) +{ + struct ast_str *content; + char *from, *to; + struct sip_request request; + + /* If the subscription has not yet been accepted do not send a NOTIFY */ + if (!ast_test_flag(&dialog->flags[1], SIP_DIALOG_ESTABLISHED)) { + return 0; + } + + sip_request_prepare(&request, dialog, SIP_METHOD_NOTIFY, 0, TRUE); + + switch (state_info->exten_state) { + case AST_EXTENSION_DEACTIVATED: + if (timeout) { + sip_request_add_header(&request, "Subscription-State", "terminated;reason=timeout"); + } else { + sip_request_add_header(&request, "Subscription-State", "terminated;reason=probation"); + sip_request_add_header(&request, "Retry-After", "60"); + } + + break; + case AST_EXTENSION_REMOVED: + sip_request_add_header(&request, "Subscription-State", "terminated;reason=noresource"); + break; + default: + if (dialog->expiry > 0 || ast_test_flag(&dialog->flags[2], SIP_SUBSCRIPTION_STATE_ACTIVE)) { + sip_request_add_header(&request, "Subscription-State", "active"); + } else { /* Expired */ + sip_request_add_header(&request, "Subscription-State", "terminated;reason=timeout"); + } + + break; + } + + switch (dialog->subscribe_events) { + case SIP_SUBSCRIBE_DIALOG_INFO_XML: + sip_request_add_header(&request, "Event", "dialog"); + sip_request_add_header(&request, "Content-Type", "application/dialog-info+xml"); + break; + case SIP_SUBSCRIBE_PIDF_XML: + sip_request_add_header(&request, "Event", "presence"); + sip_request_add_header(&request, "Content-Type", "application/pidf+xml"); + break; + default: + ast_log(LOG_WARNING, "Unknown subscribe event: %d\n", dialog->subscribe_events); + return 0; + } + + from = ast_strdupa(sip_request_get_header(&dialog->initial_request, "From")); + from = sip_remove_uri_parameters(sip_get_in_brackets(from)); + + to = ast_strdupa(sip_request_get_header(&dialog->initial_request, "To")); + to = sip_remove_uri_parameters(sip_get_in_brackets(to)); + + content = ast_str_alloca(4096); + + switch (dialog->subscribe_events) { + case SIP_SUBSCRIBE_DIALOG_INFO_XML: + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, + "\n", + dialog->dialog_version, to); + + if (state_info->exten_state > 0 && (state_info->exten_state & AST_EXTENSION_RINGING) && sip_config.notify_ringing) { + /* Twice the extension length should be enough for XML encoding */ + char local_display[AST_MAX_EXTENSION * 2], remote_display[AST_MAX_EXTENSION * 2], *local_uri, *remote_uri; + + /* 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 + * notifycid 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(to); + remote_uri = ast_strdupa(to); + + ast_xml_escape(dialog->exten, local_display, sizeof(local_display)); + ast_xml_escape(dialog->exten, remote_display, sizeof(remote_display)); + + /* 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. */ + if (sip_config.notify_callerid) { + RAII_VAR(struct ast_channel *, channel, NULL, ao2_cleanup); + + 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. */ + if (sip_config.pedantic_checking) { + ast_str_append(&content, 0, + "\n", + dialog->exten, dialog->call_id, dialog->remote_tag, dialog->local_tag); + } else { + ast_str_append(&content, 0, "\n", + dialog->exten, dialog->call_id); + } + + /* 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->exten); + } + } else { + ast_str_append(&content, 0, "\n", dialog->exten); + } + + if (state_info->exten_state & AST_EXTENSION_RINGING && sip_config.notify_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", to); + } + + ast_str_append(&content, 0, "\n\n"); + break; + case SIP_SUBSCRIBE_PIDF_XML: + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, + "\n", + from); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + if (state_info->exten_state & AST_EXTENSION_RINGING && sip_config.notify_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"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n", dialog->dialog_version); + + 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"); + ast_str_append(&content, 0, "\n"); + break; + default: + break; + } + + sip_request_add_content(&request, ast_str_buffer(content)); + + dialog->dialog_version++; + /* 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_extension_state_send() uses dialog->pending_invite_cseq for queuing control. Updates stall if + * pendinginvite <> 0. The most appropriate solution is to remove the subscription when the NOTIFY transaction fails. + * The client will re-subscribe after restarting or maxexpiry timeout. */ + return sip_request_send(dialog, &request, SIP_SEND_CRITICAL, dialog->outgoing_cseq); +} + +/* Notify user of messages waiting in voicemail (RFC3842) */ +int sip_send_notify_with_mwi(struct sip_dialog *dialog, int new_messages, int old_messages, const char *mwi_exten) +{ + struct sip_request request; + const char *user, *domain; + int port; + struct ast_str *content; + + sip_request_init(&request, dialog, SIP_METHOD_NOTIFY, NULL); + + sip_request_add_header(&request, "Event", "message-summary"); + sip_request_add_header(&request, "Content-Type", "application/simple-message-summary"); + + user = S_OR(mwi_exten, sip_config.mwi_exten); + /* domain initialization occurs here because sip_request_init changes ast_sockaddr_stringify string. */ + 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); + } + + content = ast_str_alloca(2048); + + ast_str_append(&content, 0, "Messages-Waiting: %s\r\n", new_messages ? "yes" : "no"); + + if (port != (dialog->socket.transport & AST_TRANSPORT_TLS ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT)) { + ast_str_append(&content, 0, "Message-Account: sip:%s@%s:%d;transport=%s\r\n", + user, domain, port, ast_transport2str(dialog->socket.transport)); + } else { + ast_str_append(&content, 0, "Message-Account: sip:%s@%s;transport=%s\r\n", + user, domain, ast_transport2str(dialog->socket.transport)); + } + + /* Cisco has a bug in the SIP stack where it can't accept the (0/0) notification. */ + ast_str_append(&content, 0, "Voice-Message: %d/%d%s\r\n", + new_messages, old_messages, (ast_test_flag(&dialog->flags[1], SIP_CISCO_USECALLMANAGER) ? "" : " (0/0)")); + + if (dialog->subscribe_events) { + if (dialog->expiry) { + sip_request_add_header(&request, "Subscription-State", "active"); + } else { /* Expired */ + sip_request_add_header(&request, "Subscription-State", "terminated;reason=timeout"); + } + } + + sip_request_add_content(&request, ast_str_buffer(content)); + + if (!dialog->initial_request.uri) { + sip_request_copy(&dialog->initial_request, &request); + + ast_debug(1, "Set initial %s request for '%s'\n", sip_methods[request.method].name, dialog->call_id); + } + + return sip_request_send(dialog, &request, SIP_SEND_RELIABLE, dialog->outgoing_cseq); +} + +int sip_send_refer(struct sip_dialog *dialog) +{ + dialog->refer_status = SIP_REFER_SENT; /* Set refer status */ + + return sip_send_request(dialog, SIP_METHOD_REFER, FALSE, SIP_INIT_NONE, NULL); +} + +/* Send an out-of-dialog SIP REFER message with content */ +int sip_send_refer_with_content(struct sip_dialog *dialog, const char *content_type, const char *content) +{ + /* Refer is outgoing call */ + ast_set_flag(&dialog->flags[0], SIP_OUTGOING); + + 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, SIP_TIMEOUT); + sip_dialog_set_already_gone(dialog); + + dialog->refer_status = SIP_REFER_SENT; + + return sip_send_request(dialog, SIP_METHOD_REFER, FALSE, SIP_INIT_REQUEST, NULL); +} + +/* Send SIP INFO with video update request */ +int sip_send_info_with_media_control(struct sip_dialog *dialog) +{ + struct sip_request request; + + sip_request_prepare(&request, dialog, SIP_METHOD_INFO, 0, TRUE); + sip_request_add_header(&request, "Content-Type", "application/media_control+xml"); + + sip_request_add_content(&request, "\n"); + sip_request_add_content(&request, "\n"); + sip_request_add_content(&request, "\n"); + sip_request_add_content(&request, "\n"); + sip_request_add_content(&request, "\n"); + sip_request_add_content(&request, "\n"); + + return sip_request_send(dialog, &request, SIP_SEND_RELIABLE, dialog->outgoing_cseq); +} + +/* Transmit with SIP MESSAGE method */ +int sip_send_message(struct sip_dialog *dialog, int init, int add_authorization) +{ + struct sip_request request; + const char *content_type; + struct ast_variable *header; + + if (init) { + sip_request_init(&request, dialog, SIP_METHOD_MESSAGE, NULL); + sip_request_copy(&dialog->initial_request, &request); + + ast_debug(1, "Set initial %s request for '%s'\n", sip_methods[request.method].name, dialog->call_id); + } else { + sip_request_prepare(&request, dialog, SIP_METHOD_MESSAGE, 0, TRUE); + } + + if (add_authorization) { + if (!sip_dialog_build_authorization(dialog, SIP_METHOD_MESSAGE)) { + sip_request_add_authorization(&request, dialog); + } else { + ast_log(LOG_WARNING, "No authentication available for '%s'\n", dialog->call_id); + } + } + + content_type = NULL; + + /* Add any additional MESSAGE headers. */ + for (header = dialog->message_headers; header; header = header->next) { + if (!strcasecmp(header->name, "Content-Type")) { + /* Save content type */ + content_type = header->value; + } else { + sip_request_add_header(&request, header->name, header->value); + } + } + + if (ast_strlen_zero(content_type)) { + /* "Content-Type" not set - use default value */ + content_type = "text/plain;charset=UTF-8"; + } + + sip_request_add_header(&request, "Content-Type", content_type); + sip_request_add_content(&request, dialog->message_content); + + return sip_request_send(dialog, &request, SIP_SEND_RELIABLE, dialog->outgoing_cseq); +} diff -durN asterisk-22.3.0.orig/channels/sip/response.c asterisk-22.3.0/channels/sip/response.c --- asterisk-22.3.0.orig/channels/sip/response.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/response.c 2025-04-17 11:11:22.822702982 +1200 @@ -0,0 +1,765 @@ +/* + * 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/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/sdp.h" +#include "include/utils.h" +#include "include/config.h" + +static int sip_response_alloc(struct sip_request *response, const char *message, int method); +static int __sip_send_response(struct sip_dialog *dialog, const char *message, struct sip_request *request, int reliable); +static int sip_response_needs_contact(struct sip_request *response); +static void sip_response_copy_via(struct sip_dialog *dialog, struct sip_request *request, const struct sip_request *orig_request); +static void sip_response_temp_dialog_free(void *data); + +AST_THREADSTORAGE_CUSTOM(sip_response_temp_dialog_buf, NULL, sip_response_temp_dialog_free); + +/* Initialize SIP response, based on SIP request */ +static int sip_response_alloc(struct sip_request *response, const char *status_line, int method) +{ + /* Initialize a response */ + memset(response, 0, sizeof(*response)); + + response->method = method; + response->response = TRUE; + + if (sscanf(status_line, "%30d", &response->code) != 1) { + return -1; + } + + response->status_line = ast_strdup(status_line); + + return 0; +} + +/* Transmit response on SIP request*/ +int sip_response_send(struct sip_dialog *dialog, struct sip_request *response, int reliable, uint32_t cseq) +{ + int res; + struct ast_str *data; + + if (!(data = sip_request_build(response))) { + return -1; + } + + if (sip_dialog_debug(dialog)) { + const struct ast_sockaddr *address = sip_dialog_get_address(dialog); + + ast_verbose("<--- SIP write to (%s%s) %s://%s --->\n%s<------------>\n", + sip_nat_mode2str(dialog->flags), reliable ? ", reliably" : "", + ast_transport2str(dialog->socket.transport), ast_sockaddr_stringify(address), ast_str_buffer(data)); + } + + if (dialog->record_history) { + sip_history_append(dialog, reliable ? "TxRespRel" : "TxResp", "%d %s", cseq, sip_methods[response->method].name); + } + + /* 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); + } + + if (reliable) { + res = sip_packet_send_reliable(dialog, response->method, cseq, response->code, data, reliable == SIP_SEND_CRITICAL); + } else { + res = sip_packet_send(dialog, data); + } + + ast_free(data); + sip_request_destroy(response); + + return res; +} + +/* Test if this response needs a contact header */ +static int sip_response_needs_contact(struct sip_request *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_PRACK: + 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: + case SIP_METHOD_PING: + default: + break; + } + + return FALSE; +} + +/* Copy SIP VIA Headers from the request to the response If the client indicates that it wishes to know the port we + * received from, it adds ;rport without an argument to the topmost via header. We need to add the port number (from our + * point of view) to that parameter. We always add ;received= to the topmost via header .*/ +static void sip_response_copy_via(struct sip_dialog *dialog, struct sip_request *request, const struct sip_request *orig_request) +{ + int iter; + const char *via; + char *first_via, *next_via, *rport; + + iter = 0; + via = sip_request_next_header(orig_request, "Via", &iter); + + if (ast_strlen_zero(via)) { + ast_log(LOG_NOTICE, "No Via header present to copy\n"); + return; + } + + /* Only work on first value */ + first_via = ast_strdupa(via); + + if ((next_via = strchr(first_via, ','))) { + *next_via++ = '\0'; + } + + /* Find ;rport; (empty request) */ + rport = strstr(first_via, ";rport"); + + if (rport && *(rport + 6) == '=') { + rport = NULL; /* We already have a parameter to rport */ + } + + if (ast_test_flag(&dialog->flags[0], SIP_NAT_FORCE_RPORT) || (rport && ast_test_flag(&dialog->flags[0], SIP_NAT_RPORT_PRESENT))) { + if ((rport = strstr(first_via, ";rport"))) { + /* We need to add received port - rport */ + char *sep; + + if ((sep = strchr(rport + 1, ';'))) { + memmove(rport, sep, strlen(sep) + 1); + } else { + *rport = '\0'; + } + } + + /* Add rport to first VIA header if requested */ + sip_request_build_header(request, "Via", "%s;received=%s;rport=%d%s%s", + first_via, ast_sockaddr_stringify_addr_remote(&dialog->received_address), + ast_sockaddr_port(&dialog->received_address), next_via ? "," : "", next_via ? next_via : ""); + } else { + /* We should *always* add a received to the topmost via */ + sip_request_build_header(request, "Via", "%s;received=%s%s%s", + first_via, ast_sockaddr_stringify_addr_remote(&dialog->received_address), + next_via ? "," : "", next_via ? next_via : ""); + } + + for (;;) { + via = sip_request_next_header(orig_request, "Via", &iter); + + if (ast_strlen_zero(via)) { + break; + } + + /* else add the following via headers untouched */ + sip_request_add_header(request, "Via", via); + } +} + +/* Prepare SIP response packet */ +int sip_response_prepare(struct sip_request *response, struct sip_dialog *dialog, const char *status_line, struct sip_request *request) +{ + const char *to; + int ttl; + + sip_response_alloc(response, status_line, request->method); + sip_response_copy_via(dialog, response, request); + + sip_request_copy_header(response, request, "From"); + to = sip_request_get_header(request, "To"); + + if (!strcasestr(to, "tag=") && response->code != 100) { + struct ast_str *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) && ast_test_flag(&dialog->flags[0], SIP_OUTGOING)) { + ast_str_append(&new_to, 0, ";tag=%s", dialog->remote_tag); + } else if (dialog->local_tag && !ast_test_flag(&dialog->flags[0], SIP_OUTGOING)) { + ast_str_append(&new_to, 0, ";tag=%s", dialog->local_tag); + } + + to = ast_strdupa(ast_str_buffer(new_to)); + } + + sip_request_add_header(response, "To", to); + + if (response->code >= 200 && response->code < 300 && + (response->method == SIP_METHOD_SUBSCRIBE || response->method == SIP_METHOD_REGISTER || + response->method == SIP_METHOD_PUBLISH)) { + if (dialog->expiry) { /* Only add contact if we have an expiry time */ + if (response->method == SIP_METHOD_SUBSCRIBE) { + sip_request_build_header(response, "Contact", "%s;expires=%d", dialog->our_contact, dialog->expiry); + } else { + sip_request_build_header(response, "Contact", "<%s>;expires=%d", dialog->full_contact, dialog->expiry); + } + } + + /* For registration responses, we also need expiry and contact info */ + sip_request_add_expires(response, dialog->expiry); + + if (response->method == SIP_METHOD_REGISTER && ast_test_flag(&dialog->flags[0], SIP_USE_PATH)) { + sip_request_copy_header(response, request, "Path"); + } + } else if (!ast_strlen_zero(dialog->our_contact) && sip_response_needs_contact(response)) { + sip_request_add_header(response, "Contact", dialog->our_contact); + } + + sip_request_copy_header(response, request, "Call-ID"); + sip_request_copy_header(response, request, "CSeq"); + + if (!ast_strlen_zero(sip_config.useragent)) { + sip_request_add_header(response, "Server", sip_config.useragent); + } + + sip_request_add_header(response, "Allow", SIP_ALLOWED_METHODS); + sip_request_add_supported(response, dialog); + + if (response->code >= 100 && response->code < 300) { + int iter = 0; + + for (;;) { + const char *record_route = sip_request_next_header(request, "Record-Route", &iter); + + if (ast_strlen_zero(record_route)) { + break; + } + + /* Add what we're responding to */ + sip_request_add_header(response, "Record-Route", record_route); + } + } + + /* 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->stimer && dialog->stimer->active) { + sip_request_build_header(response, "Session-Expires", "%d;refresher=%s", dialog->stimer->interval, + dialog->stimer->refresher == SIP_STIMER_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->stimer->refresher == SIP_STIMER_REFRESHER_UAS || + (dialog->stimer->refresher == SIP_STIMER_REFRESHER_UAC && dialog->stimer->remote_active)) { + sip_set_option_supported(&response->options, SIP_OPTION_TIMER); + } + } + + if (!ast_strlen_zero(dialog->html_url)) { + sip_request_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->received_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)) { + setsockopt(sip_socket_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); + } + + if (!ast_sockaddr_port(&dialog->address)) { + ast_sockaddr_set_port(&dialog->address, SIP_STANDARD_PORT); + } + } + } + + return 0; +} + +/* Base transmit response function */ +static int __sip_send_response(struct sip_dialog *dialog, const char *status_line, struct sip_request *request, int reliable) +{ + struct sip_request response; + + sip_response_prepare(&response, dialog, status_line, request); + + if (ast_test_flag(&dialog->flags[0], SIP_SEND_REMOTE_PARTY_ID) && + ast_test_flag(&dialog->flags[1], SIP_CONNECTEDLINE_UPDATE_PENDING) && (response.code == 180 || response.code == 183)) { + ast_clear_flag(&dialog->flags[1], SIP_CONNECTEDLINE_UPDATE_PENDING); + sip_request_add_remote_party_id(&response, dialog); + } + + if (response.method == SIP_METHOD_INVITE) { + sip_request_add_call_info(&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_request_add_diversion(&response, dialog); + } + + /* 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) { + if (ast_test_flag(&dialog->flags[1], SIP_Q850_REASON)) { + 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_request_build_header(&response, "Reason", "Q.850;cause=%i", hangupcause & 0x7f); + } + } + } + + return sip_response_send(dialog, &response, reliable, request->cseq); +} + +/* Transmit response, no resendmits */ +int sip_send_response(struct sip_dialog *dialog, const char *status_line, struct sip_request *request) +{ + return __sip_send_response(dialog, status_line, request, SIP_SEND_UNRELIABLE); +} + +/* Transmit response, no resendmits */ +int sip_send_response_with_unsupported(struct sip_dialog *dialog, const char *status_line, struct sip_request *request, + const char *unsupported) +{ + struct sip_request response; + + sip_response_prepare(&response, dialog, status_line, request); + sip_request_add_date(&response); + + sip_request_add_header(&response, "Unsupported", unsupported); + + return sip_response_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + +/* Transmit 422 response with Min-SE header (Session-Timers) */ +int sip_send_response_with_min_se(struct sip_dialog *dialog, const char *status_line, struct sip_request *request, int min_se) +{ + struct sip_request response; + + sip_response_prepare(&response, dialog, status_line, request); + sip_request_add_date(&response); + + sip_request_build_header(&response, "Min-SE", "%d", min_se); + + return sip_response_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + +/* 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_send_response_reliable(struct sip_dialog *dialog, const char *status_line, struct sip_request *request) +{ + return __sip_send_response(dialog, status_line, request, request->ignore ? SIP_SEND_UNRELIABLE : SIP_SEND_CRITICAL); +} + +/* Append Retry-After header field when transmitting response */ +int sip_send_response_with_retry_after(struct sip_dialog *dialog, const char *status_line, struct sip_request *request, int retry_after) +{ + struct sip_request response; + + sip_response_prepare(&response, dialog, status_line, request); + sip_request_build_header(&response, "Retry-After", "%d", retry_after); + + return sip_response_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + +/* Add date before transmitting response */ +int sip_send_response_with_date(struct sip_dialog *dialog, const char *status_line, struct sip_request *request) +{ + struct sip_request response; + + sip_response_prepare(&response, dialog, status_line, request); + sip_request_add_date(&response); + + return sip_response_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + +/* Append Accept header, content length before transmitting response */ +int sip_send_response_with_accept(struct sip_dialog *dialog, const char *status_line, struct sip_request *request) +{ + struct sip_request response; + + sip_response_prepare(&response, dialog, status_line, request); + sip_request_add_header(&response, "Accept", "application/sdp"); + + return sip_response_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + +/* Append Min-Expires header, content length before transmitting response */ +int sip_send_response_with_min_expires(struct sip_dialog *dialog, const char *status_line, struct sip_request *request, int min_expires) +{ + struct sip_request response; + + sip_response_prepare(&response, dialog, status_line, request); + sip_request_build_header(&response, "Min-Expires", "%d", min_expires); + + return sip_response_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + +/* Respond with authorization request */ +int sip_send_response_with_www_authenticate(struct sip_dialog *dialog, const char *status_line, struct sip_request *request, + int reliable, int stale) +{ + struct sip_request response; + + /* Choose Realm */ + sip_dialog_set_realm(dialog, request); + sip_response_prepare(&response, dialog, status_line, request); + + /* Stale means that they sent us correct authentication, but based it on an old challenge (nonce) */ + sip_request_build_header(&response, "WWW-Authenticate", "Digest algorithm=MD5,realm=\"%s\",nonce=\"%s\"%s", + dialog->realm, dialog->nonce, stale ? ",stale=true" : ""); + + sip_history_append(dialog, "AuthChal", "Auth challenge sent for %s (nonce count %d)", dialog->username, dialog->nonce_count); + + return sip_response_send(dialog, &response, reliable, request->cseq); +} + +int sip_send_response_with_authorization_failure(struct sip_dialog *dialog, struct sip_request *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_BAD_TRANSPORT: + case SIP_AUTHORIZATION_ACL_FAILED: + ast_log(LOG_NOTICE, "Failed to authenticate device %s for %s, code %d\n", + sip_request_get_header(request, "From"), sip_methods[dialog->method].name, res); + status_line = "403 Forbidden"; + + break; + case SIP_AUTHORIZATION_SESSION_LIMIT: + /* Unexpected here, actually. As it's handled elsewhere. */ + ast_log(LOG_NOTICE, "Call limit reached for device %s for %s, code %d\n", + sip_request_get_header(request, "From"), sip_methods[dialog->method].name, res); + 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_log(LOG_NOTICE, "RTP init failure for device %s for %s, code %d\n", + sip_request_get_header(request, "From"), sip_methods[dialog->method].name, res); + + status_line = "503 Service Unavailable"; + + break; + case SIP_AUTHORIZATION_SUCCESSFUL: + case SIP_AUTHORIZATION_CHALLENGE_SENT: + /* These should have been handled elsewhere. */ + default: + ast_log(LOG_NOTICE, "Unexpected error for device %s for %s, code %d\n", + sip_request_get_header(request, "From"), sip_methods[dialog->method].name, res); + status_line = "503 Service Unavailable"; + + break; + } + + if (reliable == SIP_SEND_RELIABLE) { + return sip_send_response_reliable(dialog, status_line, request); + } else { + return sip_send_response(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_send_response_with_fake_authorization(struct sip_dialog *dialog, struct sip_request *request) +{ + char *authorization; + /* We have to emulate EXACTLY what we'd get with a good peer * and a bad password, or else we leak information. */ + struct sip_authorization_data authorization_data; + + authorization = ast_strdupa(sip_request_get_header(request, "Authorization")); + + 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_send_response_with_www_authenticate(dialog, "401 Unauthorized", request, SIP_SEND_UNRELIABLE, FALSE); + /* Schedule auto destroy in 32 seconds (according to RFC 3261) */ + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + 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_send_response_with_www_authenticate(dialog, "401 Unauthorized", request, SIP_SEND_UNRELIABLE, FALSE); + + /* Schedule auto destroy in 32 seconds */ + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + return 0; + } + + sip_parse_authorization(authorization, &authorization_data); + + /* Verify nonce from request matches our nonce. If not, send 401 with new nonce */ + if (strcmp(dialog->nonce, S_OR(authorization_data.nonce, ""))) { + if (!request->ignore) { + sip_dialog_build_nonce(dialog, TRUE); + } + + sip_send_response_with_www_authenticate(dialog, "401 Unauthorized", request, SIP_SEND_UNRELIABLE, FALSE); + /* Schedule auto destroy in 32 seconds */ + sip_dialog_sched_destroy(dialog, SIP_DEFAULT_TIMEOUT); + + } else { + __sip_send_response(dialog, "403 Forbidden", &dialog->initial_request, SIP_SEND_UNRELIABLE); + } + + return 0; +} + +/* Respond with an optionind response */ +int sip_send_response_with_optionsind(struct sip_dialog *dialog, struct sip_request *request) +{ + struct sip_request response; + + sip_response_prepare(&response, dialog, "200 OK", request); + + sip_request_add_header(&response, "Content-Type", "application/x-cisco-remotecc-response+xml"); + sip_request_add_date(&response); + + sip_request_add_content(&response, "\n"); + sip_request_add_content(&response, "\n"); + sip_request_add_content(&response, "\n"); + sip_request_add_content(&response, "200\n"); + sip_request_add_content(&response, "\n"); + sip_request_add_content(&response, "\n"); + sip_request_add_content(&response, "\n"); + sip_request_add_content(&response, "\n"); + sip_request_add_content(&response, "\n"); + sip_request_add_content(&response, "\n"); + sip_request_add_content(&response, "\n"); + sip_request_add_content(&response, "\n"); + sip_request_add_content(&response, "\n"); + sip_request_add_content(&response, "\n"); + sip_request_add_content(&response, "\n"); + sip_request_add_content(&response, "\n"); + + return sip_response_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + +int sip_send_response_provisional(struct sip_dialog *dialog, const char *status_line, struct sip_request *request, int with_sdp) +{ + int res; + + if (with_sdp) { + res = sip_send_response_with_sdp(dialog, status_line, request, SIP_SEND_UNRELIABLE, FALSE, FALSE); + } else { + res = sip_send_response(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_send_response_with_sdp(struct sip_dialog *dialog, const char *status_line, struct sip_request *request, + int reliable, int old_sdp, int add_remote_party_id) +{ + struct sip_request response; + + sip_response_prepare(&response, dialog, status_line, request); + + if (add_remote_party_id) { + sip_request_add_remote_party_id(&response, dialog); + sip_request_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, TRUE, dialog->t38_state == SIP_T38_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 */ + } + + sip_request_add_require(&response); + + return sip_response_send(dialog, &response, reliable, request->cseq); +} + +int sip_send_response_with_remote_party_id(struct sip_dialog *dialog, const char *status_line, struct sip_request *request) +{ + struct sip_request response; + + sip_response_prepare(&response, dialog, status_line, request); + sip_request_add_remote_party_id(&response, dialog); + + return sip_response_send(dialog, &response, SIP_SEND_UNRELIABLE, request->cseq); +} + +int sip_send_response_with_feature_event(struct sip_dialog *dialog, int feature, struct sip_request *request) +{ + struct sip_request response; + + sip_response_prepare(&response, dialog, "200 OK", request); + + sip_request_add_header(&response, "Content-Type", "application/x-as-feature-event+xml"); + sip_request_add_content(&response, "\n"); + + if (feature == SIP_FEATURE_DO_NOT_DISTURB) { + sip_request_add_content(&response, + "\n"); + } else if (feature == SIP_FEATURE_CALL_FORWARD) { + sip_request_add_content(&response, + "\n"); + } + + return sip_response_send(dialog, &response, SIP_SEND_UNRELIABLE, 0); +} + +static void sip_response_temp_dialog_free(void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + ast_string_field_free_memory(dialog); + ast_free(dialog); +} + +/* Transmit response, no resendmits, using a temporary dialog structure */ +int sip_send_response_using_temp(struct sip_socket *socket, const char *status_line, struct sip_request *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. Here we should state clearly how we should reinitialize it before + * using it. E.g. certainly the threadstorage should be left alone, but other thihngs such as flags etc. maybe + * need cleanup? */ + 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); + ast_sockaddr_copy(&dialog->received_address, &socket->address); + + sip_get_our_address(dialog->socket.transport, &dialog->address, &dialog->our_address); + + dialog->method = request->method; + dialog->outgoing_cseq = SIP_INITIAL_CSEQ; + + ast_string_field_build(dialog, branch, "%s%08x", SIP_MAGIC_COOKIE, (unsigned int) ast_random()); + sip_dialog_build_local_tag(dialog); + + ast_copy_flags(&dialog->flags[0], &sip_config.flags[0], SIP_NAT_FORCE_RPORT); + ast_copy_flags(&dialog->flags[2], &sip_config.flags[2], SIP_NAT_AUTO_RPORT); + + sip_dialog_check_via(dialog, request); + + ast_string_field_set(dialog, from_domain, sip_config.from_domain); + dialog->from_domain_port = sip_config.from_domain_port; + + ast_string_field_set(dialog, call_id, request->call_id); + + /* Use this temporary dialog structure to send the status_line */ + __sip_send_response(dialog, status_line, request, SIP_SEND_UNRELIABLE); + + /* Free the string fields, but not the pool space */ + ast_string_field_init(dialog, 0); + + return 0; +} diff -durN asterisk-22.3.0.orig/channels/sip/route.c asterisk-22.3.0/channels/sip/route.c --- asterisk-22.3.0.orig/channels/sip/route.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/route.c 2025-04-17 11:11:22.822702982 +1200 @@ -0,0 +1,201 @@ +/* + * 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/request.h" +#include "include/proxy.h" +#include "include/dialog.h" +#include "include/utils.h" + +const char *sip_route_add(struct sip_route *route, const char *uri, int insert_head) +{ + struct sip_route_hop *hop; + + /* Expand len to include null terminator */ + if (!(hop = ast_calloc(1, sizeof(*hop)))) { + return NULL; + } + + hop->uri = ast_strdup(uri); + + if (insert_head) { + AST_LIST_INSERT_HEAD(&route->hops, hop, next); + route->type = SIP_ROUTE_INVALIDATED; + } else { + if (sip_route_empty(route)) { + route->type = SIP_ROUTE_INVALIDATED; + } + + AST_LIST_INSERT_TAIL(&route->hops, hop, next); + } + + return hop->uri; +} + +void sip_route_parse(struct sip_route *route, const char *orig_header, int insert_head) +{ + char *header; + + if (!route) { + return; + } + + header = ast_strdup(orig_header); + + while (!ast_strlen_zero(header)) { + char *uri; + + if (!(uri = strchr(header, '<'))) { + break; + } + + uri += 1; + + if (!(header = strchr(uri, '>'))) { + break; + } + + *header++ = '\0'; + + if (sip_route_add(route, uri, insert_head)) { + ast_debug(2, "Parsing hop: <%s>\n", uri); + } + } +} + +void sip_route_copy(struct sip_route *to_route, const struct sip_route *from_route) +{ + struct sip_route_hop *hop; + + /* make sure dst is empty */ + sip_route_clear(to_route); + + AST_LIST_TRAVERSE(&from_route->hops, hop, next) { + const char *uri; + + if ((uri = sip_route_add(to_route, hop->uri, FALSE))) { + ast_debug(2, "Copied hop: <%s>\n", uri); + } + } + + to_route->type = from_route->type; +} + +void sip_route_clear(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; +} + +void sip_route_dump(const struct sip_route *route) +{ + if (sip_route_empty(route)) { + ast_debug(1, "No route/path\n"); + } else { + struct sip_route_hop *hop; + + AST_LIST_TRAVERSE(&route->hops, hop, next) { + ast_debug(1, "Route/path hop: <%s>\n", hop->uri); + } + } +} + +struct ast_str *sip_route_list(const struct sip_route *route, int cli_format, int skip) +{ + struct sip_route_hop *hop; + const char *comma; + struct ast_str *path; + int pos; + + if ((path = ast_str_create(64)) == NULL) { + return NULL; + } + + comma = cli_format ? ", " : ","; + pos = 0; + + AST_LIST_TRAVERSE(&route->hops, hop, next) { + if (pos >= skip) { + ast_str_append(&path, 0, "%s<%s>", ast_str_strlen(path) ? comma : "", hop->uri); + } + + pos++; + } + + if (cli_format && !ast_str_strlen(path)) { + ast_str_append(&path, 0, "N/A"); + } + + 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_INVALIDATED) { + struct sip_route_hop *hop = AST_LIST_FIRST(&route->hops); + + if (hop && !strstr(hop->uri, ";lr")) { + route->type = SIP_ROUTE_STRICT; + } else { + route->type = SIP_ROUTE_LOOSE; + } + } + + 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 -durN asterisk-22.3.0.orig/channels/sip/rtp_glue.c asterisk-22.3.0/channels/sip/rtp_glue.c --- asterisk-22.3.0.orig/channels/sip/rtp_glue.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/rtp_glue.c 2025-04-17 11:11:22.822702982 +1200 @@ -0,0 +1,360 @@ +/* + * 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/request.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/config.h" +#include "include/rtp_glue.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 *acl; + int allow; + + if (!(dialog = ast_channel_tech_pvt(channel))) { + return FALSE; + } + + ao2_lock(dialog); + + if (dialog->peer && dialog->peer->direct_media_acl) { + acl = ast_duplicate_acl_list(dialog->peer->direct_media_acl); + } else { + acl = NULL; + } + + ao2_unlock(dialog); + + if (!acl) { + return TRUE; + } + + allow = TRUE; + + if (ast_test_flag(&dialog->flags[0], SIP_DIRECT_MEDIA)) { + 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(acl, &remote_address, "SIP Direct Media ACL: ") == 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)); + + ast_debug(3, "Reinvite %s to %s denied by directmedia ACL on %s\n", type, remote_host, local_host); + + allow = FALSE; + } + } + + ast_free_acl_list(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; + } + + ao2_ref(dialog->audio_rtp, +1); + + *audio_rtp = dialog->audio_rtp; + res = AST_RTP_GLUE_RESULT_LOCAL; + + if (ast_test_flag(&dialog->flags[0], SIP_DIRECT_MEDIA)) { + res = AST_RTP_GLUE_RESULT_REMOTE; + } else if (ast_test_flag(&dialog->flags[0], 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; + } + + if (ast_test_flag(&dialog->flags[1], SIP_T38_SUPPORT)) { + switch (dialog->t38_state) { + case SIP_T38_LOCAL_REINVITE: + case SIP_T38_PEER_REINVITE: + case SIP_T38_ENABLED: + res = AST_RTP_GLUE_RESULT_LOCAL; + break; + case SIP_T38_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; + } + + ao2_ref(dialog->video_rtp, +1); + + *video_rtp = dialog->video_rtp; + res = AST_RTP_GLUE_RESULT_FORBID; + + if (ast_test_flag(&dialog->flags[0], SIP_DIRECT_MEDIA)) { + res = AST_RTP_GLUE_RESULT_REMOTE; + } + + 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; + } + + ao2_ref(dialog->text_rtp, +1); + + *text_rtp = dialog->text_rtp; + res = AST_RTP_GLUE_RESULT_FORBID; + + if (ast_test_flag(&dialog->flags[0], SIP_DIRECT_MEDIA)) { + res = AST_RTP_GLUE_RESULT_REMOTE; + } + + 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 private is not owned by 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 && !ast_test_flag(&dialog->flags[0], 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 (ast_test_flag(&dialog->flags[0], SIP_DIRECT_MEDIA_OUTGOING) && !dialog->outgoing_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. */ + ast_clear_flag(&dialog->flags[0], SIP_DIRECT_MEDIA_OUTGOING); + ao2_unlock(dialog); + + return 0; + } + + if (changed && !ast_test_flag(&dialog->flags[0], SIP_GOT_REFER) && !ast_test_flag(&dialog->flags[0], SIP_DEFER_BYE_ON_TRANSFER)) { + if (ast_channel_state(channel) != AST_STATE_UP) { /* We are in early state */ + if (dialog->record_history) { + sip_history_append(dialog, "ExtInv", "Initial invite sent with remote bridge proposal."); + } + + 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_send_reinvite_with_sdp(dialog, FALSE, FALSE); + } else if (!ast_test_flag(&dialog->flags[0], SIP_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 */ + ast_set_flag(&dialog->flags[0], SIP_NEED_REINVITE); + } + } + + /* Reset lastrtprx timer */ + dialog->last_rtp_received = dialog->last_rtp_sent = time(NULL); + ao2_unlock(dialog); + + return 0; +} diff -durN asterisk-22.3.0.orig/channels/sip/sdp.c asterisk-22.3.0/channels/sip/sdp.c --- asterisk-22.3.0.orig/channels/sip/sdp.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/sdp.c 2025-04-17 11:11:22.823702956 +1200 @@ -0,0 +1,2532 @@ + /* + * 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/request.h" +#include "include/proxy.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_RTPMAP_CODECS 32 /* Maximum number of codecs allowed in received SDP */ + +static void sip_sdp_media_address(struct sip_dialog *dialog, int add_video, int add_text, struct ast_sockaddr *audio_address, + struct ast_sockaddr *video_address, struct ast_sockaddr *text_address); +static void sip_sdp_remove_unused_crypto(struct ast_sdp_srtp **sdp_srtp); +static const char *sip_sdp_iterator_next(struct sip_request *request, 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_a_audio(const char *a_line, struct sip_dialog *dialog, struct ast_rtp_codecs *audio_rtp, + int *audio_codec, int *rtpmap_codecs); +static int sip_sdp_parse_a_video(const char *a_line, struct sip_dialog *dialog, struct ast_rtp_codecs *video_rtp, + int *video_codec, int *rtpmap_codecs); +static int sip_sdp_parse_a_text(const char *a_line, struct sip_dialog *dialog, struct ast_rtp_codecs *text_rtp, + char *red_fmtp, int *red_num_gen, int *red_data_pt, int *rtpmap_codecs); +static int sip_sdp_parse_a_image(const char *a_line, struct sip_dialog *dialog); +static int sip_sdp_parse_a_send_recv(const char *a_aline, int *send_recv); +static int sip_sdp_parse_a_crypto(const char *a_line, struct sip_dialog *dialog, struct ast_rtp_instance *rtp, + struct ast_sdp_srtp **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_packet_size, int *max_packet_size); +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 char *sip_sdp_get_a_send_recv(struct sip_dialog *dialog); +static char *sip_sdp_get_a_crypto(struct ast_sdp_srtp *sdp_srtp, int tag_len_32); + +static struct sip_sdp_media *sip_sdp_media_alloc(struct sip_dialog *dialog); +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 remote_rtcp_mux); +static void sip_sdp_change_ice_rtcp_mux(struct sip_dialog *dialog, struct ast_rtp_instance *rtp, int remote_rtcp_mux); +static void sip_sdp_start_ice(struct ast_rtp_instance *rtp, int is_offer); + +/* Determine whether a SIP message contains an SDP in its body Also updates request->sdp_start and request->sdp_end to + * indicate where the SDP lives in the message body. */ +int sip_sdp_find(struct sip_request *request) +{ + const char *content_type; + char *boundary; + int sdp, headers, boundary_len, i; + + /* Content-Length of zero means there can't possibly be an SDP here, even if the Content-Type says there is */ + if (!atoi(sip_request_get_header(request, "Content-Length"))) { + return FALSE; + } + + content_type = sip_request_get_header(request, "Content-Type"); + + /* if the body contains only SDP, this is easy */ + if (!strncasecmp(content_type, "application/sdp", 15)) { + request->sdp_start = 0; + request->sdp_end = request->line_count; + + return request->sdp_end > 0; + } + + /* if it's not multipart/mixed, there cannot be an SDP */ + if (strncasecmp(content_type, "multipart/mixed", 15)) { + return FALSE; + } + + /* if there is no boundary marker, it's invalid */ + if ((boundary = strcasestr(content_type, ";boundary="))) { + boundary += 10; + } else if ((boundary = strcasestr(content_type, "; boundary="))) { + boundary += 11; + } else { + return FALSE; + } + + boundary = ast_strdupa(boundary); + + /* If the boundary is quoted with ", remove quotes */ + if (*boundary == '\"') { + boundary++; + boundary = strsep(&boundary, "\""); + } else { + boundary = strsep(&boundary, ";"); + } + + headers = FALSE; + sdp = FALSE; + boundary_len = strlen(boundary); + + /* search for the boundary marker, the empty line delimiting headers from sdp part and the end boundry if it exists */ + for (i = 0; i < request->line_count; i++) { + if (!strncmp(request->line[i], "--", 2) && !strncmp(request->line[i] + 2, boundary, boundary_len)) { + if (sdp && headers) { + request->sdp_end = i - 1; + return TRUE; + } + + sdp = FALSE; + continue; + } + + if (!strncasecmp(request->line[i], "Content-Type: application/sdp", 30)) { + sdp = TRUE; + } else if (ast_strlen_zero(request->line[i])) { + if (sdp && !headers) { + request->sdp_start = i + 1; + headers = TRUE; + } + } + } + + return FALSE; +} + +/* Set all IP media addresses for this call */ +static void sip_sdp_media_address(struct sip_dialog *dialog, int add_video, int add_text, struct ast_sockaddr *audio_address, + struct ast_sockaddr *video_address, struct ast_sockaddr *text_address) +{ + int use_external; + struct ast_sockaddr address; + + /* First, get our address */ + ast_rtp_instance_get_local_address(dialog->audio_rtp, &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. */ + use_external = ast_sockaddr_cmp_addr(&dialog->our_address, &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 (!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 { + /* 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) && !use_external) { + 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) { + /* Determine video destination */ + if (!ast_sockaddr_isnull(&dialog->video_redirect_address)) { + ast_sockaddr_copy(video_address, &dialog->video_redirect_address); + } else { + if (dialog->video_rtp) { + 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) && !use_external) { + 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) { + /* Determine text destination */ + if (!ast_sockaddr_isnull(&dialog->text_redirect_address)) { + ast_sockaddr_copy(text_address, &dialog->text_redirect_address); + } else { + if (dialog->text_rtp) { + 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) && !use_external) { + 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) +{ + struct ast_sdp_srtp *next_sdp_srtp; + + if (!*sdp_srtp) { + return; + } + + /* Delete all but the first crypto line */ + if ((next_sdp_srtp = AST_LIST_NEXT(*sdp_srtp, sdp_srtp_list))) { + AST_LIST_NEXT(*sdp_srtp, sdp_srtp_list) = NULL; + ast_sdp_srtp_destroy(next_sdp_srtp); + } +} + +static const char *sip_sdp_iterator_next(struct sip_request *request, int *iter, int no_m_line) +{ + const char *line; + + if (!*iter) { + *iter = request->sdp_start; + } else if (*iter >= request->sdp_end) { + return NULL; + } + + line = request->line[*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_request *request, int add_image, int is_offer) +{ + 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, rtpmap_codecs, send_recv, rtcp_mux_audio, rtcp_mux_video, secure_audio, + secure_video, red_data_pt[10], red_num_gen, iter; + struct ast_sockaddr session_address, audio_address, video_address, text_address, image_address; + 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 */ + struct ast_rtp_codecs audio_rtp, video_rtp, text_rtp; + char red_fmtp[16]; /* For T.140 RED, actual attribute+codec is set by sip_sdp_parse_a_image */ + struct ast_str *format_names; + const char *line; + struct sip_sdp_media *sdp_media; + + res = -1; + + /* Initial check */ + if (!dialog->audio_rtp) { + ast_log(LOG_ERROR, "Got SDP but have no RTP allocated\n"); + goto cleanup; + } + + 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_rtp, 0, sizeof(audio_rtp)); + memset(&video_rtp, 0, sizeof(video_rtp)); + memset(&text_rtp, 0, sizeof(text_rtp)); + + if (ast_rtp_codecs_payloads_initialize(&audio_rtp) || + ast_rtp_codecs_payloads_initialize(&video_rtp) || + ast_rtp_codecs_payloads_initialize(&text_rtp)) { + goto cleanup; + } + + /* Update our last rtprx when we receive an SDP, too */ + dialog->last_rtp_received = dialog->last_rtp_sent = time(NULL); + sip_sdp_media_destroy(dialog); + + rtpmap_codecs = 0; + audio_codec = 255; + video_codec = 255; + + red_num_gen = 0; + /* For T.140 RED, actual attribute+codec is set by sip_sdp_parse_a_image */ + 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; + + secure_audio = FALSE; + secure_video = FALSE; + send_recv = SIP_SDP_SEND_UNKNOWN; + + rtcp_mux_audio = FALSE; + rtcp_mux_video = FALSE; + + /* Scan session level SDP parameters (lines before first media stream) */ + iter = 0; + + while ((line = sip_sdp_iterator_next(request, &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)) { + parsed = TRUE; + + 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); + } + } else if (line[0] == 'a') { + if (sip_sdp_parse_a_audio(line, dialog, &audio_rtp, &audio_codec, &rtpmap_codecs)) { + parsed = TRUE; + } else if (sip_sdp_parse_a_video(line, dialog, &video_rtp, &video_codec, &rtpmap_codecs)) { + parsed = TRUE; + } else if (sip_sdp_parse_a_text(line, dialog, &text_rtp, red_fmtp, &red_num_gen, red_data_pt, &rtpmap_codecs)) { + parsed = TRUE; + } else if (sip_sdp_parse_a_image(line, dialog)) { + parsed = TRUE; + } else if (sip_sdp_parse_a_send_recv(line, &send_recv)) { + 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: 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_iterator_next(request, &iter, FALSE))) { + int type, has_crypto, rtcp_mux, mux_iter, codecs_len; + unsigned int codec, port, ports; + char protocol[32]; + const char *codecs , *mux_line; + + if (line[0] != 'm') { + ast_log(LOG_WARNING, "Invalid SIP line (not m=): '%s'\n", line); + break; + } + + /* We need to check for this ahead of time, this is terrible */ + mux_iter = iter; + rtcp_mux = FALSE; + + while ((mux_line = sip_sdp_iterator_next(request, &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))) { + ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer list\n"); + goto cleanup; + } + + type = AST_MEDIA_TYPE_UNKNOWN; + has_crypto = FALSE; + + ports = 0; + protocol[0] = '\0'; + + /* Check for 'audio' media offer */ + if (dialog->audio_rtp && !strncmp(line, "m=audio ", 8)) { + if (sscanf(line, "m=audio %30u/%30u %31s %n", &port, &ports, protocol, &codecs_len) == 3 || + sscanf(line, "m=audio %30u %31s %n", &port, protocol, &codecs_len) == 2) { + codecs = line + codecs_len; + + /* produce zero-port m-line since it may be needed */ + ast_str_set(&sdp_media->decline_m_line, 0, "m=audio 0 %s %s\r\n", protocol, codecs); + + if (port == 0) { + ast_debug(1, "Ignoring audio media offer because port number is zero\n"); + continue; + } + + if (sip_sdp_media_has_type(dialog, AST_MEDIA_TYPE_AUDIO)) { + ast_log(LOG_WARNING, "Declining non-primary audio stream: %s\n", line); + continue; + } + + /* Check number of ports offered for stream */ + if (ports > 1) { + ast_log(LOG_WARNING, "%u ports offered for audio media, not supported. Will try anyway\n", ports); + } + + if ((!strcmp(protocol, "RTP/SAVPF") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) && + !ast_test_flag(&dialog->flags[2], SIP_USE_AVPF)) { + if (!request->response) { + ast_log(LOG_NOTICE, + "Received SAVPF profle in audio offer but AVPF is not enabled, enabling: %s\n", + line); + + secure_audio = TRUE; + ast_set_flag(&dialog->flags[2], SIP_USE_AVPF); + } else { + ast_log(LOG_WARNING, + "Received SAVPF profle in audio answer but AVPF is not enabled: %s\n", + line); + continue; + } + } else if ((!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVP")) && + ast_test_flag(&dialog->flags[2], SIP_USE_AVPF)) { + if (!request->response) { + ast_log(LOG_NOTICE, + "Received SAVP profle in audio offer but AVPF is enabled, disabling: %s\n", + line); + + secure_audio = TRUE; + ast_clear_flag(&dialog->flags[2], SIP_USE_AVPF); + } else { + ast_log(LOG_WARNING, + "Received SAVP profile in audio offer but AVPF is enabled: %s\n", line); + continue; + } + } else if (!strcmp(protocol, "UDP/TLS/RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) { + secure_audio = TRUE; + has_crypto = TRUE; + + if (dialog->secure_audio_rtp) { + ast_set_flag(dialog->secure_audio_rtp, AST_SRTP_CRYPTO_OFFER_OK); + } + } else if (!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "RTP/SAVPF")) { + secure_audio = TRUE; + } else if (!strcmp(protocol, "RTP/AVPF") && !ast_test_flag(&dialog->flags[2], SIP_USE_AVPF)) { + if (!request->response) { + ast_log(LOG_NOTICE, + "Received AVPF profile in audio offer but AVPF is not enabled, enabling: %s\n", + line); + ast_set_flag(&dialog->flags[2], SIP_USE_AVPF); + } else { + ast_log(LOG_WARNING, + "Received AVP profile in audio answer but AVPF is enabled: %s\n", line); + continue; + } + } else if (!strcmp(protocol, "RTP/AVP") && ast_test_flag(&dialog->flags[2], SIP_USE_AVPF)) { + if (!request->response) { + ast_log(LOG_NOTICE, + "Received AVP profile in audio answer but AVPF is enabled, disabling: %s\n", line); + ast_clear_flag(&dialog->flags[2], SIP_USE_AVPF); + } else { + ast_log(LOG_WARNING, + "Received AVP profile in audio answer but AVPF is enabled: %s\n", line); + continue; + } + } else if (strcmp(protocol, "RTP/AVP") && strcmp(protocol, "RTP/AVPF")) { + ast_log(LOG_WARNING, "Unknown RTP profile in audio offer: %s\n", line); + continue; + } + + /* 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); + goto cleanup; + } + + ast_debug(1, "Found RTP audio format %u\n", codec); + ast_rtp_codecs_payloads_set_m_type(&audio_rtp, NULL, codec); + } + + type = AST_MEDIA_TYPE_AUDIO; + audio_port = port; + } else { + ast_log(LOG_WARNING, "Rejecting audio media offer due to invalid or unsupported syntax: %s\n", line); + goto cleanup; + } + /* Check for 'video' media offer */ + } else if (dialog->video_rtp && !strncmp(line, "video ", 8)) { + if (sscanf(line, "m=video %30u/%30u %31s %n", &port, &ports, protocol, &codecs_len) == 3 || + (sscanf(line, "m=video %30u %31s %n", &port, protocol, &codecs_len) == 2)) { + codecs = line + codecs_len; + + /* produce zero-port m-line since it may be needed later */ + ast_str_set(&sdp_media->decline_m_line, 0, "m=video 0 %s %s\r\n", protocol, codecs); + + if (port == 0) { + ast_debug(1, "Ignoring video stream offer because port number is zero\n"); + continue; + } + + /* Check number of ports offered for stream */ + if (ports > 1) { + ast_log(LOG_WARNING, "%u ports offered for video stream, not supported. Will try anyway\n", ports); + } + + if (sip_sdp_media_has_type(dialog, AST_MEDIA_TYPE_VIDEO)) { + ast_log(LOG_WARNING, "Declining non-primary video stream: %s\n", line); + continue; + } + + if ((!strcmp(protocol, "RTP/SAVPF") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) && + !ast_test_flag(&dialog->flags[2], SIP_USE_AVPF)) { + ast_log(LOG_WARNING, "Received SAVPF profle in video offer but AVPF is not enabled: %s\n", line); + continue; + } else if ((!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVP")) && + ast_test_flag(&dialog->flags[2], SIP_USE_AVPF)) { + ast_log(LOG_WARNING, "Received SAVP profile in video offer but AVPF is enabled: %s\n", line); + continue; + } else if (!strcmp(protocol, "UDP/TLS/RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) { + secure_video = TRUE; + has_crypto = TRUE; + + if (dialog->secure_video_rtp || (dialog->secure_video_rtp = ast_sdp_srtp_alloc())) { + ast_set_flag(dialog->secure_video_rtp, AST_SRTP_CRYPTO_OFFER_OK); + } + } else if (!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "RTP/SAVPF")) { + secure_video = TRUE; + } else if (!strcmp(protocol, "RTP/AVPF") && !ast_test_flag(&dialog->flags[2], SIP_USE_AVPF)) { + ast_log(LOG_WARNING, "Received AVPF profile in video offer but AVPF is not enabled: %s\n", line); + continue; + } else if (!strcmp(protocol, "RTP/AVP") && ast_test_flag(&dialog->flags[2], SIP_USE_AVPF)) { + ast_log(LOG_WARNING, "Received AVP profile in video offer but AVPF is enabled: %s\n", line); + continue; + } else if (strcmp(protocol, "RTP/AVP") && strcmp(protocol, "RTP/AVPF")) { + ast_log(LOG_WARNING, "Unknown RTP profile in video offer: %s\n", line); + continue; + } + + /* 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); + goto cleanup; + } + + ast_debug(1, "Found RTP video format %u\n", codec); + ast_rtp_codecs_payloads_set_m_type(&video_rtp, NULL, codec); + } + + type = AST_MEDIA_TYPE_VIDEO; + 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", line); + goto cleanup; + } + /* Check for 'text' media offer */ + } else if (dialog->text_rtp && !strncmp(line, "m=text ", 7)) { + if (sscanf(line, "m=text %30u/%30u %31s %n", &port, &ports, protocol, &codecs_len) == 3 || + sscanf(line, "m=text %30u %31s %n", &port, protocol, &codecs_len) == 2) { + codecs = line + codecs_len; + + /* produce zero-port m-line since it may be needed later */ + ast_str_set(&sdp_media->decline_m_line, 0, "m=text 0 %s %s\r\n", protocol, codecs); + + if (port == 0) { + ast_debug(1, "Ignoring text stream offer because port number is zero\n"); + continue; + } + + /* Check number of ports offered for stream */ + if (ports > 1) { + ast_log(LOG_WARNING, "%u ports offered for text stream, not supported. Will try anyway\n", ports); + } + + if (sip_sdp_media_has_type(dialog, AST_MEDIA_TYPE_TEXT)) { + ast_log(LOG_WARNING, "Declining non-primary text stream: %s\n", line); + continue; + } + + if (!strcmp(protocol, "RTP/AVPF") && !ast_test_flag(&dialog->flags[2], SIP_USE_AVPF)) { + ast_log(LOG_WARNING, "Received AVPF profile in text offer but AVPF is not enabled: %s\n", line); + continue; + } else if (!strcmp(protocol, "RTP/AVP") && ast_test_flag(&dialog->flags[2], SIP_USE_AVPF)) { + ast_log(LOG_WARNING, "Received AVP profile in text offer but AVPF is enabled: %s\n", line); + continue; + } else if (strcmp(protocol, "RTP/AVP") && strcmp(protocol, "RTP/AVPF")) { + ast_log(LOG_WARNING, "Unknown RTP profile in text offer: %s\n", line); + continue; + } + + /* 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); + goto cleanup; + } + + ast_debug(1, "Found RTP text format %u\n", codec); + ast_rtp_codecs_payloads_set_m_type(&text_rtp, NULL, codec); + } + + type = AST_MEDIA_TYPE_TEXT; + text_port = port; + dialog->no_text_support = FALSE; + } else { + ast_log(LOG_WARNING, "Rejecting text stream offer due to invalid or unsupported syntax: %s\n", line); + goto cleanup; + } + /* Check for 'image' media offer */ + } else if (!strncmp(line, "m=image ", 6)) { + if (sscanf(line, "m=image %30u udptl t38", &port) == 1 || sscanf(line, "m=image %30u UDPTL t38", &port) == 1) { + /* produce zero-port m-line since it may be needed later length is "m=image 0 udptl t38" + "\r\n" */ + ast_str_set(&sdp_media->decline_m_line, 0, "m=image 0 udptl t38\r\n"); + + if (port == 0) { + ast_debug(1, "Ignoring image stream offer because port number is zero\n"); + continue; + } + + if (sip_fax_alloc(dialog)) { + ast_log(LOG_WARNING, "Failed to initialize UDPTL, declining image stream\n"); + continue; + } + + if (sip_sdp_media_has_type(dialog, AST_MEDIA_TYPE_IMAGE)) { + ast_log(LOG_WARNING, "Declining non-primary image stream: %s\n", line); + continue; + } + + if (dialog->t38_state != SIP_T38_ENABLED) { + memset(&dialog->remote_t38_parameters, 0, sizeof(dialog->remote_t38_parameters)); + + /* 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); + } + + type = AST_MEDIA_TYPE_IMAGE; + image_port = port; + } else if (sscanf(line, "m=image %30u %31s t38", &port, protocol) == 2) { + ast_log(LOG_WARNING, "Declining image stream due to unsupported transport: %s\n", line); + + /* produce zero-port m-line since this is guaranteed to be declined */ + ast_str_set(&sdp_media->decline_m_line, 0, "m=image 0 %s t38\r\n", protocol); + continue; + } else { + ast_log(LOG_WARNING, "Rejecting image media offer due to invalid or unsupported syntax: %s\n", line); + goto cleanup; + } + } else { + char type[16]; + + if ((sscanf(line, "m=%15s %30u/%30u %n", type, &port, &ports, &codecs_len) == 3 && codecs_len > 0) || + (sscanf(line, "m=%15s %30u %n", type, &port, &codecs_len) == 2 && codecs_len > 0)) { + /* produce zero-port m-line since it may be needed later */ + ast_str_set(&sdp_media->decline_m_line, 0, "m=%s 0 %s\r\n", type, line + codecs_len); + continue; + } else { + ast_log(LOG_WARNING, "Unsupported top-level media type in offer: %s\n", line); + goto cleanup; + } + } + + /* Media stream specific parameters */ + while ((line = sip_sdp_iterator_next(request, &iter, TRUE))) { + int parsed = FALSE; + + if (line[0] == 'c') { + if (type == AST_MEDIA_TYPE_AUDIO && sip_sdp_parse_c(line, &audio_address)) { + parsed = TRUE; + } else if (type == AST_MEDIA_TYPE_VIDEO && sip_sdp_parse_c(line, &video_address)) { + parsed = TRUE; + } else if (type == AST_MEDIA_TYPE_TEXT && sip_sdp_parse_c(line, &text_address)) { + parsed = TRUE; + } else if (type == AST_MEDIA_TYPE_IMAGE && sip_sdp_parse_c(line, &image_address)) { + parsed = TRUE; + } + } else if (line[0] == 'a') { + /* Audio specific scanning */ + if (type == AST_MEDIA_TYPE_AUDIO) { + if (sip_sdp_parse_a_audio(line, dialog, &audio_rtp, &audio_codec, &rtpmap_codecs)) { + parsed = TRUE; + } else if (sip_sdp_parse_a_send_recv(line, &send_recv)) { + parsed = TRUE; + } else if (!has_crypto && sip_sdp_parse_a_crypto(line, dialog, + dialog->audio_rtp, &dialog->secure_audio_rtp)) { + has_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 (type == AST_MEDIA_TYPE_VIDEO) { + if (sip_sdp_parse_a_video(line, dialog, &video_rtp, &video_codec, &rtpmap_codecs)) { + parsed = TRUE; + } else if (!has_crypto && sip_sdp_parse_a_crypto(line, dialog, + dialog->video_rtp, &dialog->secure_video_rtp)) { + has_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 (type == AST_MEDIA_TYPE_TEXT) { + if (sip_sdp_parse_a_text(line, dialog, &text_rtp, red_fmtp, + &red_num_gen, red_data_pt, &rtpmap_codecs)) { + parsed = TRUE; + } else if (!has_crypto && sip_sdp_parse_a_crypto(line, dialog, + dialog->text_rtp, &dialog->secure_text_rtp)) { + has_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 (type == AST_MEDIA_TYPE_IMAGE) { + if (sip_sdp_parse_a_image(line, dialog)) { + parsed = TRUE; + } + } + } + + ast_debug(3, "Processing media-level (%s) SDP '%s': %s\n", + ast_codec_media_type2str(type), line, parsed ? "OK" : "Unsupported or failed."); + } + + sdp_media->type = type; + + /* Ensure crypto lines are provided where necessary */ + if (type == AST_MEDIA_TYPE_AUDIO && secure_audio && !has_crypto) { + ast_log(LOG_WARNING, "Rejecting secure audio stream without encryption details\n"); + goto cleanup; + } else if (type == AST_MEDIA_TYPE_VIDEO && secure_video && !has_crypto) { + ast_log(LOG_WARNING, "Rejecting secure video 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 (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)) && ast_test_flag(&dialog->flags[0], SIP_USE_SRTP)) { + 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); + + if (image_port == 0) { + sip_fax_set_state(dialog, SIP_T38_DISABLED); + } + + if (is_offer) { + /* 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_rtp, &audio_rtp, NULL); + ast_rtp_codecs_payloads_xover(&video_rtp, &video_rtp, NULL); + ast_rtp_codecs_payloads_xover(&text_rtp, &text_rtp, NULL); + } + + /* Now gather all of the codecs that we are asked for: */ + ast_rtp_codecs_payload_formats(&audio_rtp, audio_format_cap, &audio_non_format_cap); + ast_rtp_codecs_payload_formats(&video_rtp, video_format_cap, &video_non_format_cap); + ast_rtp_codecs_payload_formats(&text_rtp, 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 (request->debug) { + struct ast_str *format_names, *audio_format_names, *video_format_names, *text_format_names, *joint_format_names, + *non_format_names, *audio_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(512); + audio_non_format_names = ast_str_alloca(512); + joint_non_format_names = ast_str_alloca(512); + + ast_verb(3, "Format capabilities: local %s, remote audio %s, remote video %s, remote text %s, combined %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, combined %s\n", + ast_rtp_lookup_mime_multiple2(non_format_names, NULL, dialog->non_format_cap, 0, 0), + ast_rtp_lookup_mime_multiple2(audio_non_format_names, NULL, audio_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; + + ast_format_cap_remove_by_type(dialog->joint_format_cap, AST_MEDIA_TYPE_UNKNOWN); + /* Our joint codec profile for this call */ + ast_format_cap_append_from_cap(dialog->joint_format_cap, joint_format_cap, AST_MEDIA_TYPE_UNKNOWN); + + ast_format_cap_remove_by_type(dialog->remote_format_cap, AST_MEDIA_TYPE_UNKNOWN); + /* The other side's capability in latest offer */ + ast_format_cap_append_from_cap(dialog->remote_format_cap, format_cap, AST_MEDIA_TYPE_UNKNOWN); + + dialog->joint_non_format_cap = joint_non_format_cap; /* DTMF capabilities */ + + 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 (ast_test_flag(&dialog->flags[1], SIP_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_rtp)) { + /* Peer did not force us to use a specific framing, so use our own */ + ast_rtp_codecs_set_framing(&audio_rtp, 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); + + if (request->response) { + sip_sdp_start_ice(dialog->audio_rtp, TRUE); + } + + 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_rtp, 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); + + if (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + ast_clear_flag(&dialog->flags[0], SIP_DTMF); + + if (joint_non_format_cap & AST_RTP_DTMF) { + /* XXX Would it be reasonable to drop the DSP at this point? */ + ast_set_flag(&dialog->flags[0], SIP_DTMF_RFC2833); + + /* Since RFC2833 is now negotiated we need to change some properties of the RTP stream */ + ast_rtp_instance_set_prop(dialog->audio_rtp, AST_RTP_PROPERTY_DTMF, 1); + ast_rtp_instance_set_prop(dialog->audio_rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, + ast_test_flag(&dialog->flags[0], SIP_RFC2833_COMPENSATE)); + } else { + ast_set_flag(&dialog->flags[0], SIP_DTMF_INBAND); + } + } + } 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, request->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_rtp, 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, request->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->red = TRUE; + ast_rtp_red_init(dialog->text_rtp, 300, red_data_pt, 2); + } else { + dialog->red = FALSE; + } + + ast_rtp_codecs_payloads_copy(&text_rtp, 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 (ast_test_flag(&dialog->flags[1], SIP_SYMMETRIC_RTP) && + ast_test_flag(&dialog->flags[1], SIP_UDPTL_DESTINATION)) { + 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_T38_ACCEPT && dialog->t38_state == SIP_T38_LOCAL_REINVITE) { + sip_fax_set_state(dialog, SIP_T38_ENABLED); + } else if (add_image == SIP_SDP_T38_INITIATE && dialog->channel && dialog->last_invite_cseq) { + sip_fax_set_state(dialog, SIP_T38_PEER_REINVITE); /* T38 Offered in re-invite from remote party */ + + /* If fax detection is enabled then send us off to the fax extension */ + if (ast_test_flag(&dialog->flags[1], 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_T38_DISABLED); + ast_udptl_stop(dialog->udptl); + + ast_debug(1, "Peer doesn't provide T.38 UDPTL\n"); + } + } + + if (audio_port == 0 && dialog->t38_state != SIP_T38_DISABLED && dialog->t38_state != SIP_T38_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 (request->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 (ast_test_flag(&dialog->flags[1], SIP_ONHOLD) && + (!ast_sockaddr_isnull(&audio_address) || !ast_sockaddr_isnull(&video_address) || + !ast_sockaddr_isnull(&text_address) || !ast_sockaddr_isnull(&image_address)) && + (send_recv == SIP_SDP_SEND_RECV || send_recv == SIP_SDP_SEND_UNKNOWN)) { + ast_queue_unhold(dialog->channel); + + /* Activate a re-invite */ + ast_queue_frame(dialog->channel, &ast_null_frame); + sip_dialog_change_onhold(dialog, request, FALSE, send_recv); + } 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))) || + (send_recv != SIP_SDP_SEND_RECV && send_recv != SIP_SDP_SEND_UNKNOWN)) { + ast_queue_hold(dialog->channel, dialog->moh_suggest); + + if (send_recv != SIP_SDP_SEND_RECV) { + ast_rtp_instance_stop(dialog->audio_rtp); + } + + /* RTCP needs to go ahead, even if we're on hold!!! */ + /* Activate a re-invite */ + ast_queue_frame(dialog->channel, &ast_null_frame); + sip_dialog_change_onhold(dialog, request, TRUE, send_recv); + } + + res = 0; + +cleanup: + if (res) { + sip_sdp_media_destroy(dialog); + } + + ast_rtp_codecs_payloads_destroy(&text_rtp); + ast_rtp_codecs_payloads_destroy(&video_rtp); + ast_rtp_codecs_payloads_destroy(&audio_rtp); + + 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 parts '%s' to '%s'\n", + dialog->remote_sdp_version, version, dialog->sdp_unique, unique); + } + + if (ast_test_flag(&dialog->flags[1], SIP_IGNORE_SDP_VERSION) + || version > dialog->remote_sdp_version || strcmp(unique, S_OR(dialog->sdp_unique, ""))) { + dialog->remote_sdp_version = version; + ast_string_field_set(dialog, sdp_unique, unique); + } else { + if (dialog->t38_state == SIP_T38_LOCAL_REINVITE) { + dialog->remote_sdp_version = version; + ast_string_field_set(dialog, sdp_unique, unique); + + ast_log(LOG_WARNING, + "Call '%s' responded to our T.38 reinvite without changing SDP version; 'ignoresdpversion' should be set for this peer\n", + dialog->call_id); + } else { + 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; + } + } + + return TRUE; +} + +static int sip_sdp_parse_c(const char *c_line, struct ast_sockaddr *address) +{ + char protocol[8], host[256]; + int family; + + /* Check for Media-description-level-address */ + 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 Audio host in c= line '%s'\n", c_line); + return FALSE; + } + + return TRUE; + } else { + ast_log(LOG_WARNING, "Invalid host in c= line '%s'\n", c_line); + } + + return FALSE; +} + +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 *rtpmap_codecs) +{ + unsigned int codec, rate, ptime; + char type[128], fmtp[256]; + + if (sscanf(a_line, "a=ptime:%30d", &ptime) == 1) { + if (ptime && dialog->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 (*rtpmap_codecs < SIP_SDP_MAX_RTPMAP_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; + (*rtpmap_codecs)++; + } 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) { + if (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_a_video(const char *a_line, struct sip_dialog *dialog, struct ast_rtp_codecs *video_rtp, + int *video_codec, int *rtpmap_codecs) +{ + 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 (*rtpmap_codecs < SIP_SDP_MAX_RTPMAP_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_rtp, NULL, codec, "video", type, 0, rate))) { + ast_debug(1, "Found video description format %s for ID %u\n", type, codec); + + *video_codec = codec; + (*rtpmap_codecs)++; + } else { + ast_rtp_codecs_payloads_unset(video_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(video_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(video_rtp, codec, new_format); + ao2_replace(format, new_format); + } else { + ast_rtp_codecs_payloads_unset(video_rtp, 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, "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_rtp, 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_rtp, codec, new_format); + ao2_replace(format, new_format); + } else { + ast_rtp_codecs_payloads_unset(video_rtp, NULL, codec); + } + } + } else { + return FALSE; + } + + return TRUE; +} + +static int sip_sdp_parse_a_text(const char *a_line, struct sip_dialog *dialog, struct ast_rtp_codecs *text_rtp, + char *red_fmtp, int *red_num_gen, int *red_data_pt, int *rtpmap_codecs) +{ + 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 (*rtpmap_codecs < SIP_SDP_MAX_RTPMAP_CODECS) { + if (!strncmp(type, "T140", 4)) { /* Text */ + if (dialog->text_rtp) { + ast_rtp_codecs_payloads_set_rtpmap_type_rate(text_rtp, 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_rtp, 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); + } + } + + (*rtpmap_codecs)++; + } 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 { + red_pt = strsep(&red_pts, "/"); + sscanf(red_pt, "%30u", (unsigned int *) &red_data_pt[*red_num_gen]); + } while (red_pt && (*red_num_gen)++ < AST_RED_MAX_GENERATION); + + } else { + return FALSE; + } + + return TRUE; +} + +static int sip_sdp_parse_a_image(const char *orig_a_line, struct sip_dialog *dialog) +{ + unsigned int max_size, bitrate, fill_bit_removal, transcoding_mmr, transcoding_jbig; + char *a_line; + + if (sip_fax_alloc(dialog)) { + return FALSE; + } + + /* Due to a typo in an IANA registration of one of the T.38 attributes, RFC5347 section 2.5.2 recommends that all T.38 + * attributes be parsed in a case insensitive manner. Hence, the importance of proof reading (and code reviews). */ + a_line = ast_strdupa(orig_a_line); + ast_str_to_lower(a_line); + + if (sscanf(a_line, "a=t38faxmaxbuffer:%30u", &max_size) == 1) { + ast_debug(3, "MaxBufferSize:%u\n", max_size); + } else if (sscanf(a_line, "a=t38maxbitrate:%30u", &bitrate) == 1 || sscanf(a_line, "a=t38faxmaxrate:%30u", &bitrate) == 1) { + ast_debug(3, "T38MaxBitRate: %u\n", bitrate); + + switch (bitrate) { + case 14400: + dialog->remote_t38_parameters.rate = AST_T38_RATE_14400; + break; + case 12000: + dialog->remote_t38_parameters.rate = AST_T38_RATE_12000; + break; + case 9600: + dialog->remote_t38_parameters.rate = AST_T38_RATE_9600; + break; + case 7200: + dialog->remote_t38_parameters.rate = AST_T38_RATE_7200; + break; + case 4800: + dialog->remote_t38_parameters.rate = AST_T38_RATE_4800; + break; + case 2400: + dialog->remote_t38_parameters.rate = AST_T38_RATE_2400; + break; + } + } else if (sscanf(a_line, "a=t38faxversion:%30u", &dialog->remote_t38_parameters.version) == 1) { + ast_debug(3, "FaxVersion: %u\n", dialog->remote_t38_parameters.version); + } else if (sscanf(a_line, "a=t38faxmaxdatagram:%30u", &dialog->t38_max_datagram) == 1 || + sscanf(a_line, "a=t38maxdatagram:%30u", &dialog->t38_max_datagram) == 1) { + /* override the supplied value if the configuration requests it */ + ast_debug(3, "FaxMaxDatagram: %u\n", dialog->t38_max_datagram); + + ast_udptl_set_far_max_datagram(dialog->udptl, dialog->t38_max_datagram); + } else if (sscanf(a_line, "a=t38faxfillbitremoval:%30u", &fill_bit_removal) == 1) { + ast_debug(3, "FillBitRemoval: %u\n", fill_bit_removal); + + dialog->remote_t38_parameters.fill_bit_removal = fill_bit_removal; + } else if (sscanf(a_line, "a=t38faxtranscodingmmr:%30u", &transcoding_mmr) == 1) { + ast_debug(3, "Transcoding MMR: %u\n", dialog->remote_t38_parameters.transcoding_mmr); + + dialog->remote_t38_parameters.transcoding_mmr = transcoding_mmr; + } else if (sscanf(a_line, "a=t38faxtranscodingjbig:%30u", &transcoding_jbig) == 1) { + ast_debug(3, "Transcoding JBIG: %u\n", dialog->remote_t38_parameters.transcoding_jbig); + + dialog->remote_t38_parameters.transcoding_jbig = transcoding_jbig; + } else if (!strncmp(a_line, "a=t38faxratemanagement:", 24)) { + ast_debug(3, "RateManagement: %s\n", a_line + 24); + + if (!strcmp(a_line + 24, "localTCF")) { + dialog->remote_t38_parameters.rate_management = AST_T38_RATE_MANAGEMENT_LOCAL_TCF; + } else if (!strcmp(a_line + 24, "transferredTCF")) { + dialog->remote_t38_parameters.rate_management = AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF; + } + } else if (!strncmp(a_line, "a=t38faxudpec:", 15)) { + ast_debug(3, "UDP EC: %s\n", a_line + 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_send_recv(const char *a_line, int *send_recv) +{ + if (!strcmp(a_line, "a=sendrecv")) { + if (*send_recv == SIP_SDP_SEND_UNKNOWN) { + *send_recv = SIP_SDP_SEND_RECV; + } + } else if (!strcmp(a_line, "a=sendonly")) { + if (*send_recv == SIP_SDP_SEND_UNKNOWN) { + *send_recv = SIP_SDP_SEND_ONLY; + } + } else if (!strcmp(a_line, "a=inactive")) { + if (*send_recv == SIP_SDP_SEND_UNKNOWN) { + *send_recv = SIP_SDP_SEND_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 **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 (!*srtp) { + if (ast_test_flag(&dialog->flags[0], SIP_OUTGOING)) { + ast_log(LOG_WARNING, "Ignoring unexpected crypto attribute in SDP answer\n"); + return FALSE; + } + + if (!(*srtp = ast_sdp_srtp_alloc())) { + return FALSE; + } + } + + if (!(*srtp)->crypto && !((*srtp)->crypto = ast_sdp_crypto_alloc())) { + return FALSE; + } + + /* skip "crypto:" */ + if (ast_sdp_crypto_process(rtp, *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 && ast_test_flag(&dialog->flags[2], SIP_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_request *request, int old_sdp, 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, *video_m_line, *text_m_line, *image_m_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(char *, audio_crypto, NULL, ast_free_ptr); + RAII_VAR(char *, video_crypto, NULL, ast_free_ptr); + RAII_VAR(char *, text_crypto, NULL, ast_free_ptr); + RAII_VAR(struct ast_format_cap *, format_cap, NULL, ao2_cleanup); + RAII_VAR(struct ast_format_cap *, joint_format_cap, NULL, ao2_cleanup); + const char *send_recv; + 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; + } + + add_audio = FALSE; + add_video = FALSE; + add_text = FALSE; + direct_media = FALSE; + + audio_m_line = ast_str_alloca(256); + audio_a_lines = ast_str_create(256); + + video_m_line = ast_str_alloca(256); + video_a_lines = ast_str_create(256); + + text_m_line = ast_str_create(256); + text_a_lines = ast_str_create(256); + + image_m_line = ast_str_create(256); + 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; + } + + /* XXX 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(); + dialog->local_sdp_version = dialog->sdp_id; + } else if (!old_sdp) { + dialog->local_sdp_version++; + } + + sip_sdp_media_address(dialog, add_video, add_text, &audio_address, &video_address, &text_address); + + if (add_media) { + int codec, non_codec, min_packet_size, max_packet_size; + + direct_media = !ast_sockaddr_isnull(&dialog->audio_redirect_address) && ast_format_cap_count(dialog->redirect_format_cap); + + if (direct_media) { + 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 */ + add_audio = ast_format_cap_has_type(joint_format_cap, AST_MEDIA_TYPE_AUDIO); + + /* 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 offers, but caller probably did not offer it!\n"); + } else if (dialog->video_rtp) { + add_video = TRUE; + + ast_debug(2, "This call needs video offers!\n"); + } else { + ast_debug(2, "This call needs video offers, but there's no video support enabled!\n"); + } + } + + /* Check if we need_ text in this call */ + if ((ast_format_cap_has_type(dialog->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 offers!\n"); + } else { + ast_debug(2, "This call needs text offers, but there's no text support enabled!\n"); + } + } + + /* XXX note, Video and Text are negated - 'true' means 'no' */ + 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 ? "True" : "False", dialog->no_text_support ? "True" : "False"); + ast_debug(1, "Our preferred codec: %s\n", ast_format_cap_get_names(dialog->outgoing_format_cap, &format_names)); + + if (ast_test_flag(&dialog->flags[1], SIP_ONHOLD) == SIP_ONHOLD_RECV_ONLY || + ast_test_flag(&dialog->flags[1], SIP_ONHOLD) == SIP_ONHOLD_INACTIVE) { + direct_media = FALSE; + } + + ast_debug(1, "Audio is at %s\n", ast_sockaddr_stringify_port(&audio_address)); + + /* We break with the "recommendation" and send our IP, in order that our peer doesn't have to ast_gethostbyname() us */ + audio_crypto = sip_sdp_get_a_crypto(dialog->secure_audio_rtp, ast_test_flag(&dialog->flags[2], SIP_SRTP_TAG_32)); + + 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, + ast_test_flag(&dialog->flags[2], SIP_USE_AVPF), ast_test_flag(&dialog->flags[2], SIP_FORCE_AVP))); + + /* 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) { + video_crypto = sip_sdp_get_a_crypto(dialog->secure_video_rtp, ast_test_flag(&dialog->flags[2], SIP_SRTP_TAG_32)); + + 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, + ast_test_flag(&dialog->flags[2], SIP_USE_AVPF), ast_test_flag(&dialog->flags[2], SIP_FORCE_AVP))); + + ast_debug(1, "Video is at %s\n", ast_sockaddr_stringify(&video_address)); + + if (!direct_media) { + if (ast_test_flag(&dialog->flags[2], SIP_ICE_SUPPORT)) { + sip_sdp_add_ice(dialog->video_rtp, &video_a_lines); + } + } + } + + /* 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) { + text_crypto = sip_sdp_get_a_crypto(dialog->secure_text_rtp, ast_test_flag(&dialog->flags[2], SIP_SRTP_TAG_32)); + + 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, + ast_test_flag(&dialog->flags[2], SIP_USE_AVPF), ast_test_flag(&dialog->flags[2], SIP_FORCE_AVP))); + + ast_debug(1, "Text is at %s\n", ast_sockaddr_stringify(&text_address)); + + if (!direct_media) { + if (ast_test_flag(&dialog->flags[2], SIP_ICE_SUPPORT)) { + sip_sdp_add_ice(dialog->text_rtp, &text_a_lines); + } + } + } + + /* Start building generic SDP headers */ + if (ast_test_flag(&dialog->flags[2], SIP_RELAY_NEAREND)) { + ast_str_append(&audio_a_lines, 0, "a=label:X-relay-nearend\r\n"); + } else if (ast_test_flag(&dialog->flags[2], SIP_RELAY_FAREND)) { + ast_str_append(&audio_a_lines, 0, "a=label:X-relay-farend\r\n"); + } + + min_packet_size = 0; + max_packet_size = 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 (!ast_test_flag(&dialog->flags[2], SIP_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_packet_size, &max_packet_size); + 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 (ast_format_get_type(format) == AST_MEDIA_TYPE_AUDIO) { + sip_sdp_add_audio_codec(dialog, format, &audio_m_line, &audio_a_lines, &min_packet_size, &max_packet_size); + } else if (add_video && ast_format_get_type(format) == AST_MEDIA_TYPE_VIDEO) { + sip_sdp_add_video_codec(dialog, format, &video_m_line, &video_m_line); + } 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"); + } + + if (min_packet_size) { + ast_str_append(&audio_a_lines, 0, "a=ptime:%d\r\n", min_packet_size); + } + + if (max_packet_size) { + ast_str_append(&audio_a_lines, 0, "a=maxptime:%d\r\n", max_packet_size); + } + + if (!ast_test_flag(&dialog->flags[0], SIP_OUTGOING)) { + ast_debug(1, "Setting framing on incoming call: %u\n", min_packet_size); + ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(dialog->audio_rtp), min_packet_size); + } + + if (!direct_media) { + if (ast_test_flag(&dialog->flags[2], SIP_ICE_SUPPORT)) { + sip_sdp_add_ice(dialog->audio_rtp, &audio_a_lines); + + /* Start ICE negotiation, and setting that we are controlled agent, as this is response to offer */ + if (request->response) { + sip_sdp_start_ice(dialog->audio_rtp, FALSE); + } + } + } + + /* If we've got rtcp-mux enabled, just unconditionally offer it in all SDPs */ + if (ast_test_flag(&dialog->flags[2], SIP_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"); + } + } + + if (add_image) { + unsigned int rate; + + /* 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)); + + if (ast_sockaddr_cmp_addr(&image_address, &audio_address)) { + ast_str_append(&image_m_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->local_t38_parameters.version); + + switch (dialog->local_t38_parameters.rate) { + case AST_T38_RATE_2400: + rate = 2400; + case AST_T38_RATE_4800: + rate = 4800; + case AST_T38_RATE_7200: + rate = 7200; + case AST_T38_RATE_9600: + rate = 9600; + case AST_T38_RATE_12000: + rate = 12000; + case AST_T38_RATE_14400: + rate = 14400; + default: + rate = 0; + } + + ast_str_append(&image_a_lines, 0, "a=T38MaxBitRate:%u\r\n", rate); + + if (dialog->local_t38_parameters.fill_bit_removal) { + ast_str_append(&image_a_lines, 0, "a=T38FaxFillBitRemoval\r\n"); + } + + if (dialog->local_t38_parameters.transcoding_mmr) { + ast_str_append(&image_a_lines, 0, "a=T38FaxTranscodingMMR\r\n"); + } + + if (dialog->local_t38_parameters.transcoding_jbig) { + ast_str_append(&image_a_lines, 0, "a=T38FaxTranscodingJBIG\r\n"); + } + + switch (dialog->local_t38_parameters.rate_management) { + case AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF: + ast_str_append(&image_a_lines, 0, "a=T38FaxRateManagement:transferredTCF\r\n"); + break; + case AST_T38_RATE_MANAGEMENT_LOCAL_TCF: + ast_str_append(&image_a_lines, 0, "a=T38FaxRateManagement:localTCF\r\n"); + break; + } + + ast_str_append(&image_a_lines, 0, "a=T38FaxMaxDatagram:%u\r\n", ast_udptl_get_local_max_datagram(dialog->udptl)); + + switch (ast_udptl_get_error_correction_scheme(dialog->udptl)) { + case UDPTL_ERROR_CORRECTION_NONE: + break; + case UDPTL_ERROR_CORRECTION_FEC: + ast_str_append(&image_a_lines, 0, "a=T38FaxUdpEC:t38UDPFEC\r\n"); + break; + case UDPTL_ERROR_CORRECTION_REDUNDANCY: + ast_str_append(&image_a_lines, 0, "a=T38FaxUdpEC:t38UDPRedundancy\r\n"); + break; + } + } + + send_recv = sip_sdp_get_a_send_recv(dialog); + + if (add_audio) { + ast_str_append(&audio_m_line, 0, "\r\n"); + ast_str_append(&audio_a_lines, 0, "%s", send_recv); + } + + if (add_video) { + ast_str_append(&video_m_line, 0, "\r\n"); + ast_str_append(&video_a_lines, 0, "%s", send_recv); + } + + if (add_text) { + ast_str_append(&text_m_line, 0, "\r\n"); + ast_str_append(&text_a_lines, 0, "%s", send_recv); + } + + sip_request_add_header(request, "Content-Type", "application/sdp"); + sip_request_add_content(request, "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_request_build_content(request, "o=%s %d %d IN %s %s\r\n", + ast_strlen_zero(sip_config.sdp_username) ? "-" : sip_config.sdp_username, + dialog->sdp_id, dialog->local_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_request_build_content(request, "s=%s\r\n", ast_strlen_zero(sip_config.sdp_session) ? "-" : sip_config.sdp_session); + + /* SDP connection */ + sip_request_build_content(request, "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)); + + /* Build max bitrate string, only if video response is appropriate */ + if (add_video && dialog->max_call_bitrate && !ast_test_flag(&dialog->flags[1], SIP_CISCO_USECALLMANAGER)) { + sip_request_build_content(request, "b=CT:%d\r\n", dialog->max_call_bitrate); + } + + /* Time the session is active */ + sip_request_add_content(request, "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) { + switch (sdp_media->type) { + case AST_MEDIA_TYPE_AUDIO: + if (add_audio) { + sip_request_add_content(request, ast_str_buffer(audio_m_line)); + + if (audio_crypto) { + sip_request_add_content(request, audio_crypto); + } + + sip_request_add_content(request, ast_str_buffer(audio_a_lines)); + } else { + sip_request_add_content(request, ast_str_buffer(sdp_media->decline_m_line)); + } + + break; + case AST_MEDIA_TYPE_VIDEO: + if (add_video) { /* only if video response is appropriate */ + sip_request_add_content(request, ast_str_buffer(video_m_line)); + sip_request_add_content(request, ast_str_buffer(video_a_lines)); + + if (video_crypto) { + sip_request_add_content(request, video_crypto); + } + } else { + sip_request_add_content(request, ast_str_buffer(sdp_media->decline_m_line)); + } + + break; + case AST_MEDIA_TYPE_TEXT: + if (add_text) { /* only if text response is appropriate */ + sip_request_add_content(request, ast_str_buffer(text_m_line)); + sip_request_add_content(request, ast_str_buffer(text_a_lines)); + + if (text_crypto) { + sip_request_add_content(request, text_crypto); + } + } else { + sip_request_add_content(request, ast_str_buffer(sdp_media->decline_m_line)); + } + + break; + case AST_MEDIA_TYPE_IMAGE: + if (add_image) { + sip_request_add_content(request, ast_str_buffer(image_m_line)); + sip_request_add_content(request, ast_str_buffer(image_a_lines)); + } else { + sip_request_add_content(request, ast_str_buffer(sdp_media->decline_m_line)); + } + + break; + case AST_MEDIA_TYPE_UNKNOWN: + sip_request_add_content(request, ast_str_buffer(sdp_media->decline_m_line)); + break; + } + } + } else { + /* generate new SDP from scratch, no offers */ + if (add_audio) { + sip_request_add_content(request, ast_str_buffer(audio_m_line)); + sip_request_add_content(request, ast_str_buffer(audio_a_lines)); + + if (audio_crypto) { + sip_request_add_content(request, audio_crypto); + } + } + + if (add_video) { /* only if video response is appropriate */ + sip_request_add_content(request, ast_str_buffer(video_m_line)); + sip_request_add_content(request, ast_str_buffer(video_a_lines)); + + if (video_crypto) { + sip_request_add_content(request, video_crypto); + } + } + + if (add_text) { /* only if text response is appropriate */ + sip_request_add_content(request, ast_str_buffer(text_m_line)); + sip_request_add_content(request, ast_str_buffer(text_a_lines)); + + if (text_crypto) { + sip_request_add_content(request, text_crypto); + } + } + + if (add_image) { + sip_request_add_content(request, ast_str_buffer(image_m_line)); + sip_request_add_content(request, ast_str_buffer(image_a_lines)); + } + } + + /* Update lastrtprx when we send our SDP */ + dialog->last_rtp_received = dialog->last_rtp_sent = time(NULL); /* XXX why both ? */ + format_names = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + + ast_debug(3, "Done building SDP. Settling 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_rtp_check container so its not in there twice. */ + ao2_lock(sip_dialogs_rtp_check); + + ao2_t_unlink(sip_dialogs_rtp_check, dialog, "unlink dialog"); + ao2_t_link(sip_dialogs_rtp_check, dialog, "link dialog"); + + ao2_unlock(sip_dialogs_rtp_check); + + 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_packet_size, int *max_packet_size) +{ + int rtp_code; + const char *mime; + unsigned int rate, framing; + + 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 if (AST_RTP_PT_LAST_STATIC < rtp_code) { + 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); + framing = ast_format_cap_get_format_framing(dialog->format_cap, format); + + 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); + } + + if (max_packet_size && ast_format_get_maximum_ms(format) && + (ast_format_get_maximum_ms(format) < *max_packet_size)) { + *max_packet_size = ast_format_get_maximum_ms(format); + } + + if (framing && (framing < *min_packet_size)) { + *min_packet_size = framing; + } + + /* Our first codec packetization processed cannot be zero */ + if (*min_packet_size == 0 && framing) { + *min_packet_size = framing; + } + + if (*max_packet_size == 0 && ast_format_get_maximum_ms(format)) { + *max_packet_size = ast_format_get_maximum_ms(format); + } +} + +/* 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 && + ast_test_flag(&dialog->flags[1], SIP_CISCO_USECALLMANAGER)) { + /* 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 && + ast_test_flag(&dialog->flags[1], SIP_CISCO_USECALLMANAGER)) { + const char *imageattr = (const char *) ast_format_attribute_get(format, "imageattr"); + + if (ast_strlen_zero(imageattr)) { + imageattr = "[x=640,y=480,q=0.50]"; + } + + 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 t140code = 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, t140code, t140code, t140code); + } +} + +/* 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 ", ast_sockaddr_stringify_addr_remote(&candidate->relay_address)); + ast_str_append(a_lines, 0, "rport %s", ast_sockaddr_stringify_port(&candidate->relay_address)); + } + + ast_str_append(a_lines, 0, "\r\n"); + ao2_ref(candidate, -1); + } + + ao2_iterator_destroy(&iter); +} + +static char *sip_sdp_get_a_send_recv(struct sip_dialog *dialog) +{ + if (ast_test_flag(&dialog->flags[1], SIP_ONHOLD) == SIP_ONHOLD_RECV_ONLY) { + return "a=recvonly\r\n"; + } else if (ast_test_flag(&dialog->flags[1], SIP_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, int tag_len_32) +{ + 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, tag_len_32)); + } while ((sdp_srtp = AST_LIST_NEXT(sdp_srtp, sdp_srtp_list))); + + return ast_strdup(ast_str_buffer(crypto)); +} + +static struct sip_sdp_media *sip_sdp_media_alloc(struct sip_dialog *dialog) +{ + struct sip_sdp_media *sdp_media; + + 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; + } + + AST_LIST_INSERT_TAIL(&dialog->sdp_media, sdp_media, next); + sdp_media->type = AST_MEDIA_TYPE_UNKNOWN; + + 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 is_offer) +{ + 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, is_offer ? 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 remote_rtcp_mux) +{ + int local_rtcp_mux = !!ast_test_flag(&dialog->flags[2], SIP_RTCP_MUX); + int fd = -1; + + if (local_rtcp_mux && remote_rtcp_mux) { + ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_MUX); + } 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 remote_rtcp_mux) +{ + struct ast_rtp_engine_ice *ice; + int local_rtcp_mux; + + if (!(ice = ast_rtp_instance_get_ice(rtp))) { + return; + } + + local_rtcp_mux = !!ast_test_flag(&dialog->flags[2], SIP_RTCP_MUX); + + if (local_rtcp_mux && remote_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 -durN asterisk-22.3.0.orig/channels/sip/security_events.c asterisk-22.3.0/channels/sip/security_events.c --- asterisk-22.3.0.orig/channels/sip/security_events.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/security_events.c 2025-04-17 11:11:22.824702929 +1200 @@ -0,0 +1,320 @@ +/* + * 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/request.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/config.h" +#include "include/utils.h" +#include "include/security_events.h" + +/* Determine transport type used to receive request*/ +void sip_report_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->exten, + .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)); +} + +void sip_report_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->exten, + .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)); +} + +void sip_report_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->exten, + .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)); +} + +void sip_report_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->exten, + .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)); +} + +void sip_report_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->exten, + .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)); +} + +void sip_report_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->exten, sizeof(account_id)); + } + + snprintf(session_id, sizeof(session_id), "%p", dialog); + ast_security_event_report(AST_SEC_EVT(&chal_resp_failed)); +} + +void sip_report_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->exten, sizeof(account_id)); + } + + snprintf(session_id, sizeof(session_id), "%p", dialog); + ast_security_event_report(AST_SEC_EVT(&chal_sent)); +} + +void sip_report_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->exten, + .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_report_security_event(const struct sip_dialog *dialog, const struct sip_request *request, const int res) +{ + + char *authorization; + struct sip_authorization_data authorization_data; + RAII_VAR(struct sip_peer *, peer, NULL, ao2_cleanup); + + if (!ast_strlen_zero(dialog->exten)) { + peer = sip_peer_find(dialog->exten, TRUE, FALSE); /* exten contains the username */ + } else { + peer = sip_peer_find_by_address(&dialog->received_address, dialog->socket.transport, TRUE, FALSE); + } + + switch (res) { + case SIP_AUTHORIZATION_DONT_KNOW: + break; + case SIP_AUTHORIZATION_SUCCESSFUL: + if (peer) { + sip_report_auth_success(dialog, !ast_strlen_zero(peer->secret) || !ast_strlen_zero(peer->md5secret)); + } + + break; + case SIP_AUTHORIZATION_CHALLENGE_SENT: + sip_report_chal_sent(dialog); + break; + case SIP_AUTHORIZATION_SECRET_FAILED: + case SIP_AUTHORIZATION_USERNAME_MISMATCH: + authorization = ast_strdupa(sip_request_get_header(request, "Authorization")); + sip_parse_authorization(authorization, &authorization_data); + + if (res == SIP_AUTHORIZATION_SECRET_FAILED) { + sip_report_inval_password(dialog, S_OR(authorization_data.nonce, ""), S_OR(authorization_data.response, "")); + } else if (peer) { + sip_report_chal_resp_failed(dialog, S_OR(authorization_data.username, ""), peer->username); + } + + break; + case SIP_AUTHORIZATION_NOT_FOUND: + /* with sip_config.always_auth_reject on, generates 2 events */ + sip_report_inval_acct(dialog); + break; + case SIP_AUTHORIZATION_UNKNOWN_DOMAIN: + sip_report_failed_acl(dialog, "domain_must_match"); + break; + case SIP_AUTHORIZATION_PEER_NOT_DYNAMIC: + sip_report_failed_acl(dialog, "peer_not_dynamic"); + break; + case SIP_AUTHORIZATION_ACL_FAILED: + /* with sip_config.always_auth_reject on, generates 2 events */ + sip_report_failed_acl(dialog, "device_must_match_acl"); + break; + case SIP_AUTHORIZATION_BAD_TRANSPORT: + sip_report_inval_transport(dialog, ast_transport2str(dialog->socket.transport)); + break; + case SIP_AUTHORIZATION_RTP_FAILED: + break; + case SIP_AUTHORIZATION_SESSION_LIMIT: + sip_report_session_limit(dialog); + break; + } + + return res; +} diff -durN asterisk-22.3.0.orig/channels/sip/stimer.c asterisk-22.3.0/channels/sip/stimer.c --- asterisk-22.3.0.orig/channels/sip/stimer.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/stimer.c 2025-04-17 11:11:22.824702929 +1200 @@ -0,0 +1,448 @@ +/* + * 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/netsock.h" +#include "include/route.h" +#include "include/request.h" +#include "include/response.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/config.h" +#include "include/utils.h" +#include "include/manager.h" + +static int __sip_stimer_start(const void *data); +static int __sip_stimer_stop(const void *data); +static int sip_stimer_timeout(const void *data); + +const char *sip_stimer_mode2str(int mode) +{ + switch (mode) { + case SIP_STIMER_MODE_ACCEPT: + return "Accept"; + case SIP_STIMER_MODE_ORIGINATE: + return "Originate"; + case SIP_STIMER_MODE_REFUSE: + return "Refuse"; + default: + break; + } + + return "Unknown"; +} + +const char *sip_stimer_refresher2str(int refresher) +{ + switch (refresher) { + case SIP_STIMER_REFRESHER_AUTO: + return "auto"; + case SIP_STIMER_REFRESHER_UAC: + return "uac"; + case SIP_STIMER_REFRESHER_UAS: + return "uas"; + default: + break; + } + + return "unknown"; +} + +/* Allocate Session-Timers struct w/in dialog */ +struct sip_stimer *sip_stimer_alloc(struct sip_dialog *dialog) +{ + if (dialog->stimer) { + return dialog->stimer; + } + + if (!(dialog->stimer = ast_calloc(1, sizeof(*dialog->stimer)))) { + return NULL; + } + + dialog->stimer->sched_id = -1; /* Session-Timers ast_sched scheduler id */ + dialog->stimer->active = FALSE; + + if (dialog->peer) { + dialog->stimer->cached_mode = dialog->peer->stimer_mode; + } else { + dialog->stimer->cached_mode = sip_config.stimer_mode; + } + + return dialog->stimer; +} + +/* Check Session Timers for an INVITE request */ +int sip_stimer_handle_invite(struct sip_dialog *dialog, struct sip_request *request, int reinvite) +{ + int active, interval, refresher; + + active = FALSE; + interval = 0; + refresher = SIP_STIMER_REFRESHER_AUTO; + + /* Session-Timers */ + if (sip_supported_option(&dialog->options, SIP_OPTION_TIMER)) { + int min_se, max_se; + + max_se = -1; /* UAC's Session-Expires in integer format */ + min_se = -1; /* UAC's Min-SE in integer format */ + + /* 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(request, &max_se, &refresher)) { + sip_send_response_reliable(dialog, "400 Bad Request", request); + return -1; + } + + if (refresher == SIP_STIMER_REFRESHER_UAC) { + refresher = SIP_STIMER_REFRESHER_UAS; + } else { + refresher = SIP_STIMER_REFRESHER_UAC; + } + + /* Parse the Min-SE header */ + if (sip_parse_min_se(request, &min_se)) { + sip_send_response_reliable(dialog, "400 Bad Request", request); + return -1; + } + + /* Allocate Session-Timers struct w/in the dialog */ + sip_stimer_alloc(dialog); + + switch (sip_stimer_get_mode(dialog, TRUE)) { + case SIP_STIMER_MODE_ACCEPT: + case SIP_STIMER_MODE_ORIGINATE: + if (max_se > 0 && max_se < sip_stimer_get_expiry(dialog, FALSE)) { + sip_send_response_with_min_se(dialog, "422 Session Interval Too Small", + request, sip_stimer_get_expiry(dialog, FALSE)); + return -1; + } + + dialog->stimer->remote_active = TRUE; + active = TRUE; + + if (refresher == SIP_STIMER_REFRESHER_AUTO) { + refresher = sip_stimer_get_refresher(dialog); + } + + if (max_se > 0) { + if (sip_stimer_get_expiry(dialog, TRUE) >= min_se) { + interval = MIN(max_se, sip_stimer_get_expiry(dialog, TRUE)); + } else { + interval = max_se; + } + } else if (min_se > 0) { + interval = MAX(min_se, sip_stimer_get_expiry(dialog, TRUE)); + } else { + interval = max_se; + } + + break; + case SIP_STIMER_MODE_REFUSE: + if (sip_supported_option(&dialog->required_options, SIP_OPTION_TIMER)) { + sip_send_response_with_unsupported(dialog, "420 Option Disabled", request, "timer"); + ast_log(LOG_WARNING, "Received SIP INVITE with supported but disabled option: timer\n"); + + return -1; + } + + break; + default: + break; + } + } else { + /* The UAC did not request session-timers. Asterisk (UAS), will now decide (based on session-timer-mode whether to run + * session-timers for this session or not. */ + if (sip_stimer_get_mode(dialog, TRUE) == SIP_STIMER_MODE_ORIGINATE) { + sip_stimer_alloc(dialog); + + active = TRUE; + interval = sip_stimer_get_expiry(dialog, TRUE); + + refresher = SIP_STIMER_REFRESHER_UAC; + dialog->stimer->remote_active = sip_supported_option(&dialog->options, SIP_OPTION_TIMER); + } + } + + if (!reinvite) { + /* Start session refresh timer based on negotiation/config */ + if (active) { + dialog->stimer->active = TRUE; + dialog->stimer->interval = interval; + dialog->stimer->refresher = refresher; + } + } else if (dialog->stimer && dialog->stimer->active) { + /* A re-invite request sent within a dialog will serve as a refresh request, no matter whether the re-invite + * was sent for refreshing the session or modifying it.*/ + ast_debug (2, "Restarting session-timers on a refresh for '%s'\n", dialog->call_id); + + /* The UAC may be adjusting the session-timers mid-session */ + if (interval > 0) { + dialog->stimer->interval = interval; + dialog->stimer->refresher = refresher; + } + } + + return 0; +} + +/* Get the session-timer mode */ +int sip_stimer_get_mode(struct sip_dialog *dialog, int no_cached) +{ + if (!dialog->stimer) { + if (dialog->peer) { + return dialog->peer->stimer_mode; + } + + return sip_config.stimer_mode; + } + + if (!no_cached && dialog->stimer->cached_mode != SIP_STIMER_MODE_INVALID) { + return dialog->stimer->cached_mode; + } + + if (dialog->peer) { + dialog->stimer->cached_mode = dialog->peer->stimer_mode; + + return dialog->stimer->cached_mode; + } + + dialog->stimer->cached_mode = sip_config.stimer_mode; + + return dialog->stimer->cached_mode; +} + +/* Get Max or Min SE (session timer expiry */ +int sip_stimer_get_expiry(struct sip_dialog *dialog, int cached_max) +{ + if (cached_max) { + if (dialog->stimer->cached_max_se) { + return dialog->stimer->cached_max_se; + } + + if (dialog->peer) { + dialog->stimer->cached_max_se = dialog->peer->stimer_max_se; + + return dialog->stimer->cached_max_se; + } + + dialog->stimer->cached_max_se = sip_config.stimer_max_se; + + return dialog->stimer->cached_max_se; + } + + /* Find Min SE timer */ + if (dialog->stimer->cached_min_se) { + return dialog->stimer->cached_min_se; + } + + if (dialog->peer) { + dialog->stimer->cached_min_se = dialog->peer->stimer_min_se; + + return dialog->stimer->cached_min_se; + } + + dialog->stimer->cached_min_se = sip_config.stimer_min_se; + + return dialog->stimer->cached_min_se; +} + +/* Get the entity (UAC or UAS) that's acting as the session-timer refresher. This is only called when processing an INVITE, + * so in that case Asterisk is always currently the UAS. If this is ever used to process responses, the function will have + * to be changed. */ +int sip_stimer_get_refresher(struct sip_dialog *dialog) +{ + if (dialog->stimer->cached_refresher != SIP_STIMER_REFRESHER_AUTO) { + return dialog->stimer->cached_refresher; + } + + if (dialog->peer) { + if (dialog->peer->stimer_refresher == SIP_STIMER_REFRESHER_UAC) { + dialog->stimer->cached_refresher = SIP_STIMER_REFRESHER_UAS; + } else { + dialog->stimer->cached_refresher = SIP_STIMER_REFRESHER_UAC; + } + + return dialog->stimer->cached_refresher; + } + + if (sip_config.stimer_refresher == SIP_STIMER_REFRESHER_UAC) { + dialog->stimer->cached_refresher = SIP_STIMER_REFRESHER_UAS; + } else { + dialog->stimer->cached_refresher = SIP_STIMER_REFRESHER_UAC; + } + + return dialog->stimer->cached_refresher; +} + +/* Process session refresh timeout event */ +static int sip_stimer_timeout(const void *data) +{ + struct sip_dialog *dialog; + int active; + + dialog = (struct sip_dialog *) data; + active = FALSE; + + if (!dialog->channel || !dialog->stimer->active || ast_channel_state(dialog->channel) != AST_STATE_UP) { + goto error; + } + + if (dialog->stimer->refresher == SIP_STIMER_REFRESHER_UAC) { + active = TRUE; + + sip_send_reinvite_with_sdp(dialog, TRUE, dialog->t38_state == SIP_T38_ENABLED); + } else { + struct ast_channel *channel; + + ast_log(LOG_WARNING, "Session-Timer expired on '%s'\n", dialog->call_id); + + if ((channel = sip_dialog_lock_with_channel(dialog))) { + 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->stimer->sched_id = -1; + dialog->stimer->active = FALSE; + + /* If we are not asking to be rescheduled, then we need to release our reference to the dialog. */ + ao2_t_cleanup(dialog, "drop dialog"); + } + + return active; +} + +void sip_stimer_delete_sched(struct sip_dialog *dialog) +{ + if (dialog->stimer->sched_id != -1) { + ast_debug(2, "Session timer stopped on '%s'\n", dialog->call_id); + + AST_SCHED_DEL_UNREF(sip_sched_context, dialog->stimer->sched_id, ao2_t_cleanup(dialog, "drop dialog")); + } +} + +/* Run by the sched thread. */ +static int __sip_stimer_stop(const void *data) +{ + struct sip_dialog *dialog = (struct sip_dialog *) data; + + sip_stimer_delete_sched(dialog); + ao2_t_cleanup(dialog, "drop dialog"); + + return 0; +} + +/* Stop session timer */ +void sip_stimer_stop(struct sip_dialog *dialog) +{ + dialog->stimer->active = FALSE; + ao2_t_bump(dialog, "bump dialog"); + + if (ast_sched_add(sip_sched_context, 0, __sip_stimer_stop, dialog) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(dialog, "drop dialog"); + } +} + +/* Run by the sched thread. */ +static int __sip_stimer_start(const void *data) +{ + struct sip_dialog *dialog; + unsigned int timeout; + + 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. + * The minimum of 32 seconds and one third of the session interval is RECOMMENDED. */ + timeout = dialog->stimer->interval * 1000; + + if (dialog->stimer->refresher == SIP_STIMER_REFRESHER_UAC) { + timeout /= 2; + } else { + timeout -= MIN(timeout / 3, 32000); + } + + /* in the event a timer is already going, stop it */ + ao2_t_bump(dialog, "bump dialog"); + + if ((dialog->stimer->sched_id = ast_sched_add(sip_sched_context, timeout, sip_stimer_timeout, dialog)) == -1) { + ao2_t_cleanup(dialog, "drop dialog"); + } else { + ast_debug(2, "Session timer started on '%s' expires in %ums\n", dialog->call_id, timeout); + } + + ao2_t_cleanup(dialog, "drop dialog"); + + return 0; +} + +/* Start session timer */ +void sip_stimer_start(struct sip_dialog *dialog) +{ + dialog->stimer->active = TRUE; + ao2_t_bump(dialog, "bump dialog"); + + if (ast_sched_add(sip_sched_context, 0, __sip_stimer_start, dialog) == -1) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(dialog, "drop dialog"); + } +} + +/* Restart session timer */ +void sip_stimer_restart(struct sip_dialog *dialog) +{ + if (dialog->stimer->active) { + sip_stimer_start(dialog); + } +} diff -durN asterisk-22.3.0.orig/channels/sip/utils.c asterisk-22.3.0/channels/sip/utils.c --- asterisk-22.3.0.orig/channels/sip/utils.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/channels/sip/utils.c 2025-04-17 11:11:22.825702903 +1200 @@ -0,0 +1,2411 @@ +/* + * 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/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/request.h" +#include "include/proxy.h" +#include "include/stimer.h" +#include "include/peers.h" +#include "include/dialog.h" +#include "include/utils.h" +#include "include/config.h" + +static int sip_cmp_parameters(const char *params1, const char *params2); +static int sip_cmp_headers(const char *header1, const char *header2); +static int sip_cmp_domain(const char *domain1, const char *domain2); +static int sip_get_name_and_number(const char *contact, struct ast_str **name, struct ast_str **number); + +/* The core structure to setup dialogs. We parse incoming messages by using structure and then route the messages according to the type */ +const struct sip_methods sip_methods[] = { + [SIP_METHOD_OPTIONS] = {"OPTIONS", FALSE, TRUE}, + [SIP_METHOD_INVITE] = {"INVITE", TRUE, TRUE}, + [SIP_METHOD_ACK] = {"ACK", FALSE, FALSE}, + [SIP_METHOD_BYE] = {"BYE", FALSE, FALSE}, + [SIP_METHOD_CANCEL] = {"CANCEL", FALSE, FALSE}, + [SIP_METHOD_REGISTER] = {"REGISTER", FALSE, TRUE}, + [SIP_METHOD_NOTIFY] = {"NOTIFY", FALSE, TRUE}, + [SIP_METHOD_REFER] = {"REFER", FALSE, TRUE}, + [SIP_METHOD_SUBSCRIBE] = {"SUBSCRIBE", FALSE, TRUE}, + [SIP_METHOD_PUBLISH] = {"PUBLISH", FALSE, TRUE}, + [SIP_METHOD_UPDATE] = {"UPDATE", FALSE, FALSE}, + [SIP_METHOD_MESSAGE] = {"MESSAGE", FALSE, TRUE}, + [SIP_METHOD_INFO] = {"INFO", FALSE, FALSE}, + [SIP_METHOD_PRACK] = {"PRACK", FALSE, FALSE}, + [SIP_METHOD_PING] = {"PING", FALSE, FALSE}, +}; + +const struct sip_options sip_options[] = { + [SIP_OPTION_REPLACES] = {"replaces", TRUE}, /* RFC3891: Replaces: header for transfer */ + [SIP_OPTION_TIMER] = {"timer", TRUE}, /* RFC4028: SIP Session-Timers */ + [SIP_OPTION_NOREFERSUB] = {"norefersub", FALSE}, /* Disable the REFER subscription, RFC 4488 */ + [SIP_OPTION_EXTENDED_REFER] = {"extended-refer", FALSE}, /* Cisco */ + [SIP_OPTION_100REL] = {"100rel", FALSE}, /* RFC3262: PRACK 100% reliability */ + [SIP_OPTION_EARLY_SESSION] = {"early-session", FALSE}, /* RFC3959: SIP Early session support */ + [SIP_OPTION_EVENTLIST] = {"eventlist", FALSE}, /* SIMPLE events: RFC4662 */ + [SIP_OPTION_FROM_CHANGE] = {"from-change", FALSE}, /* RFC 4916- Connected line ID updates */ + [SIP_OPTION_GRUU] = {"gruu", FALSE}, /* GRUU: Globally Routable User Agent URI's */ + [SIP_OPTION_HISTINFO] = {"histinfo", FALSE}, /* RFC4244 History info */ + [SIP_OPTION_JOIN] = {"join", FALSE}, /* RFC3911: SIP Join header support */ + [SIP_OPTION_OUTBOUND] = {"outbound", FALSE}, /* SIP outbound - the final NAT battle - draft-sip-outbound */ + [SIP_OPTION_PATH] = {"path", FALSE}, /* RFC3327: Path support */ + [SIP_OPTION_PREF] = {"pref", FALSE}, /* RFC3840: Callee preferences */ + [SIP_OPTION_PRECONDITION] = {"precondition", FALSE}, /* RFC3312: Precondition support */ + [SIP_OPTION_PRIVACY] = {"privacy", FALSE}, /* RFC3323: Privacy with proxies*/ + [SIP_OPTION_RECIPIENT_LIST_INVITE] = {"recipient-list-invite", FALSE}, /* RFC-ietf-sip-uri-list-conferencing-02.txt conference invite lists */ + [SIP_OPTION_RECIPIENT_LIST_SUBSCRIBE] = {"recipient-list-subscribe", FALSE}, /* RFC-ietf-sip-uri-list-subscribe-02.txt - subscription lists */ + [SIP_OPTION_RESOURCE_PRIORITY] = {"resource-priority", FALSE}, /* RFC4412 Resource priorities */ + [SIP_OPTION_SEC_AGREE] = {"sec_agree", FALSE}, /* RFC3329: Security agreement mechanism */ + [SIP_OPTION_SDP_ANAT] = {"sdp-anat", FALSE}, /* RFC4092: Usage of the SDP ANAT Semantics in the SIP */ + [SIP_OPTION_TARGET_DIALOG] = {"tdialog", FALSE}, /* RFC4538: Target-dialog */ +}; + +/* A per-thread buffer for transport to string conversion */ +AST_THREADSTORAGE(sip_transports2str_buf); + +/* A per-thread buffer for options to string conversion */ +AST_THREADSTORAGE(sip_options2str_buf); + +/* Converts ascii port to int representation. If no pt buffer is provided or the pt has errors when being converted + * to an int value, the port provided as the standard is used. */ +unsigned int sip_str2port(const char *buf, unsigned int standard_port) +{ + int port; + + if (ast_strlen_zero(buf) || (sscanf(buf, "%30d", &port) != 1) || (port < 1) || (port > 65535)) { + port = standard_port; + } + + return port; +} + +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"; +} + +const char *sip_force_rport2str(struct ast_flags *flags) +{ + if (ast_test_flag(&flags[2], SIP_NAT_AUTO_RPORT)) { + return ast_test_flag(&flags[0], SIP_NAT_FORCE_RPORT) ? "Auto (Yes)" : "Auto (No)"; + } + + return AST_CLI_YESNO(ast_test_flag(&flags[0], SIP_NAT_FORCE_RPORT)); +} + +const char *sip_comedia2str(struct ast_flags *flags) +{ + if (ast_test_flag(&flags[2], SIP_NAT_AUTO_COMEDIA)) { + return ast_test_flag(&flags[1], SIP_SYMMETRIC_RTP) ? "Auto (Yes)" : "Auto (No)"; + } + + return AST_CLI_YESNO(ast_test_flag(&flags[1], SIP_SYMMETRIC_RTP)); +} + +/* Convert DTMF mode to printable string */ +const char *sip_invite_state2str(int state) +{ + switch (state) { + case SIP_INVITE_NONE: + return "None"; + case SIP_INVITE_CALLING: + return "Calling (Trying)"; + case SIP_INVITE_PROCEEDING: + return "Proceeding"; + case SIP_INVITE_EARLY_MEDIA: + return "Early media"; + case SIP_INVITE_COMPLETED: + return "Completed (done)"; + case SIP_INVITE_CONFIRMED: + return "Confirmed (up)"; + case SIP_INVITE_TERMINATED: + return "Done"; + case SIP_INVITE_CANCELLED: + return "Cancelled"; + default: + break; + } + + return "Unknown"; +} + +/* Convert transfer status to string */ +const char *sip_refer_status2str(int status) +{ + switch (status) { + case SIP_REFER_NONE: + return "None"; + case SIP_REFER_SENT: + return "Request sent"; + case SIP_REFER_ACCEPTED: + return "Accepted"; + case SIP_REFER_200_OK: + return "Done"; + case SIP_REFER_FAILED: + return "Failed"; + case SIP_REFER_NO_AUTH: + return "Failed - auth failure"; + default: + break; + } + + return "Unknown"; +} + +/* Convert DTMF mode to printable string */ +const char *sip_dtmf_mode2str(int mode) +{ + switch (mode) { + case SIP_DTMF_RFC2833: + return "rfc2833"; + case SIP_DTMF_INBAND: + return "inband"; + case SIP_DTMF_AUTO: + return "auto"; + default: + break; + } + + return "unknown"; +} + +/* Convert AllowOverlap setting to printable string */ +const char *sip_allow_overlap2str(int mode) +{ + switch (mode) { + case SIP_ALLOW_OVERLAP_YES: + return "Yes"; + case SIP_ALLOW_OVERLAP_DTMF: + return "DTMF"; + case SIP_ALLOW_OVERLAP_NO: + return "No"; + default: + break; + } + + return "Unknown"; +} + +const char *sip_trust_id_outbound2str(int mode) +{ + switch (mode) { + case SIP_TRUST_ID_OUTBOUND_NO: + return "No"; + case SIP_TRUST_ID_OUTBOUND_YES: + return "Yes"; + default: + break; + } + + return "Unknown"; +} + +/* Display SIP nat mode */ +const char *sip_nat_mode2str(struct ast_flags *flags) +{ + return ast_test_flag(&flags[0], SIP_NAT_FORCE_RPORT) ? "NAT" : "no NAT"; +} + +const char *sip_t38_ecmode2str(int ecmode) +{ + switch (ecmode) { + case SIP_T38_SUPPORT_UDPTL: + return "None"; + case SIP_T38_SUPPORT_UDPTL_FEC: + return "FEC"; + case SIP_T38_SUPPORT_UDPTL_REDUNDANCY: + return "Redundancy"; + 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 < 500 && cause >= 400) { + /* 4xx class error that is unknown - someting wrong with our request */ + return AST_CAUSE_INTERWORKING; + } else if (cause < 600 && cause >= 500) { + /* 5xx class error - problem in the remote end */ + return AST_CAUSE_CONGESTION; + } else if (cause < 700 && cause >= 600) { + /* 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: + ast_debug(1, "Hangup cause %d (no match found in SIP)\n", cause); + return NULL; + } +} + +/* Return int representing a bit field of transport types found in const char *transport */ +int sip_str2transport(const char *transport) +{ + if (ast_strlen_zero(transport)) { + return 0; + } + + 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 { + return 0; + } +} + +/* Return configuration of transports for a device */ +const char *sip_transports2str(unsigned int transports) +{ + char *buf; + int buf_len; + + if (!transports) { + return "UNKNOWN"; + } + + if (!(buf = ast_threadstorage_get(&sip_transports2str_buf, 16))) { + return ""; + } + + buf[0] = '\0'; + buf_len = 0; + + if (transports & AST_TRANSPORT_UDP) { + strcpy(buf + buf_len, "UDP,"); + buf_len += 4; + } + + if (transports & AST_TRANSPORT_TCP) { + strcpy(buf + buf_len, "TCP,"); + buf_len += 4; + } + + if (transports & AST_TRANSPORT_TLS) { + strcpy(buf + buf_len, "TLS,"); + buf_len += 4; + } + + /* Remove the trailing ',' if present */ + if (buf_len) { + buf[buf_len - 1] = '\0'; + } + + return buf; +} + +/* 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"; +} + +/* Get caller id name from SIP headers, copy into output buffer + * From RFC3261: + * + * From = ( "From" / "f" ) HCOLON from-spec + * from-spec = ( name-addr / addr-spec ) *( SEMI from-param ) + * name-addr = [ display-name ] LAQUOT addr-spec RAQUOT + * display-name = *(token LWS)/ quoted-string + * token = 1*(alphanum / "-" / "." / "!" / "%" / "*" + * / "_" / "+" / "`" / "'" / "~" ) + * quoted-string = SWS DQUOTE *(qdtext / quoted-pair ) DQUOTE + * qdtext = LWS / %x21 / %x23-5B / %x5D-7E + * / UTF8-NONASCII + * quoted-pair = "\" (%x00-09 / %x0B-0C / %x0E-7F) + * + * HCOLON = *WSP ":" SWS + * SWS = [LWS] + * LWS = *[*WSP CRLF] 1*WSP + * WSP = (SP / HTAB) + * + * Deviations from it: + * - following CRLF's in LWS is not done (here at least) + * - ascii NUL is never legal as it terminates the C-string + * - utf8-nonascii is not checked for validity */ +int sip_get_name(const char *contact, struct ast_str **name) +{ + const char *name_start, *name_end; + + ast_str_reset(*name); + + if (*contact == '<') { + return 0; + } + + name_start = contact; + + /* quoted-string rules */ + if (*contact == '"') { + name_start++; + + for (name_end = name_start; *name_end; name_end++) { + if (*name_end == '"') { /* end of quoted-string */ + break; + } else if (*name_end == 0x5c) { /* quoted-pair = "\" (%x00-09 / %x0B-0C / %x0E-7F) */ + name_end++; + + if (!*name_end) { + break; + } + + if (*name_end > 0x7f || *name_end == 0xa || *name_end == 0xd) { + continue; /* not a valid quoted-pair, so skip it */ + } + } else if ((*name_end != 0x9 && *name_end < 0x20) || *name_end == 0x7f) { + continue; /* skip this invalid character. */ + } + } + + /* if this is successful, name_end should be at the ending quote */ + if (*name_end != '"') { + ast_log(LOG_WARNING, "No ending quote for display-name was found\n"); + + return -1; + } + } else { /* either an addr-spec or tokenLWS-combo */ + for (name_end = name_start; *name_end; name_end++) { + /* token or WSP (without LWS) */ + if ((*name_end >= '0' && *name_end <= '9') || (*name_end >= 'A' && *name_end <= 'Z') || + (*name_end >= 'a' && *name_end <= 'z') || *name_end == '-' || *name_end == '.' || *name_end == '!' || + *name_end == '%' || *name_end == '*' || *name_end == '_' || *name_end == '+' || *name_end == '`' || + *name_end == '\'' || *name_end == '~' || *name_end == 0x9 || *name_end == ' ') { + } else if (*name_end == '<') { + /* we could assert that the previous char is LWS, but we don't care */ + break; + } else if (*name_end == ':') { + return -1; + } else { + continue; /* skip this invalid character */ + } + } + + if (*name_end != '<') { /* if we never found the start of addr-spec then this is invalid */ + ast_log(LOG_WARNING, "No starting angle-bracket was found\n"); + return -1; + } + + /* terminate name while trimming any trailing whitespace */ + do { + name_end--; + } while (name_start <= name_end && (*name_end == 0x9 || *name_end == ' ')); + + name_end++; + } + + ast_str_set_substr(name, 0, name_start, name_end - name_start); + + return 0; +} + +static int sip_get_name_and_number(const char *orig_contact, struct ast_str **name, struct ast_str **number) +{ + char *contact, *user; + + /* strip the display-name portion off the beginning of the contact. */ + sip_get_name(orig_contact, name); + + /* get uri within < > brackets */ + contact = ast_strdupa(orig_contact); + contact = sip_get_in_brackets(contact); + + /* parse out the number here */ + if (sip_parse_uri(contact, "sip:,sips:", &user, NULL, NULL) || ast_strlen_zero(user)) { + ast_log(LOG_ERROR, "Cannot parse name and number from sip contact\n"); + return -1; + } + + /* number is not option, and must be present at this point */ + ast_uri_decode(user, ast_uri_sip_user); + ast_str_set(number, 0, "%s", user); + + return 0; +} + +char *sip_get_in_brackets(char *contact) +{ + char *sep; + + if ((sep = strchr(contact, '<'))) { + contact = sep + 1; + + if ((sep = strchr(contact, '>'))) { + *sep = '\0'; + } + } + + return contact; +} + +/* Get tag from header */ +int sip_get_tag(struct sip_request *request, const char *header, struct ast_str **tag) +{ + const char *tag_start, *tag_end, *contact; + + ast_str_reset(*tag); + + contact = sip_request_get_header(request, header); + + if (!(tag_start = strcasestr(contact, ";tag="))) { + return FALSE; + } + + tag_start += 5; + tag_end = tag_start + strcspn(tag_start, ";"); + + ast_str_set_substr(tag, 0, tag_start, tag_end - tag_start); + + return TRUE; +} + +int sip_get_address(const char *orig_contact, struct ast_sockaddr *address) +{ + char *contact, *user, *domain, *protocol; + enum ast_transport transport; + + /* Work on a copy */ + contact = ast_strdupa(orig_contact); + + /* We have only the part in here so we just need to parse a SIP URI. Note: The outbound proxy could be using UDP + * between the proxy and Asterisk. We still need to be able to send to the remote agent through the proxy. */ + + if (sip_parse_uri(contact, "sip:,sips:", &user, &domain, &protocol)) { + ast_log(LOG_WARNING, "Invalid contact uri %s (missing sip: or sips:)\n", orig_contact); + return -1; + } + + if (ast_strlen_zero(domain)) { + ast_log(LOG_WARNING, "Invalid URI: missing domain\n"); + return -1; + } + + if (!ast_strlen_zero(protocol) || !(transport = sip_str2transport(protocol))) { + transport = AST_TRANSPORT_UDP; + } + + if (ast_sockaddr_resolve_first_af(address, domain, 0, AST_AF_INET)) { + ast_log(LOG_WARNING, "Invalid host name in Contact (can't resolve in DNS): '%s'\n", domain); + return -1; + } + + /* set port */ + if (!ast_sockaddr_port(address)) { + int use_tls = transport == AST_TRANSPORT_TLS; + + ast_sockaddr_set_port(address, use_tls ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + } + + return 0; +} + +/*! Parse multiline SIP headers into one header This is used if pedantic_checking is enabled */ +void sip_compress_headers(char *data) +{ + int input_len, output_len, skip_blanks, want_boundary, done; + + skip_blanks = FALSE; + want_boundary = FALSE; + done = FALSE; + + input_len = 0; + output_len = 0; + + while (data[input_len] != '\0') { + /* Eliminate all CRs */ + if (data[input_len] == '\r') { + input_len++; + continue; + } + + /* Check for end-of-line */ + if (data[input_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[input_len + 1] == '\0') { + break; + } + + /* Check for a continuation line */ + if (!done && (data[input_len + 1] == ' ' || data[input_len + 1] == '\t')) { + /* Merge continuation line */ + input_len++; + continue; + } + + /* Propagate LF and start new line */ + data[output_len++] = data[input_len++]; + skip_blanks = FALSE; + + continue; + } else { + want_boundary = FALSE; + } + + if (!done && (data[input_len] == ' ' || data[input_len] == '\t')) { + if (skip_blanks) { + input_len++; + continue; + } + + data[output_len++] = data[input_len++]; + skip_blanks = TRUE; + + continue; + } + + data[output_len++] = data[input_len++]; + + if (skip_blanks) { + skip_blanks = FALSE; + } + } + + data[output_len] = '\0'; +} + +void sip_pedantic_decode(char *str) +{ + if (sip_config.pedantic_checking && !ast_strlen_zero(str)) { + ast_uri_decode(str, ast_uri_sip_user); + } +} + +/* Parses a URI in its components. */ +int sip_parse_uri(char *uri, const char *schemes, char **user, char **domain, char **transport) +{ + char *sep, *parameters; + const char *scheme; + int scheme_len; + + /* check for valid input */ + if (ast_strlen_zero(uri)) { + return -1; + } + + /* Initialize requested strings - some functions don't care if sip_parse_uri fails and will attempt to use string + * pointers passed into sip_parse_uri even after a sip_parse_uri failure */ + if (user) { + *user = ""; + } + + if (domain) { + *domain = ""; + } + + if (transport) { + *transport = ""; + } + + if (schemes) { + scheme = schemes; + + for (;;) { + if ((sep = strchr(scheme, ','))) { + scheme_len = sep - scheme; + sep += 1; + } else { + scheme_len = strlen(scheme); + } + + if (!strncasecmp(uri, scheme, scheme_len)) { + uri += scheme_len; + break; + } + + if (!sep) { + scheme = NULL; + break; + } + + scheme = sep; + } + + if (!scheme) { + ast_debug(1, "No supported scheme found in '%s' using the schemes %s\n", uri, schemes); + return -1; + } + } + + /* sip:user:password@host:port;transport=udp?name=value */ + /* sip:user:password@host:port;transport=tcp;user=phone */ + /* sip:user@host */ + /* sips:host?transport=tls */ + if ((sep = strchr(uri, '@'))) { + *sep++ = '\0'; + parameters = sep; + + if (domain) { + *domain = sep; + } + + if ((sep = strchr(uri, ':'))) { + *sep++ = '\0'; + + if (user) { + *user = uri; + } + } else if (user) { + *user = uri; + } + } else { + if (domain) { + *domain = uri; + } + + parameters = uri; + } + + if ((sep = strchr(parameters, ';'))) { + *sep++ = '\0'; + parameters = sep; + + if ((sep = strchr(parameters, '?'))) { + *sep++ = '\0'; + } + + while (!ast_strlen_zero(parameters)) { + if ((sep = strchr(parameters, ';'))) { + *sep++ = '\0'; + } + + if (!strncasecmp(parameters, "transport=", 10)) { + if (transport) { + *transport = parameters + 10; + } + } + + parameters = sep; + } + } + + return 0; +} + +/* Remove URI parameters at end of URI, not in username part though */ +char *sip_remove_uri_parameters(char *uri) +{ + char *domain, *parameters; + + if ((domain = strchr(uri, '@'))) { + if ((parameters = strchr(domain, ';'))) { + *parameters++ = '\0'; + } + } else if ((parameters = strchr(uri, ';'))) { + *parameters++ = '\0'; + } + + return uri; +} + +/*! Helper routine for sip_cmp_uri to compare URI parameters. This takes the parameters from two SIP URIs and determines + * if the URIs match. The rules for parameters *suck*. Here's a breakdown + * 1. If a parameter appears in both URIs, then they must have the same value in order for the URIs to match + * 2. If one URI has a user, maddr, ttl, or method parameter, then the other URI must also have that parameter and must + * have the same value in order for the URIs to match + * 3. All other headers appearing in only one URI are not considered when determining if URIs match */ +static int sip_cmp_parameters(const char *orig_parameters1, const char *orig_parameters2) +{ + char *parameters1, *parameters2, *name1, *value1, *name2, *value2; + int zero_len1, zero_len2, maddr_match, ttl_match, user_match, method_match; + + if (ast_strlen_zero(orig_parameters1)) { + parameters1 = NULL; + zero_len1 = TRUE; + } else { + parameters1 = ast_strdupa(orig_parameters1); + zero_len1 = FALSE; + } + + zero_len2 = ast_strlen_zero(orig_parameters2); + + /* Quick optimization. If both params are zero-length, then they match */ + if (zero_len1 && zero_len2) { + return 0; + } + + maddr_match = FALSE; + ttl_match = FALSE; + user_match = FALSE; + method_match = FALSE; + + for (value1 = strsep(¶meters1, ";"); value1; value1 = strsep(¶meters1, ";")) { + int matched = FALSE; + + name1 = strsep(&value1, "="); + + if (!value1) { + value1 = ""; + } + + /* Checkpoint reached. We have the name and value parsed for name1 We have to duplicate parameters2 each + * time through this loop or else the inner loop below will not work properly. */ + if (!zero_len2) { + parameters2 = ast_strdupa(orig_parameters2); + } else { + parameters2 = NULL; + } + + for (value2 = strsep(¶meters2, ";"); value2; value2 = strsep(¶meters2, ";")) { + name2 = strsep(&value2, "="); + + if (!value2) { + value2 = ""; + } + + if (!strcasecmp(name1, name2)) { + if (!strcasecmp(value1, value2)) { + matched = TRUE; + break; + } + + return 1; + } + } + + /* Check to see if the parameter is one of the 'must-match' parameters */ + if (!strcasecmp(name1, "maddr")) { + if (!matched) { + return 1; + } + + maddr_match = TRUE; + } else if (!strcasecmp(name1, "ttl")) { + if (!matched) { + return 1; + } + + ttl_match = TRUE; + } else if (!strcasecmp(name1, "user")) { + if (!matched) { + return 1; + } + + user_match = TRUE; + } else if (!strcasecmp(name1, "method")) { + if (!matched) { + return 1; + } + + method_match = TRUE; + } + } + + if (!zero_len2) { + parameters2 = ast_strdupa(orig_parameters2); + } else { + parameters2 = NULL; + } + + /* We've made it out of that horrible O(m*n) construct and there are no failures yet. We're not done yet, though, + * because parameters2 could have an maddr, ttl, user, or method header and parameters1 did not. */ + for (value2 = strsep(¶meters2, ";"); value2; value2 = strsep(¶meters2, ";")) { + name2 = strsep(&value2, "="); + + if ((!strcasecmp(name2, "maddr") && !maddr_match) || (!strcasecmp(name2, "ttl") && !ttl_match) || + (!strcasecmp(name2, "user") && !user_match) || (!strcasecmp(name2, "method") && !method_match)) { + return 1; + } + } + + return 0; +} + +/* Helper routine for sip_cmp_uri to compare URI headers. This takes the headers from two SIP URIs and determines if the URIs + * match. The rules for headers is simple. If a header appears in one URI, then it must also appear in the other URI. The order + * in which the headers appear does not matter. */ +static int sip_cmp_headers(const char *orig_headers1, const char *orig_headers2) +{ + char *headers1, *headers2, *header1; + int zero_len1, zero_len2; + + if (ast_strlen_zero(orig_headers1)) { + headers1 = NULL; + zero_len1 = TRUE; + } else { + headers1 = ast_strdupa(orig_headers1); + zero_len1 = FALSE; + } + + if (ast_strlen_zero(orig_headers2)) { + headers2 = NULL; + zero_len2 = TRUE; + } else { + headers2 = ast_strdupa(orig_headers2); + zero_len2 = FALSE; + } + + /* If one URI contains no headers and the other does, then they cannot possibly match */ + if (zero_len1 != zero_len2) { + return 1; + } + + if (zero_len1 && zero_len2) { + return 0; + } + + /* At this point, we can definitively state that both inputs are not zero-length. First, one more optimization. + * If the length of the headers is not equal, then we definitely have no match */ + if (strlen(headers1) != strlen(headers2)) { + return 1; + } + + for (header1 = strsep(&headers1, "&"); header1; header1 = strsep(&headers1, "&")) { + if (!strcasestr(headers2, header1)) { + return 0; + } + } + + return 1; +} + +/* Compare domain sections of SIP URIs. For hostnames, a case insensitive string comparison is used. For IP addresses, + * a binary comparison is used. This is mainly because IPv6 addresses have many ways of writing the same address. + * For specifics about IP address comparison, see the following document: http://tools.ietf.org/html/draft-ietf-sip-ipv6-abnf-fix-05 */ +static int sip_cmp_domain(const char *domain1, const char *domain2) +{ + struct ast_sockaddr address1, address2; + int address1_parsed, address2_parsed; + + address1_parsed = ast_sockaddr_parse(&address1, domain1, 0); + address2_parsed = ast_sockaddr_parse(&address2, domain2, 0); + + if (address1_parsed != address2_parsed) { + /* One domain was an IP address and the other had a domain name. FAIL! */ + return 1; + } + + /* Both are domain names. A string comparison will work perfectly here */ + if (!address1_parsed) { + return strcasecmp(domain1, domain2); + } + + /* Both contain IP addresses */ + return ast_sockaddr_cmp(&address1, &address2); +} + +int sip_cmp_uri(const char *orig_uri1, const char *orig_uri2) +{ + char *uri1, *uri2, *scheme1, *scheme2, *user1, *user2, *domain1, *domain2, *parameters1, *parameters2, *headers1, *headers2; + + if (!orig_uri1 || !orig_uri2) { + return 1; + } + + uri1 = ast_strdupa(orig_uri1); + uri2 = ast_strdupa(orig_uri2); + + ast_uri_decode(uri1, ast_uri_sip_user); + ast_uri_decode(uri2, ast_uri_sip_user); + + scheme1 = uri1; + scheme2 = uri2; + + if ((user1 = strchr(scheme1, ':'))) { + *user1++ = '\0'; + } + + if ((user2 = strchr(scheme2, ':'))) { + *user2++ = '\0'; + } + + if (strcmp(scheme1, scheme2)) { + return 1; + } + + /* This function is tailored for SIP and SIPS URIs. There's no need to check scheme2 since we have determined scheme1 and + * scheme2 are equivalent already. */ + if (strcmp(scheme1, "sip") && strcmp(scheme1, "sips")) { + return 1; + } + + if (ast_strlen_zero(user1) || ast_strlen_zero(user2)) { + return 1; + } + + if ((domain1 = strchr(user1, '@'))) { + *domain1++ = '\0'; + } + + if ((domain2 = strchr(user2, '@'))) { + *domain2++ = '\0'; + } + + /* Check for mismatched username and passwords. This is the only case-sensitive comparison of a SIP URI */ + if ((domain1 && !domain2) || (domain2 && !domain1) || (domain1 && domain2 && strcmp(user1, user2))) { + return 1; + } + + if (!domain1) { + domain1 = user1; + } + + if (!domain2) { + domain2 = user2; + } + + /* Strip off the parameters and headers so we can compare domain and port */ + if ((parameters1 = strchr(domain1, ';'))) { + *parameters1++ = '\0'; + } + + if ((parameters2 = strchr(domain2, ';'))) { + *parameters2++ = '\0'; + } + + /* Headers come after parameters, but there may be headers without parameters */ + if ((headers1 = strchr(parameters1 ? parameters1 : domain1, '?'))) { + *headers1++ = '\0'; + } + + if ((headers2 = strchr(parameters2 ? parameters2 : domain2, '?'))) { + *headers2++ = '\0'; + } + + if (sip_cmp_domain(domain1, domain2)) { + return 1; + } + + /* Headers have easier rules to follow, so do those first */ + if (sip_cmp_headers(headers1, headers2)) { + return 1; + } + + /* And now the parameters. Ugh */ + if (sip_cmp_parameters(parameters1, parameters2)) { + return 1; + } + + return 0; +} + +/* Returns true if 'name' (with optional trailing whitespace) matches the sip method name */ +int sip_cmp_method(int method, const char *name) +{ + int name_len = strlen(sip_methods[method].name); + + /* true if the string is long enough, and ends with whitespace, and matches */ + return strlen(name) >= name_len && name[name_len] < 33 && !strncasecmp(sip_methods[method].name, name, name_len); +} + +/* sip_find_method: Find SIP method from header */ +int sip_find_method(const char *name) +{ + int method; + + for (method = 0; method < ARRAY_LEN(sip_methods); method++) { + if (sip_cmp_method(method, name)) { + return method; + } + } + + return SIP_METHOD_UNKNOWN; +} + +void sip_set_method_allowed(unsigned int *allowed_methods, int method, int allow) +{ + if (allow) { + (*allowed_methods) |= (1 << method); + } else { + (*allowed_methods) &= ~(1 << method); + } +} + +/* Check if method is allowed for a device or a dialog */ +int sip_allowed_method(unsigned int *allowed_methods, int method) +{ + return (*allowed_methods & (1 << method)) != 0; +} + +void sip_parse_methods(unsigned int *methods, const char *orig_allow) +{ + char *name, *allow; + + allow = ast_strdupa(orig_allow); + + while ((name = strsep(&allow, ","))) { + int method; + + name = ast_skip_blanks(name); + + if ((method = sip_find_method(name)) != SIP_METHOD_UNKNOWN) { + sip_set_method_allowed(methods, method, TRUE); + } + } +} + +/* Parse supported header in incoming packet. This function parses through the options parameters and builds a bit field + * representing all the SIP options in that field. When an item is found that is not supported, it is copied to the + * unsupported out buffer. */ +unsigned int sip_parse_options(struct sip_request *request, const char *header, struct ast_str **unsupported, size_t max_len) +{ + char *supported, *name; + unsigned int options; + + supported = ast_strdupa(sip_request_get_header(request, header)); + + if (ast_strlen_zero(supported)) { + return 0; + } + + ast_debug(3, "Supported: '%s'\n", supported); + + options = 0; + + while ((name = strsep(&supported, ","))) { + int option; + + /* trim leading and trailing whitespace */ + name = ast_strip(name); + + for (option = 0; option < ARRAY_LEN(sip_options); option++) { + if (!strcasecmp(sip_options[option].name, name)) { + break; + } + } + + if (sip_options[option].supported) { + ast_debug(3, "Supported option '%s'\n", name); + sip_set_option_supported(&options, option); + } else if (option < ARRAY_LEN(sip_options) && unsupported) { + /* If option is not supported, add to unsupported out buffer */ + ast_str_append(unsupported, max_len, "%s%s", ast_str_strlen(*unsupported) ? "," : "", name); + } else { + ast_debug(3, "Unsupported option '%s'\n", name); + } + } + + return options; +} + +int sip_supported_option(unsigned int *options, int option) +{ + return (*options & (1 << option)) != 0; +} + +void sip_set_option_supported(unsigned int *options, int option) +{ + *options |= (1 << option); +} + +char *sip_options2str(unsigned int options) +{ + char *buf; + int buf_len, option; + + if (!(buf = ast_threadstorage_get(&sip_options2str_buf, 128))) { + return ""; + } + + buf[0] = '\0'; + buf_len = 0; + + for (option = 0; option < ARRAY_LEN(sip_options); option++) { + if (sip_supported_option(&options, option)) { + if (buf_len + strlen(sip_options[option].name) + 2 >= 128) { + break; + } + + if (buf_len) { + strcpy(buf + buf_len, ","); + buf_len += 1; + } + + strcpy(buf + buf_len, sip_options[option].name); + buf_len += strlen(sip_options[option].name); + } + } + + return buf; +} + +int sip_parse_via(struct sip_request *request) +{ + char *via = ast_strdupa(sip_request_get_header(request, "Via")); + + via = strsep(&via, ","); /* Only use the first, leftmost neader */ + + if (ast_strlen_zero(via) || strncmp(via, "SIP/2.0", 7)) { + ast_log(LOG_WARNING, "Not a Via header: %s\n", via); + return -1; + } + + ast_free(request->via_sent_by); + ast_free(request->via_branch); + ast_free(request->via_maddr); + + request->via_sent_by = NULL; + request->via_branch = NULL; + request->via_maddr = NULL; + request->via_ttl = 1; + request->via_rport = FALSE; + + strsep(&via, " "); + + request->via_sent_by = ast_strdup(strsep(&via, ";")); + + while (!ast_strlen_zero(via)) { + if (!strncmp(via, "branch=", 7)) { + via += 7; + request->via_branch = ast_strdup(strsep(&via, ";")); + } else if (!strncmp(via, "maddr=", 6)) { + via += 6; + request->via_maddr = ast_strdup(strsep(&via, ";")); + } else if (!strncmp(via, "ttl=", 4)) { + via += 4; + request->via_ttl = atoi(strsep(&via, ";")); + } else if (!strncmp(via, "rport", 5)) { + via += 5; + request->via_rport = via[0] != '='; /* rport query, not answer */ + strsep(&via, ";"); + } else { + strsep(&via, ";"); + } + } + + return 0; +} + +/* Takes the digest response and parses it */ +int sip_parse_authorization(char *authorization, struct sip_authorization_data *authorization_data) +{ + memset(authorization_data, 0, sizeof(*authorization_data)); + + if (strncmp(authorization, "Digest ", 7)) { + return -1; + } + + authorization += 7; + + while (!ast_strlen_zero(authorization)) { + char *name, *value; + + name = ast_skip_blanks(strsep(&authorization, "=")); + + if (*authorization == '"') { + *authorization++ = '\0'; + value = strsep(&authorization, "\""); + } else { + value = authorization; + } + + strsep(&authorization, ","); /* Skip to the next nameeter */ + + if (!strcmp(name, "uri")) { + authorization_data->uri = value; + } else if (!strcmp(name, "username")) { + authorization_data->username = value; + } else if (!strcmp(name, "realm")) { + authorization_data->realm = value; + } else if (!strcmp(name, "domain")) { + authorization_data->domain = value; + } else if (!strcmp(name, "nonce")) { + authorization_data->nonce = value; + } else if (!strcmp(name, "opaque")) { + authorization_data->opaque = value; + } else if (!strcmp(name, "qop")) { + authorization_data->qop = value; + } else if (!strcmp(name, "response")) { + authorization_data->response = value; + } else if (!strcmp(name, "algorithm")) { + authorization_data->algorithm = value; + } else { + ast_debug(1, "Unknown authorization parameter '%s': %s\n", name, value); + } + } + + return 0; +} + +/* Save contact header for 200 OK on INVITE */ +int sip_parse_ok_contact(struct sip_dialog *dialog, struct sip_request *request) +{ + char *contact = ast_strdupa(sip_request_get_header(request, "Contact")); + + /* Look for brackets */ + contact = sip_get_in_brackets(contact); + + /* Save full contact to call dialog for later bye or re-invite */ + ast_string_field_set(dialog, full_contact, contact); + + /* Save URI for later ACKs, BYE or RE-invites */ + ast_string_field_set(dialog, ok_contact_uri, contact); + + /* We should return false for URI:s we can't handle, like tel:, mailto:,ldap: etc */ + return TRUE; +} + +/* Parse the Allow header to see what methods the endpoint we are communicating with allows. We parse the allow header on + * incoming registrations and save the result to the SIP peer that is registering. When the registration expires, we clear + * what we know about the peer's allowed methods. When the peer re-registers, we once again parse to see if the list of + * allowed methods has changed. For peers that do not register, we parse the first message we receive during a call to see + * what is allowed, and save the information for the duration of the call.*/ +int sip_parse_allow(struct sip_dialog *dialog, struct sip_request *request) +{ + const char *allow = sip_request_get_header(request, "Allow"); + + if (ast_strlen_zero(allow)) { + return 0; + } + + dialog->allowed_methods = 0; + + sip_parse_methods(&dialog->allowed_methods, allow); + + 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_request *request) +{ + int hangupcause; + const char *reason, *cause; + + if (!dialog->channel) { + return -1; + } + + if (!ast_test_flag(&dialog->flags[1], SIP_Q850_REASON) || !(reason = sip_request_get_header(request, "Reason"))) { + return -1; + } + + reason = ast_skip_blanks(reason); + + if (strncasecmp(reason, "Q.850", 5)) { + return -1; + } + + if ((cause = strstr(reason, ";cause=")) && sscanf(cause + 7, "%3d", &hangupcause) == 1) { + ast_channel_hangupcause_set(dialog->channel, hangupcause & 0x7f); + + if (request->debug) { + ast_verb(3, "Using Reason header for hangup cause code: %d\n", ast_channel_hangupcause(dialog->channel)); + } + + return 0; + } + + return -1; +} + +/* Build route list from Path header RFC 3327 requires that the Path header contains SIP URIs with lr paramter. Thus, we do not + * care about strict routing SIP routers. */ +int sip_parse_path(struct sip_dialog *dialog, struct sip_peer *peer, struct sip_request *request, const char *path) +{ + sip_route_clear(&peer->path); + + if (!ast_test_flag(&peer->flags[0], SIP_USE_PATH)) { + 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"); + + if (request) { + int iter = 0; + + for (;;) { + const char *path = sip_request_next_header(request, "Path", &iter); + + if (ast_strlen_zero(path)) { + break; + } + + sip_route_parse(&peer->path, path, FALSE); + } + } else if (path) { + sip_route_parse(&peer->path, path, FALSE); + } + + /* Caches result for any dialog->route copied from peer->path */ + sip_route_is_strict(&peer->path); + + if (dialog && sip_debug) { + sip_route_dump(&peer->path); + } + + return 0; +} + +/* Get name, number and presentation from remote party id header, returns true if a valid header was found and it was different + * from the current caller id. */ +int sip_parse_remote_party_id(struct sip_dialog *dialog, struct sip_request *request) +{ + char *remote_party_id, *caller_number, *caller_name, *parameters; + int asserted_identity, presentation; + + if (!ast_test_flag(&dialog->flags[0], SIP_TRUST_REMOTE_PARTY_ID)) { + return 0; + } + + remote_party_id = ast_strdupa(sip_request_get_header(request, "Remote-Party-ID")); + + if (ast_strlen_zero(remote_party_id)) { + remote_party_id = ast_strdupa(sip_request_get_header(request, "P-Asserted-Identity")); + + if (ast_strlen_zero(remote_party_id)) { + return 0; + } + + asserted_identity = TRUE; + } else { + asserted_identity = FALSE; + } + + if ((parameters = strchr(remote_party_id, '>'))) { + parameters = strchr(parameters, ';'); + } + + caller_name = remote_party_id; + caller_number = sip_get_in_brackets(remote_party_id); + + /* Quoted (note that we're not dealing with escapes properly) */ + if (*caller_name == '"') { + caller_name++; + caller_name = strsep(&caller_name, "\""); + } + + if (!strncmp(caller_number, "sip:", 4)) { + caller_number += 4; + } else if (!strncmp(caller_number, "sips:", 5)) { + caller_number += 5; + } else { + ast_log(LOG_WARNING, "Not a SIP Remote-Party-ID/P-Asserted-Identity header: %s\n", caller_number); + return 0; + } + + caller_number = strsep(&caller_number, "@"); + + if (sip_config.shrink_callerid && ast_is_shrinkable_phonenumber(caller_number)) { + ast_shrink_phone_number(caller_number); + } + + presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED; + + if (asserted_identity) { + if (!strcasecmp(sip_request_get_header(request, "Privacy"), "id")) { + presentation = AST_PRES_PROHIB_USER_NUMBER_PASSED_SCREEN; + } else if (!strcasecmp(caller_number, "anonymous@anonymous.invalid")) { + presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; + } + } else if (parameters) { + char *privacy, *screen; + + privacy = strstr(parameters, ";privacy="); + screen = strstr(parameters, ";screen="); + + if (privacy) { + privacy += 9; + privacy = strsep(&privacy, ";"); + } else { + privacy = "no"; + } + + if (screen) { + screen += 8; + screen = strsep(&screen, ";"); + } else { + screen = "no"; + } + + if (!strcasecmp(privacy, "full")) { + if (!strcasecmp(screen, "yes")) { + presentation = AST_PRES_PROHIB_USER_NUMBER_PASSED_SCREEN; + } else if (!strcasecmp(screen, "no")) { + presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; + } + } else { + if (!strcasecmp(screen, "yes")) { + presentation = AST_PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN; + } else if (!strcasecmp(screen, "no")) { + presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED; + } + } + } + + /* Only return true if the supplied caller id is different */ + if (!strcasecmp(dialog->caller_number, caller_number) && + !strcasecmp(dialog->caller_name, caller_name) && dialog->caller_presentation == presentation) { + return 0; + } + + ast_string_field_set(dialog, caller_number, caller_number); + ast_string_field_set(dialog, caller_name, caller_name); + dialog->caller_presentation = presentation; + + if (dialog->channel) { + ast_set_callerid(dialog->channel, caller_number, caller_name, NULL); + + ast_channel_caller(dialog->channel)->id.name.presentation = presentation; + ast_channel_caller(dialog->channel)->id.number.presentation = presentation; + } + + return 1; +} + +int sip_parse_diversion(struct sip_dialog *dialog, struct sip_request *request, int set_call_forward) +{ + char *diversion, *reason; + struct ast_str *from_name, *from_number, *to_name, *to_number; + int code; + + from_name = ast_str_alloca(128); + from_number = ast_str_alloca(128); + + reason = NULL; + code = 0; + + diversion = ast_strdupa(sip_request_get_header(request, "Diversion")); + + if (!ast_strlen_zero(diversion)) { + char *domain, *parameters; + + if ((parameters = strchr(diversion, '>'))) { + parameters++; + } + + sip_get_name_and_number(diversion, &from_name, &from_number); + diversion = sip_get_in_brackets(diversion); + + if (sip_parse_uri(diversion, "sip:,sips:", NULL, &domain, NULL)) { + return -1; + } + + if (dialog->channel) { + pbx_builtin_setvar_helper(dialog->channel, "__SIPRDNISDOMAIN", domain); + } + + /* Check if we have a reason parameter */ + if (parameters && (reason = strcasestr(parameters, ";reason="))) { + reason += 8; + reason = strsep(&reason, ";"); + + ast_debug(2, "RDNIS for this call is %s (reason %s)\n", ast_str_buffer(from_number), reason); + + /* Remove any enclosing double-quotes */ + if (*reason == '"') { + reason = ast_strip_quoted(reason, "\"", "\""); + } + + if ((code = ast_redirecting_reason_parse(reason)) < 0) { + code = AST_REDIRECTING_REASON_UNKNOWN; + } else { + reason = NULL; + } + + if (dialog->channel) { + pbx_builtin_setvar_helper(dialog->channel, "__SIPREDIRECTREASON", S_OR(reason, "")); + } + } + } else { + if (!request->response) { + return 0; + } + + sip_get_name_and_number(sip_request_get_header(request, "From"), &from_name, &from_number); + } + + ast_string_field_set(dialog, redirecting_from_name, ast_str_buffer(from_name)); + ast_string_field_set(dialog, redirecting_from_number, ast_str_buffer(from_number)); + + ast_string_field_set(dialog, redirecting_reason, NULL); + dialog->redirecting_code = 0; + + to_name = ast_str_alloca(128); + to_number = ast_str_alloca(128); + + if (request->response) { + char *contact, *domain, *transport; + + contact = ast_strdupa(sip_request_get_header(request, "Contact")); + + sip_get_name_and_number(contact, &to_name, &to_number); + contact = sip_get_in_brackets(contact); + + if (sip_parse_uri(contact, "sip:,sips:", NULL, &domain, &transport)) { + return -1; + } + + if (!sip_str2transport(transport)) { + transport = ""; + } + + if (set_call_forward && dialog->channel) { + if (ast_test_flag(&dialog->flags[0], SIP_PROMISCUOUS_REDIRECT)) { + /* Build an ad-hoc SIP dial string */ + ast_channel_call_forward_build(dialog->channel, "SIP/%s::::%s@%s", + ast_str_buffer(to_number), transport, domain); + + ast_debug(2, "Found promiscuous redirection %s'\n", ast_channel_call_forward(dialog->channel)); + } else { + ast_uri_decode(ast_str_buffer(to_number), ast_uri_sip_user); + ast_str_update(to_number); + + pbx_builtin_setvar_helper(dialog->channel, "SIPDOMAIN", S_OR(domain, "")); + ast_channel_call_forward_set(dialog->channel, ast_str_buffer(to_number)); + + ast_debug(2, "Received 302 Redirect to extension '%s' (domain %s)\n", + ast_str_buffer(to_number), S_OR(domain, "")); + } + } + } else { + sip_get_name_and_number(sip_request_get_header(request, "To"), &to_name, &to_number); + + } + + ast_string_field_set(dialog, redirecting_to_name, ast_str_buffer(to_name)); + ast_string_field_set(dialog, redirecting_to_number, ast_str_buffer(to_number)); + + return 0; +} + +/* Call transfer support (the REFER method). Extracts Refer headers into dialog dialog structure If we get a SIPS uri in + * the refer-to header, we're required to set up a secure signalling path to that extension. As a minimum, this needs to be + * added to a channel variable, if not a channel flag. */ +int sip_parse_refer_to(struct sip_dialog *dialog, struct sip_request *request) +{ + char *refer_to, *referred_by, *refer_to_domain, *replaces; + const char *transfer_context; + + refer_to = ast_strdupa(sip_request_get_header(request, "Refer-To")); + + if (ast_strlen_zero(refer_to)) { + ast_log(LOG_WARNING, "Refer-To Header missing. Skipping transfer\n"); + return SIP_PARSE_REFER_MISSING_HEADER; /* Syntax error */ + } + + refer_to = sip_get_in_brackets(refer_to); + + if (!strncasecmp(refer_to, "sip:", 4)) { + refer_to += 4; + } else if (!strncasecmp(refer_to, "sips:", 5)) { + refer_to += 5; + } else { + ast_log(LOG_WARNING, "Invalid Refer-To: '%s'\n", refer_to); + return SIP_PARSE_REFER_BAD_URI; + } + + refer_to = strsep(&refer_to, ";"); + + sip_pedantic_decode(refer_to); + ast_string_field_set(dialog, refer_to, refer_to); + + /* Check for arguments in the refer_to header */ + if ((replaces = strcasestr(refer_to, "replaces="))) { + char *call_id, *to_tag, *from_tag; + + /* This is an attended transfer */ + dialog->attended_transfer = TRUE; + + replaces += 9; + call_id = strsep(&replaces, ";"); + + ast_uri_decode(call_id, ast_uri_sip_user); + ast_string_field_set(dialog, replaces_call_id, call_id); + + /* Find the different tags before we destroy the string */ + to_tag = strcasestr(replaces, "to-tag="); + from_tag = strcasestr(replaces, "from-tag="); + + /* Grab the to header */ + if (to_tag) { + to_tag += 7; + to_tag = strsep(&to_tag, "&;"); + + ast_string_field_set(dialog, replaces_to_tag, to_tag); + } + + if (from_tag) { + from_tag += 9; + from_tag = strsep(&from_tag, "&;"); + + ast_string_field_set(dialog, replaces_from_tag, from_tag); + } + + if (!strcmp(dialog->replaces_call_id, dialog->call_id) && + (!sip_config.pedantic_checking || + (!strcmp(dialog->replaces_from_tag, dialog->remote_tag) && + !strcmp(dialog->replaces_to_tag, dialog->local_tag)))) { + ast_log(LOG_WARNING, "Got an attempt to replace own Call-ID: '%s'\n", dialog->call_id); + + return SIP_PARSE_REFER_BAD_EXTENSION; + } + + if (!sip_config.pedantic_checking) { + ast_debug(2, "Attended transfer Will use Replaces: Call-ID: '%s' (No check of From:/To: tag)\n", + dialog->replaces_call_id); + } else { + ast_debug(2, "Attended transfer: Will use Replaces: Call-ID: '%s' From: tag='%s' To: tag='%s'\n", + dialog->replaces_call_id, + S_OR(dialog->replaces_from_tag, ""), S_OR(dialog->replaces_to_tag, "")); + } + } + + if ((refer_to_domain = strchr(refer_to, '@'))) { /* Separate domain */ + *refer_to_domain++ = '\0'; + refer_to_domain = strsep(&refer_to_domain, ";"); + + sip_pedantic_decode(refer_to_domain); + /* Save the domain for the dial plan */ + ast_string_field_set(dialog, refer_to_domain, refer_to_domain); + } else { + ast_string_field_set(dialog, refer_to_domain, ""); + } + + /* Get referred by header if it exists */ + referred_by = ast_strdupa(sip_request_get_header(request, "Referred-By")); + + if (!ast_strlen_zero(referred_by)) { + referred_by = sip_get_in_brackets(referred_by); + + if (!strncasecmp(referred_by, "sip:", 4)) { + referred_by += 4; /* Skip sip: */ + } else if (!strncasecmp(referred_by, "sips:", 5)) { + referred_by += 5; /* Skip sips: */ + } else { + ast_log(LOG_WARNING, "Invalid Referred-By: '%s', skipping\n", referred_by); + referred_by = NULL; + } + } + + if (referred_by) { + referred_by = strsep(&referred_by, ";"); + + sip_pedantic_decode(referred_by); + ast_string_field_build(dialog, referred_by, "", referred_by); + } else { + ast_string_field_set(dialog, referred_by, ""); + } + + 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 *, channel_relock, 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, "SIPREFERRINGCONTEXT", S_OR(dialog->context, NULL)); + pbx_builtin_setvar_helper(bridge_channel, "__SIPREFERREDBYHDR", S_OR(referred_by, NULL)); + } + + channel_relock = sip_dialog_lock_with_channel(dialog); + + if (!channel_relock) { + ast_debug(3, "Unable to reacquire owner channel lock, channel is gone\n"); + return SIP_PARSE_REFER_BAD_EXTENSION; + } + + /* 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 = S_OR(dialog->context, sip_config.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, refer_to, 1, NULL)) { + ast_debug(1, "SIP transfer to extension %s@%s by %s\n", refer_to, transfer_context, S_OR(referred_by, "Unknown")); + + /* We are ready to transfer to the extension */ + return SIP_PARSE_REFER_SUCCESS; + } + + ast_debug(1, "Failed SIP Transfer to non-existing extension %s in context %s\n n", refer_to, transfer_context); + + /* Failure, we can't find this extension */ + return SIP_PARSE_REFER_BAD_EXTENSION; +} + +/* Check for the presence of OLI tag(s) in the From header and set on the channel */ +int sip_parse_oli(struct sip_dialog *dialog, struct sip_request *request) +{ + const char *from, *oli; + int ani2; + + from = sip_request_get_header(request, "From"); + + if (ast_strlen_zero(from)) { + return 0; + } + + if ((oli = strcasestr(from, ";isup-oli="))) { + oli += 10; + } else if ((oli = strcasestr(from, ";ss7-oli="))) { + oli += 9; + } else if ((oli = strcasestr(from, ";oli="))) { + oli += 5; + } + + if (ast_strlen_zero(oli)) { + /* OLI oli is missing, or present with nothing following the '=' sign */ + return -1; + } + + /* just in case OLI is quoted */ + if (*oli == '\"') { + oli++; + } + + if (sscanf(oli, "%d", &ani2)) { + ast_channel_caller(dialog->channel)->ani2 = ani2; + } + + return 0; +} + +/* Function for parsing Min-SE header */ +int sip_parse_min_se(struct sip_request *request, int *interval) +{ + const char *min_se = sip_request_get_header(request, "Min-SE"); + + if (ast_strlen_zero(min_se)) { + return -1; + } + + *interval = 0; + min_se = ast_skip_blanks(min_se); + + if (!sscanf(min_se, "%30d", interval)) { + ast_log(LOG_WARNING, "Parsing of Min-SE header failed '%s'\n", min_se); + return -1; + } + + ast_debug(2, "Received Min-SE: %d\n", *interval); + + return 0; +} + +/* Function for parsing Session-Expires header */ +int sip_parse_session_expires(struct sip_request *request, int *interval, int *refresher) +{ + char *session_expires, *expires; + + *refresher = SIP_STIMER_REFRESHER_AUTO; + *interval = 0; + + session_expires = ast_strdupa(sip_request_get_header(request, "Session-Expires")); + + if (ast_strlen_zero(session_expires)) { + return -1; + } + + if ((expires = strsep(&session_expires, ";"))) { + if (sscanf(expires, "%30d", interval) != 1) { + ast_log(LOG_WARNING, "Invalid expiry: %s\n", session_expires); + return -1; + } + + ast_debug(2, "Session-Expires: %d\n", *interval); + + if (ast_strlen_zero(session_expires)) { + return 0; + } else if (!strncasecmp(session_expires, "refresher=", 10)) { + session_expires += 10; + + if (!strncasecmp(session_expires, "uac", 3)) { + *refresher = SIP_STIMER_REFRESHER_UAC; + ast_debug(2, "Refresher: UAC\n"); + } else if (!strncasecmp(session_expires, "uas", 3)) { + *refresher = SIP_STIMER_REFRESHER_UAS; + ast_debug(2, "Refresher: UAS\n"); + } else { + ast_log(LOG_WARNING, "Invalid session refresher: %s\n", session_expires); + return -1; + } + } + } + + return 0; +} + +void sip_parse_rtp_stats(struct sip_dialog *dialog, struct sip_request *request) +{ + char *rtp_rx_stat, *rtp_tx_stat; + struct sip_peer *peer; + + rtp_rx_stat = ast_strdupa(sip_request_get_header(request, "RTP-RxStat")); + rtp_tx_stat = ast_strdupa(sip_request_get_header(request, "RTP-TxStat")); + + if (ast_test_flag(&dialog->flags[2], SIP_SEND_QRT_URL)) { + ast_verb(3, "Peer RTP stats for '%s'\n", dialog->peer_name); + ast_verb(3, "RxStat: %s\n", rtp_rx_stat); + ast_verb(3, "TxStat: %s\n", rtp_tx_stat); + + if ((peer = sip_peer_find(dialog->peer_name, TRUE, FALSE))) { + sip_peer_send_qrt_url(peer); + ao2_t_cleanup(peer, "drop peer"); + } + } +} + +/* NAT fix - decide which IP address to use for Asterisk server? + * Using the localaddr 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_get_our_address(unsigned int transport, const struct ast_sockaddr *address, struct ast_sockaddr *our_address) +{ + /* 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. */ + int want_remap; + + /* starting guess for the internal address */ + ast_sockaddr_copy(our_address, &sip_our_address); + /* now ask the system what would it use to talk to 'addr' */ + ast_ouraddrfor(address, our_address); + + if (ast_sockaddr_is_ipv6(address) && !ast_sockaddr_is_ipv4_mapped(address)) { + if (sip_config.local_address && + !ast_sockaddr_isnull(&sip_config.external_address) && !ast_sockaddr_is_any(&sip_config.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.local_address && !ast_sockaddr_isnull(&sip_config.external_address) && + ast_apply_ha(sip_config.local_address, address) == AST_SENSE_ALLOW; + } + + if (want_remap && (!sip_config.match_external_address_locally || !ast_apply_ha(sip_config.local_address, our_address))) { + /* if we used externhost, see if it is time to refresh the info */ + if (sip_config.external_expiry && time(NULL) >= sip_config.external_expiry) { + if (ast_sockaddr_resolve_first_af(&sip_config.external_address, sip_config.external_host, 0, AST_AF_INET)) { + ast_log(LOG_NOTICE, "Warning: Re-lookup of '%s' failed!\n", sip_config.external_host); + } + + sip_config.external_expiry = time(NULL) + sip_config.external_refresh; + } + + if (!ast_sockaddr_isnull(&sip_config.external_address)) { + ast_sockaddr_copy(our_address, &sip_config.external_address); + + switch (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(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(our_address, sip_config.external_tls_port); + break; + case AST_TRANSPORT_UDP: + if (!ast_sockaddr_port(&sip_config.external_address)) { + ast_sockaddr_set_port(our_address, ast_sockaddr_port(&sip_config.bind_address)); + } + + break; + default: + break; + } + } + + ast_debug(1, "Target address %s is not local, substituting externaddr\n", ast_sockaddr_stringify(address)); + } else { + /* no remapping, but we bind to a specific address, so use it. */ + switch (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(our_address, &sip_tcp_session.local_address); + } else { + ast_sockaddr_set_port(our_address, ast_sockaddr_port(&sip_tcp_session.local_address)); + } + + break; + } /* fall through on purpose */ + 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(our_address, &sip_tls_session.local_address); + } else { + ast_sockaddr_set_port(our_address, ast_sockaddr_port(&sip_tls_session.local_address)); + } + + break; + } /* fall through on purpose */ + case AST_TRANSPORT_UDP: + /* fall through on purpose */ + default: + if (!ast_sockaddr_is_any(&sip_config.bind_address)) { + ast_sockaddr_copy(our_address, &sip_config.bind_address); + } + + if (!ast_sockaddr_port(our_address)) { + ast_sockaddr_set_port(our_address, ast_sockaddr_port(&sip_config.bind_address)); + } + } + } + + ast_debug(3, "Setting AST_TRANSPORT_%s with address %s\n", ast_transport2str(transport), ast_sockaddr_stringify(our_address)); +} + +/* 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) +{ + const char *orig_codecs; + char *codecs, *codec; + int first_codec; + RAII_VAR(struct ast_format_cap *, orig_joint_format_cap, NULL, ao2_cleanup); + + if (dialog->outgoing_call) { + orig_codecs = pbx_builtin_getvar_helper(dialog->channel, "SIP_CODEC_OUTBOUND"); + } else { + orig_codecs = pbx_builtin_getvar_helper(dialog->channel, "SIP_CODEC_INBOUND"); + + if (ast_strlen_zero(orig_codecs)) { + orig_codecs = pbx_builtin_getvar_helper(dialog->channel, "SIP_CODEC"); + } + } + + if (ast_strlen_zero(orig_codecs)) { + return; + } + + codecs = ast_strdupa(orig_codecs); + + if (!(orig_joint_format_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + return; + } + + ast_format_cap_append_from_cap(orig_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(orig_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, orig_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; + + if (!device_state) { + return NULL; + } + + channel = NULL; + creation = 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) || ast_tvcmp(ast_channel_creationtime(device_state_info->causing_channel), creation) < 0) { + channel = device_state_info->causing_channel; + creation = 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; +} + +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 -durN asterisk-22.3.0.orig/configs/samples/res_parking.conf.sample asterisk-22.3.0/configs/samples/res_parking.conf.sample --- asterisk-22.3.0.orig/configs/samples/res_parking.conf.sample 2025-04-17 11:10:32.018056908 +1200 +++ asterisk-22.3.0/configs/samples/res_parking.conf.sample 2025-04-17 11:11:22.825702903 +1200 @@ -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 -durN asterisk-22.3.0.orig/configs/samples/sip.conf.sample asterisk-22.3.0/configs/samples/sip.conf.sample --- asterisk-22.3.0.orig/configs/samples/sip.conf.sample 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/configs/samples/sip.conf.sample 2025-04-17 11:11:22.827702849 +1200 @@ -0,0 +1,1209 @@ +; +; SIP Configuration example for Asterisk +; +; Note: Please read the security documentation for Asterisk in order to +; understand the risks of installing Asterisk with the sample +; configuration. If your Asterisk is installed on a public +; IP address connected to the Internet, you will want to learn +; about the various security settings BEFORE you start +; Asterisk. +; +; Especially note the following settings: +; - permit/deny/acl - IP address filters +; - contactpermit/contactdeny/contactacl - IP address filters for registrations +; - context - Which set of services you offer various users +; +; SIP Dial Strings +; ---------------------------------------------------------- +; In the dialplan (extensions.conf) you can use several +; syntaxes for dialing SIP devices. +; SIP/devicename +; SIP/username@domain (SIP uri) +; SIP/username[:password[:md5secret[:authname[:transport]]]]@host[:port] +; SIP/devicename/extension +; SIP/devicename/extension/IPorHost +; SIP/username@domain//IPorHost +; +; Devicename +; devicename is defined as a peer in a section below. +; +; username@domain +; Call any SIP user on the Internet +; (Don't forget to enable DNS SRV records if you want to use this) +; +; devicename/extension +; If you define a SIP proxy as a peer below, you may call +; SIP/proxyhostname/user or SIP/user@proxyhostname +; where the proxyhostname is defined in a section below +; This syntax also works with ATA's with FXO ports +; +; SIP/username[:password[:md5secret[:authname]]]@host[:port] +; This form allows you to specify password or md5secret and authname +; without altering any authentication data in config. +; Examples: +; +; SIP/*98@mysipproxy +; SIP/sales:topsecret::account02@domain.com:5062 +; SIP/12345678::bc53f0ba8ceb1ded2b70e05c3f91de4f:myname@192.168.0.1 +; +; IPorHost +; The next server for this call regardless of domain/peer +; +; All of these dial strings specify the SIP request URI. +; In addition, you can specify a specific To: header by adding an +; exclamation mark after the dial string, like +; +; SIP/sales@mysipproxy!sales@edvina.net +; +; (Specifying only @todomain without touser will create an invalid SIP +; request.) +; +; Similarly, you can specify the From header as well, after a second +; exclamation mark: +; +; SIP/customer@mysipproxy!!customersupport@wearespindle.com +; +; A new feature for 1.8 allows one to specify a host or IP address to use +; when routing the call. This is typically used in tandem with func_srv if +; multiple methods of reaching the same domain exist. The host or IP address +; is specified after the third slash in the dialstring. Examples: +; +; SIP/devicename/extension/IPorHost +; SIP/username@domain//IPorHost +; +; CLI Commands +; ------------------------------------------------------------- +; Useful CLI commands to check peers/users: +; sip show peers Show all SIP peers (including friends) +; sip show registry Show status of hosts we register with +; +; sip set debug on Show all SIP messages +; +; sip reload Reload configuration file +; sip show settings Show the current channel configuration +; +; Naming devices +; ---------------------------------------------------------- +; +; When naming devices, make sure you understand how Asterisk matches calls +; that come in. +; 1. Asterisk checks the SIP From: address username and matches against +; names of devices with type=user +; The name is the text between square brackets [name] +; 2. Asterisk checks the From: addres and matches the list of devices +; with a type=peer +; 3. Asterisk checks the IP address (and port number) that the INVITE +; was sent from and matches against any devices with type=peer +; +; Don't mix extensions with the names of the devices. Devices need a unique +; name. The device name is *not* used as phone numbers. Phone numbers are +; anything you declare as an extension in the dialplan (extensions.conf). +; +; When setting up trunks, make sure there's no risk that any From: username +; (caller ID) will match any of your device names, because then Asterisk +; might match the wrong device. +; +; Note: The parameter "username" is not the username and in most cases is +; not needed at all. Check below. In later releases, it's renamed +; to "defaultuser" which is a better name, since it is used in +; combination with the "defaultip" setting. + +[general] +context=public ; Default context for incoming calls. Defaults to 'default' +;match_auth_username=yes ; if available, match user entry using the + ; 'username' field from the authentication line + ; instead of the From: field. +allowoverlap=no ; Disable overlap dialing support. (Default is yes) +;allowoverlap=yes ; Enable RFC3578 overlap dialing support. + ; Can use the Incomplete application to collect the + ; needed digits from an ambiguous dialplan match. +;allowoverlap=dtmf ; Enable overlap dialing support using DTMF delivery + ; methods (inband, RFC2833) in the early + ; media phase. Uses the Incomplete application to + ; collect the needed digits. +;allowtransfer=no ; Disable all transfers (unless enabled in peers or users) + ; Default is enabled. The Dial() options 't' and 'T' are not + ; related as to whether SIP transfers are allowed or not. +;realm=mydomain.tld ; Realm for digest authentication + ; defaults to "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 + ; Set this to your host name or domain name +;domainsasrealm=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'/'To' header + ; and should match one of domain names. + ; Otherwise default 'realm=...' will be used. + +; With the current situation, you can do one of four things: +; a) Listen on a specific IPv4 address. Example: bindaddr=192.0.2.1 +; b) Listen on a specific IPv6 address. Example: bindaddr=2001:db8::1 +; c) Listen on the IPv4 wildcard. Example: bindaddr=0.0.0.0 +; d) Listen on the IPv4 and IPv6 wildcards. Example: bindaddr=:: +; (You can choose independently for UDP, TCP, and TLS, by specifying different values for +; "udpbindaddr", "tcpbindaddr", and "tlsbindaddr".) +; (Note that using bindaddr=:: will show only a single IPv6 socket in netstat. +; IPv4 is supported at the same time using IPv4-mapped IPv6 addresses.) +; +; You may optionally add a port number. (The default is port 5060 for UDP and TCP, 5061 +; for TLS). +; IPv4 example: bindaddr=0.0.0.0:5062 +; IPv6 example: bindaddr=[::]:5062 +; +; The address family of the bound UDP address is used to determine how Asterisk performs +; DNS lookups. In cases a) and c) above, only A records are considered. In case b), only +; AAAA records are considered. In case d), both A and AAAA records are considered. Note, +; however, that Asterisk ignores all records except the first one. In case d), when both A +; and AAAA records are available, either an A or AAAA record will be first, and which one +; depends on the operating system. On systems using glibc, AAAA records are given +; priority. + +udpbindaddr=0.0.0.0 ; IP address to bind UDP listen socket to (0.0.0.0 binds to all) + ; Optionally add a port number, 192.168.1.1:5062 (default is port 5060) + +;rtpbindaddr=172.16.42.1 ; IP address to bind RTP listen sock to (default is disabled). When + ; disabled the udpbindaddr is used. + +; When a dialog is started with another SIP endpoint, the other endpoint +; should include an Allow header telling us what SIP methods the endpoint +; implements. However, some endpoints either do not include an Allow header +; or lie about what methods they implement. In the former case, Asterisk +; makes the assumption that the endpoint supports all known SIP methods. +; If you know that your SIP endpoint does not provide support for a specific +; method, then you may provide a comma-separated list of methods that your +; endpoint does not implement in the disallowed_methods option. Note that +; if your endpoint is truthful with its Allow header, then there is no need +; to set this option. This option may be set in the general section or may +; be set per endpoint. If this option is set both in the general section and +; in a peer section, then the peer setting completely overrides the general +; setting (i.e. the result is *not* the union of the two options). +; +; Note also that while Asterisk currently will parse an Allow header to learn +; what methods an endpoint supports, the only actual use for this currently +; is for determining if Asterisk may send connected line UPDATE requests and +; MESSAGE requests. Its use may be expanded in the future. +; +; Note that the TCP and TLS support for chan_sip is currently considered +; experimental. Since it is new, all of the related configuration options are +; subject to change in any release. If they are changed, the changes will +; be reflected in this sample configuration file, as well as in the UPGRADE.txt file. + +tcpenable=no ; Enable server for incoming TCP connections (default is no) +tcpbindaddr=0.0.0.0 ; IP address for TCP server to bind to (0.0.0.0 binds to all interfaces) + ; Optionally add a port number, 192.168.1.1:5062 (default is port 5060) +;tlsenable=no ; Enable server for incoming TLS (secure) connections (default is no) +;tlsbindaddr=0.0.0.0 ; IP address for TLS server to bind to (0.0.0.0) binds to all interfaces) + ; Optionally add a port number, 192.168.1.1:5063 (default is port 5061) + ; Remember that the IP address must match the common name (hostname) in the + ; certificate, so you don't want to bind a TLS socket to multiple IP addresses. + ; For details how to construct a certificate for SIP see + ; http://tools.ietf.org/html/draft-ietf-sip-domain-certs +;tcpauthtimeout=30 ; tcpauthtimeout specifies the maximum number + ; of seconds a client has to authenticate. If + ; the client does not authenticate beofre this + ; timeout expires, the client will be + ; disconnected. (default: 30 seconds) +;tcpauthlimit=100 ; tcpauthlimit specifies the maximum number of + ; unauthenticated sessions that will be allowed + ; to connect at any given time. (default: 100) +transport=udp ; Set the default transports. The order determines the primary default transport. + ; If tcpenable=no and the transport set is tcp, we will fallback to UDP. + +srvlookup=yes ; Enable DNS SRV lookups on outbound calls + ; Note: Asterisk only uses the first host + ; in SRV records + ; Disabling DNS SRV lookups disables the + ; ability to place SIP calls based on domain + ; names to some other SIP users on the Internet + ; Specifying a port in a SIP peer definition or + ; when dialing outbound calls will supress SRV + ; lookups for that peer or call. +;pedantic=yes ; Enable checking of tags in headers, + ; international character conversions in URIs + ; and multiline formatted headers for strict + ; SIP compatibility (defaults to "yes") + +; See https://wiki.asterisk.org/wiki/display/AST/IP+Quality+of+Service for a description of these parameters. +;tos_sip=cs3 ; Sets TOS for SIP packets. +;tos_audio=ef ; Sets TOS for RTP audio packets. +;tos_video=af41 ; Sets TOS for RTP video packets. +;tos_text=af41 ; Sets TOS for RTP text packets. +;cos_sip=3 ; Sets 802.1p priority for SIP packets. +;cos_audio=5 ; Sets 802.1p priority for RTP audio packets. +;cos_video=4 ; Sets 802.1p priority for RTP video packets. +;cos_text=3 ; Sets 802.1p priority for RTP text packets. +;maxexpiry=3600 ; Maximum allowed time of incoming registrations (seconds) +;minexpiry=60 ; Minimum length of registrations (default 60) +;defaultexpiry=120 ; Default length of incoming/outgoing registration +;submaxexpiry=3600 ; Maximum allowed time of incoming subscriptions (seconds), default: maxexpiry +;subminexpiry=60 ; Minimum length of subscriptions, default: minexpiry +;mwiexpiry=3600 ; Expiry time for outgoing MWI subscriptions +;maxforwards=70 ; Setting for the SIP Max-Forwards: header (loop prevention) + ; Default value is 70 +;qualifyfreq=60 ; Qualification: How often to check for the host to be up in seconds + ; and reported in milliseconds with sip show settings. + ; Set to low value if you use low timeout for NAT of UDP sessions + ; Default: 60 +;qualifygap=100 ; Number of milliseconds between each group of peers being qualified + ; Default: 100 +;qualifypeers=1 ; Number of peers in a group to be qualified at the same time + ; Default: 1 +;keepalive=60 ; Interval at which keepalive packets should be sent to a peer + ; Valid options are yes (60 seconds), no, or the number of seconds. + ; Default: 0 +;mwi_from=asterisk ; When sending MWI NOTIFY requests, use this setting in + ; the From: header as the "name" portion. Also fill the + ; "user" portion of the URI in the From: header with this + ; value if no fromuser is set + ; Default: empty +;vmexten=voicemail ; dialplan extension to reach mailbox sets the + ; Message-Account in the MWI notify message + ; defaults to "asterisk" + +; Codec negotiation +; +; 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. +; +;preferred_codec_only=yes ; Respond to a SIP invite with the single most preferred codec + ; rather than advertising all joint codec capabilities. This + ; limits the other side's codec choice to exactly what we prefer. +;disallow=all ; First disallow all codecs +;allow=ulaw ; Allow codecs in order of preference +;allow=ilbc ; see https://wiki.asterisk.org/wiki/display/AST/RTP+Packetization + ; for framing options +;autoframing=yes ; Set packetization based on the remote endpoint's (ptime) + ; preferences. Defaults to no. + +; This option specifies a preference for which music on hold class this channel +; should listen to when put on hold if the music class has not been set on the +; channel with Set(CHANNEL(musicclass)=whatever) in the dialplan, and the peer +; channel putting this one on hold did not suggest a music class. +; +; This option may be specified globally, or on a per-user or per-peer basis. + +;mohinterpret=default + +; This option specifies which music on hold class to suggest to the peer channel +; when this channel places the peer on hold. It may be specified globally or on +; a per-user or per-peer basis. + +;mohsuggest=default +;parkinglot=plaza ; Sets the default parking lot for call parking + ; This may also be set for individual users/peers + ; Parkinglots are configured in features.conf +;language=en ; Default language setting for all users/peers + ; This may also be set for individual users/peers +;tonezone=se ; Default tonezone for all users/peers + ; This may also be set for individual users/peers +;relaxdtmf=yes ; Relax dtmf handling +;trustrpid=no ; If Remote-Party-ID should be trusted +;sendrpid=yes ; If Remote-Party-ID should be sent (defaults to no) +;sendrpid=rpid ; Use the "Remote-Party-ID" header + ; to send the identity of the remote party + ; This is identical to sendrpid=yes +;sendrpid=pai ; Use the "P-Asserted-Identity" header + ; to send the identity of the remote party +;rpid_update=no ; In certain cases, the only method by which a connected line + ; change may be immediately transmitted is with a SIP UPDATE request. + ; If communicating with another Asterisk server, and you wish to be able + ; transmit such UPDATE messages to it, then you must enable this option. + ; Otherwise, we will have to wait until we can send a reinvite to + ; transmit the information. +;trust_id_outbound=no ; Controls whether or not we trust this peer with private identity + ; information (when the remote party has callingpres=prohib or equivalent). + ; no - RPID/PAI headers will not be included for private peer information + ; yes - RPID/PAI headers will include the private peer information. Privacy + ; requirements will be indicated in a Privacy header for sendrpid=pai + ; This option is set to 'legacy' by default +;prematuremedia=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 - thus users get no ring signal. + ; Setting this to "yes" will stop any media before we have + ; call progress (meaning the SIP channel will not send 183 Session + ; Progress for early media). Default is "yes". Also make sure that + ; the SIP peer is configured with progressinband=never. + ; In order for "noanswer" applications to work, you need to run + ; the progress() application in the priority before the app. +;progressinband=no ; If we should generate in-band ringing. Always + ; use 'never' to never use in-band signalling, even in cases + ; where some buggy devices might not render it + ; Valid values: yes, no, never Default: no +;useragent=Asterisk PBX ; Allows you to change the user agent string + ; The default user agent string also contains the Asterisk + ; version. If you don't want to expose this, change the + ; useragent string. +;promiscredir=no ; If yes, allows 302 or REDIR to non-local SIP address + ; Note that promiscredir when redirects are made to the + ; local system will cause loops since Asterisk is incapable + ; of performing a "hairpin" call. +;usereqphone=no ; If yes, ";user=phone" is added to uri that contains + ; a valid phone number +;dtmfmode=rfc2833 ; Set default dtmfmode for sending DTMF. Default: rfc2833 + ; Other options: + ; info : SIP INFO messages (application/dtmf-relay) + ; shortinfo : SIP INFO messages (application/dtmf) + ; inband : Inband audio (requires 64 kbit codec -alaw, ulaw) + ; auto : Use rfc2833 if offered, inband otherwise +;videosupport=yes ; Turn on support for SIP video. You need to turn this + ; on in this section to get any video support at all. + ; You can turn it off on a per peer basis if the general + ; video support is enabled, but you can't enable it for + ; one peer only without enabling in the general section. + ; If you set videosupport to "always", then RTP ports will + ; always be set up for video, even on clients that don't + ; support it. This assists callfile-derived calls and + ; certain transferred calls to use always use video when + ; available. [yes|NO|always] +;textsupport=no ; Support for ITU-T T.140 realtime text. + ; The default value is "no". +;maxcallbitrate=384 ; Maximum bitrate for video calls (default 384 kb/s) + ; Videosupport and maxcallbitrate is settable + ; for peers and users as well +;authfailureevents=no ; generate manager "peerstatus" events when peer can't + ; authenticate with Asterisk. Peerstatus will be "rejected". +;alwaysauthreject=yes ; 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/hash + ; instead of letting the requester know whether there was + ; a matching user or peer for their request. This reduces + ; the ability of an attacker to scan for valid SIP usernames. + ; This option is set to "yes" by default. +;auth_options_requests=yes ; Enabling this option will authenticate OPTIONS requests just like + ; INVITE requests are. By default this option is disabled. +;accept_outofcall_message=no ; Disable this option to reject all MESSAGE requests outside of a + ; call. By default, this option is enabled. When enabled, MESSAGE + ; requests are passed in to the dialplan. +;outofcall_message_context=messages ; Context all out of dialog msgs are sent to. When this + ; option is not set, the context used during peer matching + ; is used. This option can be defined at both the peer and + ; global level. +;auth_message_requests=yes ; Enabling this option will authenticate MESSAGE requests. + ; By default this option is enabled. However, it can be disabled + ; should an application desire to not load the Asterisk server with + ; doing authentication and implement end to end security in the + ; message body. +;g726nonstandard=yes ; If the peer negotiates G726-32 audio, use AAL2 packing + ; order instead of RFC3551 packing order (this is required + ; for Sipura and Grandstream ATAs, among others). This is + ; contrary to the RFC3551 specification, the peer _should_ + ; be negotiating AAL2-G726-32 instead :-( +;outboundproxy=proxy.provider.domain ; send outbound signaling to this proxy, not directly to the devices +;outboundproxy=proxy.provider.domain:8080 ; send outbound signaling to this proxy, not directly to the devices +;outboundproxy=proxy.provider.domain,force ; Send ALL outbound signalling to proxy, ignoring route: headers +;outboundproxy=tls://proxy.provider.domain ; same as '=proxy.provider.domain' except we try to connect with tls +;outboundproxy=192.0.2.1 ; IPv4 address literal (default port is 5060) +;outboundproxy=2001:db8::1 ; IPv6 address literal (default port is 5060) +;outboundproxy=192.168.0.2.1:5062 ; IPv4 address literal with explicit port +;outboundproxy=[2001:db8::1]:5062 ; IPv6 address literal with explicit port +; ; (could also be tcp,udp) - defining transports on the proxy line only +; ; applies for the global proxy, otherwise use the transport= option +;supportpath=yes ; This activates parsing and handling of Path header as defined in RFC 3327. This enables + ; Asterisk to route outgoing out-of-dialog requests via a set of proxies by using a pre-loaded + ; route-set defined by the Path headers in the REGISTER request. + ; NOTE: There are multiple things to consider with this setting: + ; * As this influences routing of SIP requests make sure to not trust Path headers provided + ; by the user's SIP client (the proxy in front of Asterisk should remove existing user + ; provided Path headers). + ; * When a peer has both a path and outboundproxy set, the path will be added to Route: header + ; but routing to next hop is done using the outboundproxy. + ; * If set globally, not only will all peers use the Path header, but outbound REGISTER + ; requests from Asterisk will add path to the Supported header. +;rtsavepath=yes ; If using dynamic realtime, store the path headers +;matchexternaddrlocally=yes ; Only substitute the externaddr or externhost setting if it matches + ; your localnet setting. Unless you have some sort of strange network + ; setup you will not need to enable this. +;dynamic_exclude_static=yes ; Disallow all dynamic hosts from registering + ; as any IP address used for staticly defined + ; hosts. This helps avoid the configuration + ; error of allowing your users to register at + ; the same address as a SIP provider. + +;contactdeny=0.0.0.0/0.0.0.0 ; Use contactpermit and contactdeny to +;contactpermit=172.16.0.0/255.255.0.0 ; restrict at what IPs your users may + ; register their phones. +;contactacl=named_acl_example ; Use named ACLs defined in acl.conf +;rtp_engine=asterisk ; RTP engine to use when communicating with the device + +;send_diversion=no ; Default "yes" ; Asterisk normally sends Diversion headers with certain SIP + ; invites to relay data about forwarded calls. If this option + ; is disabled, Asterisk won't send Diversion headers unless + ; they are added manually. + +; The shrinkcallerid function removes '(', ' ', ')', non-trailing '.', and '-' not +; in square brackets. For example, the caller id value 555.5555 becomes 5555555 +; when this option is enabled. Disabling this option results in no modification +; of the caller id value, which is necessary when the caller id represents something +; that must be preserved. This option can only be used in the [general] section. +; By default this option is on. +; +;shrinkcallerid=yes ; on by default +;use_q850_reason=no ; Default "no" + ; Set to yes add Reason header and use Reason header if it is available. + +; When the Transfer() application sends a REFER SIP message, extra headers specified in +; the dialplan by way of SIPAddHeader are sent out with that message. 1.8 and earlier did not +; add the extra headers. To revert to 1.8- behavior, call SIPRemoveHeader with no arguments +; before calling Transfer() to remove all additional headers from the channel. The setting +; below is for transitional compatibility only. + +; TLS Settings +; ---------------------------------------------------------- +;tlscertfile= ; Certificate chain (*.pem format only) 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, for example "asterisk_rsa.pem", the 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, at least OpenSSL 1.0.2 is required. + ; Default is to look for "asterisk.pem" in current directory + +;tlsprivatekey= ; Private key file (*.pem format only) for TLS connections. + ; If no tlsprivatekey is specified, tlscertfile is searched for + ; for both public and private key. + +;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. + +;tlscapath= +; A directory full of CA certificates. The files must be named with +; the CA subject name hash value. +; (see man SSL_CTX_load_verify_locations for more info) + +;tlsdontverifyserver=[yes|no] +; If set to yes, don't verify the servers 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. + +;tlscipher= +; A string specifying which SSL ciphers to use or not use +; A list of valid SSL cipher strings can be found at: +; http://www.openssl.org/docs/apps/ciphers.html#CIPHER_STRINGS +; +;tlsclientmethod=tlsv1 ; values include tlsv1, sslv3, sslv2. + ; Specify protocol for outbound client connections. + ; If left unspecified, the default is the general- + ; purpose version-flexible SSL/TLS method (sslv23). + ; With that, the actual protocol version used will + ; be negotiated to the highest version mutually + ; supported by Asterisk and the remote server, i.e. + ; TLSv1.2. The supported protocols are listed at + ; http://www.openssl.org/docs/ssl/SSL_CTX_new.html + ; SSLv2 and SSLv3 are disabled within Asterisk. + ; Your distribution might have changed that list + ; further. + +; SIP Timers +; ---------------------------------------------------------- +; These timers are used primarily in INVITE transactions. +; The default for Timer T1 is 500 ms or the measured run-trip time between +; Asterisk and the device if you have qualify=yes for the device. +; +;t1min=100 ; Minimum roundtrip time for messages to monitored hosts + ; Defaults to 100 ms +;timert1=500 ; Default T1 timer + ; Defaults to 500 ms or the measured round-trip + ; time to a peer (qualify=yes). +;timerb=32000 ; Call setup timer. If a provisional response is not received + ; in this amount of time, the call will autocongest + ; Defaults to 64*timert1 + +; RTP Timers +; ---------------------------------------------------------- +; These timers are currently used for both audio and video streams. The RTP timeouts +; are only applied to the audio channel. +; The settings are settable in the global section as well as per device +; +;rtptimeout=60 ; Terminate call if 60 seconds of no RTP or RTCP activity + ; on the audio channel + ; when we're not on hold. This is to be able to hangup + ; a call in the case of a phone disappearing from the net, + ; like a powerloss or grandma tripping over a cable. +;rtpholdtimeout=300 ; Terminate call if 300 seconds of no RTP or RTCP activity + ; on the audio channel + ; when we're on hold (must be > rtptimeout) +;rtpkeepalive= ; Send keepalives in the RTP stream to keep NAT open + ; (default is off - zero) + +; SIP Session-Timers (RFC 4028) +; ---------------------------------------------------------- +; SIP Session-Timers provide an end-to-end keep-alive mechanism for active SIP sessions. +; This mechanism can detect and reclaim SIP channels that do not terminate through normal +; signaling procedures. Session-Timers can be configured globally or at a user/peer level. +; The operation of Session-Timers is driven by the following configuration parameters: +; +; * session-timers - Session-Timers feature operates in the following three modes: +; originate : Request and run session-timers always +; accept : Run session-timers only when requested by other UA +; refuse : Do not run session timers in any case +; The default mode of operation is 'accept'. +; * session-expires - Maximum session refresh interval in seconds. Defaults to 1800 secs. +; * session-minse - Minimum session refresh interval in seconds. Defualts to 90 secs. +; * session-refresher - The session refresher (uac|uas). Defaults to 'uas'. +; uac - Default to the caller initially refreshing when possible +; uas - Default to the callee initially refreshing when possible + +; Note that, due to recommendations in RFC 4028, Asterisk will always honor the other +; endpoint's preference for who will handle refreshes. Asterisk will never override the +; preferences of the other endpoint. Doing so could result in Asterisk and the endpoint +; fighting over who sends the refreshes. This holds true for the initiation of session +; timers and subsequent re-INVITE requests whether Asterisk is the caller or callee, or +; whether Asterisk is currently the refresher or not. + +;session-timers=originate +;session-expires=600 +;session-minse=90 +;session-refresher=uac + +; Debugging +; ---------------------------------------------------------- +;sipdebug=yes ; Turn on SIP debugging by default, from + ; the moment the channel loads this configuration. + ; NOTE: You cannot use the CLI to turn it off. You'll + ; need to edit this and reload the config. +;recordhistory=yes ; Record SIP history by default + ; (see sip history / sip no history) +;dumphistory=yes ; Dump SIP history at end of SIP dialogue + ; SIP history is output to the DEBUG logging channel + + +; Subscriptions (BLF) +; ---------------------------------------------------------- +; You can subscribe to the status of extensions with a "hint" priority +; (See extensions.conf.sample for examples) +; chan_sip support two major formats for notifications: dialog-info and SIMPLE +; +; You will get more detailed reports (busy etc) if you have a call counter enabled +; for a device. +; +; If you set the busylevel, we will indicate busy when we have a number of calls that +; matches the busylevel treshold. +; +; For queues, you will need this level of detail in status reporting, regardless +; if you use SIP subscriptions. Queues and manager use the same internal interface +; for reading status information. +; +; Note: Subscriptions does not work if you have a realtime dialplan and use the +; realtime switch. +; +;allowsubscribe=no ; Disable support for subscriptions. (Default is yes) +;subscribecontext=default ; Set a specific context for SUBSCRIBE requests + ; Useful to limit subscriptions to local extensions + ; Settable per peer/user also +;notifyringing=no ; Control when subscriptions get notified of ringing state. + ; Specify 'no' to not send any ringing notifications. + ; Specify 'yes' to always send ringing notifications (default). + ; Specify 'notinuse' to only send ringing notifications for + ; extensions that are not currently in use. This is useful as a + ; visual indication of who is available to pick up an incoming call +;notifyhold=yes ; Notify subscriptions on HOLD state (default: no) + ; Turning on notifyringing and notifyhold will add a lot + ; more database transactions if you are using realtime. +;notifycid=yes ; Control whether caller ID information is sent along with + ; dialog-info+xml notifications (supported by snom phones). + ; Note that this feature will only work properly when the + ; incoming call is using the same extension and context that + ; is being used as the hint for the called extension. This means + ; that it won't work when using subscribecontext for your sip + ; user or peer (if subscribecontext is different than context). + ; This is also limited to a single caller, meaning that if an + ; extension is ringing because multiple calls are incoming, + ; only one will be used as the source of caller ID. Specify + ; 'ignore-context' to ignore the called context when looking + ; for the caller's channel. The default value is 'no.' Setting + ; notifycid to 'ignore-context' also causes call-pickups attempted + ; via SNOM's NOTIFY mechanism to set the context for the call pickup + ; to PICKUPMARK. +;callcounter=yes ; Enable call counters on devices. This can be set per + ; device too. + +; T.38 Fax Support +; ---------------------------------------------------------- +; This setting is available in the [general] section as well as in device configurations. +; Setting this to yes enables T.38 FAX (UDPTL) on SIP calls; it defaults to off. + +;t38pt_udptl=yes ; Enables T.38 with FEC error correction. +;t38pt_udptl=yes,fec ; Enables T.38 with FEC error correction. +;t38pt_udptl=yes,redundancy ; Enables T.38 with redundancy error correction. +;t38pt_udptl=yes,none ; Enables T.38 with no error correction. + +; In some cases, T.38 endpoints will provide a T38FaxMaxDatagram value (during T.38 setup) that +; is based on an incorrect interpretation of the T.38 recommendation, and results in failures +; because Asterisk does not believe it can send T.38 packets of a reasonable size to that +; endpoint (Cisco media gateways are one example of this situation). In these cases, during a +; T.38 call you will see warning messages on the console/in the logs from the Asterisk UDPTL +; stack complaining about lack of buffer space to send T.38 FAX packets. If this occurs, you +; can set an override (globally, or on a per-device basis) to make Asterisk ignore the +; T38FaxMaxDatagram value specified by the other endpoint, and use a configured value instead. +; This can be done by appending 'maxdatagram=' to the t38pt_udptl configuration option, +; like this: + +;t38pt_udptl=yes,fec,maxdatagram=400 ; Enables T.38 with FEC error correction and overrides +; ; the other endpoint's provided value to assume we can +; ; send 400 byte T.38 FAX packets to it. + +; FAX detection will cause the SIP channel to jump to the 'fax' extension (if it exists) +; based one or more events being detected. The events that can be detected are an incoming +; CNG tone or an incoming T.38 re-INVITE request. + +;faxdetect=yes ; Default 'no', 'yes' enables both CNG and T.38 detection +;faxdetect=cng ; Enables only CNG detection +;faxdetect=t38 ; Enables only T.38 detection + +; Outbound SIP Registrations +; ---------------------------------------------------------- +; Asterisk can register as a SIP user agent to a SIP proxy (provider) +; Format for the register statement is: +; register => [peer?][transport://]user[@domain][:secret[:authuser]]@host[:port][/extension][~expiry] +; +; Domain is either: +; - domain in DNS +; - host name in DNS +; - the name of a peer defined below or in realtime +; The domain is where you register your username, so your SIP uri you are registering to +; is username@domain +; +; If no extension is given, the 's' extension is used. The extension needs to +; be defined in extensions.conf to be able to accept calls from this SIP proxy +; (provider). +; +; A similar effect can be achieved by adding a "callbackextension" option in a peer section. +; this is equivalent to having the following line in the general section: +; +; register => fromuser:secret:username@host/callbackextension +; +; and more readable because you don't have to write the parameters in two places +; (note that the "port" is ignored - this is a bug that should be fixed). +; +; Note that a register= line doesn't mean that we will match the incoming call in any +; other way than described above. If you want to control where the call enters your +; dialplan, which context, you want to define a peer with the hostname of the provider's +; server. If the provider has multiple servers to place calls to your system, you need +; a peer for each server. +; +; Beginning with Asterisk version 1.6.2, the "user" portion of the register line may +; contain a port number. Since the logical separator between a host and port number is a +; ':' character, and this character is already used to separate between the optional "secret" +; and "authuser" portions of the line, there is a bit of a hoop to jump through if you wish +; to use a port here. That is, you must explicitly provide a "secret" and "authuser" even if +; they are blank. See the third example below for an illustration. +; +; Examples: + +;register => 1234:password@mysipprovider.com + +; This will pass incoming calls to the 's' extension + +;register => 2345:password@sip_proxy/1234 + +; Register 2345 at sip provider 'sip_proxy'. Calls from this provider +; connect to local extension 1234 in extensions.conf, default context, +; unless you configure a [sip_proxy] section below, and configure a +; context. +; Tip 1: Avoid assigning hostname to a sip.conf section like [provider.com] +; Tip 2: Use separate inbound and outbound sections for SIP providers +; (instead of type=friend) if you have calls in both directions + +;register => 3456@mydomain:5082::@mysipprovider.com + +; Note that in this example, the optional authuser and secret portions have +; been left blank because we have specified a port in the user section + +;register => tls://username:xxxxxx@sip-tls-proxy.example.org + +; The 'transport' part defaults to 'udp' but may also be 'tcp' or 'tls'. +; Using 'udp://' explicitly is also useful in case the username part +; contains a '/' ('user/name'). + +;registertimeout=20 ; retry registration calls every 20 seconds (default) +;registerattempts=10 ; Number of registration attempts before we give up + ; 0=continue forever, hammering the other server + ; until it accepts the registration + ; Default is 0 tries, continue forever +;register_retry_403=yes ; Treat 403 responses to registrations as if they were + ; 401 responses and continue retrying according to normal + ; retry rules. + +; Outbound MWI Subscriptions +; ---------------------------------------------------------- +; Asterisk can subscribe to receive the MWI from another SIP server and store it locally for retrieval +; by other phones. At this time, you can only subscribe using UDP as the transport. +; Format for the mwi register statement is: +; mwi => user[:secret[:authuser]]@host[:port]/mailbox[@context] +; +; Examples: + +;mwi => 1234:password@mysipprovider.com/1234 +;mwi => 1234:password@myportprovider.com:6969/1234 +;mwi => 1234:password:authuser@myauthprovider.com/1234 +;mwi => 1234:password:authuser@myauthportprovider.com:6969/1234 + +; MWI received will be stored in the 1234 mailbox of the SIP_Remote context +; It can be used by other phones by following the below: +; mailbox=1234@SIP_Remote + +; NAT Support +; ---------------------------------------------------------- +; WARNING: SIP operation behind a NAT is tricky and you really need +; to read and understand well the following section. +; +; When Asterisk is behind a NAT device, the "local" address (and port) that +; a socket is bound to has different values when seen from the inside or +; from the outside of the NATted network. Unfortunately this address must +; be communicated to the outside (e.g. in SIP and SDP messages), and in +; order to determine the correct value Asterisk needs to know: +; +; + whether it is talking to someone "inside" or "outside" of the NATted network. +; This is configured by assigning the "localnet" parameter with a list +; of network addresses that are considered "inside" of the NATted network. +; IF LOCALNET IS NOT SET, THE EXTERNAL ADDRESS WILL NOT BE SET CORRECTLY. +; Multiple entries are allowed, e.g. a reasonable set is the following: +; +; localnet=192.168.0.0/255.255.0.0 ; RFC 1918 addresses +; localnet=10.0.0.0/255.0.0.0 ; Also RFC1918 +; localnet=172.16.0.0/12 ; Another RFC1918 with CIDR notation +; localnet=169.254.0.0/255.255.0.0 ; Zero conf local network +; +; + the "externally visible" address and port number to be used when talking +; to a host outside the NAT. This information is derived by one of the +; following (mutually exclusive) config file parameters: +; +; a. "externaddr=hostname[:port]" specifies a static address[:port] to +; be used in SIP and SDP messages. +; The hostname is looked up only once, when [re]loading sip.conf . +; If a port number is not present, use the port specified in the "udpbindaddr" +; (which is not guaranteed to work correctly, because a NAT box might remap the +; port number as well as the address). +; This approach can be useful if you have a NAT device where you can +; configure the mapping statically. +; +; Examples: +; +;externaddr=12.34.56.78 ; use this address. +;externaddr=12.34.56.78:9900 ; use this address and port. +;externaddr=mynat.my.org:12600 ; Public address of my nat box. +;externtcpport=9900 ; The externally mapped tcp port, when Asterisk is behind a static NAT or PAT. +; ; externtcpport will default to the externaddr or externhost port if either one is set. +;externtlsport=12600 ; The externally mapped tls port, when Asterisk is behind a static NAT or PAT. +; ; externtlsport port will default to the RFC designated port of 5061. + +; b. "externhost=hostname[:port]" is similar to "externaddr" except +; that the hostname is looked up every "externrefresh" seconds +; (default 10s). This can be useful when your NAT device lets you choose +; the port mapping, but the IP address is dynamic. +; Beware, you might suffer from service disruption when the name server +; resolution fails. Examples: +; +; externhost=foo.dyndns.net ; refreshed periodically +; externrefresh=180 ; change the refresh interval +; +; Note that at the moment all these mechanism work only for the SIP socket. +; The IP address discovered with externaddr/externhost is reused for +; media sessions as well, but the port numbers are not remapped so you +; may still experience problems. +; +; NOTE 1: in some cases, NAT boxes will use different port numbers in +; the internal<->external mapping. In these cases, the "externaddr" and +; "externhost" might not help you configure addresses properly. +; +; NOTE 2: when using "externaddr" or "externhost", the address part is +; also used as the external address for media sessions. Thus, the port +; information in the SDP may be wrong! +; +; In addition to the above, Asterisk has an additional "nat" parameter to +; address NAT-related issues in incoming SIP or media sessions. +; In particular, depending on the 'nat= ' settings described below, Asterisk +; may override the address/port information specified in the SIP/SDP messages, +; and use the information (sender address) supplied by the network stack instead. +; However, this is only useful if the external traffic can reach us. +; The following settings are allowed (both globally and in individual sections): + +;nat=no ; Do no special NAT handling other than RFC3581 +;nat=force_rport ; Pretend there was an rport parameter even if there wasn't +;nat=comedia ; Send media to the port Asterisk received it from regardless +; ; of where the SDP says to send it. +;nat=auto_force_rport ; Set the force_rport option if Asterisk detects NAT (default) +;nat=auto_comedia ; Set the comedia option if Asterisk detects NAT + +; The nat settings can be combined. For example, to set both force_rport and comedia +; one would set nat=force_rport,comedia. If any of the comma-separated options is 'no', +; Asterisk will ignore any other settings and set nat=no. If one of the "auto" settings +; is used in conjunction with its non-auto counterpart (nat=comedia,auto_comedia), then +; 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 IP 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 force_rport setting causes Asterisk to always send responses back to the +; address/port from which it received requests; even if the other side doesn't support +; adding the 'rport' parameter. +; +; 'comedia RTP handling' refers to the technique of sending RTP to the port that the +; the other endpoint's RTP arrived from, and means 'connection-oriented media'. This is +; only partially related to RFC 4145 which was referred to as COMEDIA while it was in +; draft form. 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 DEFINE NAT SETTINGS IN THE +; GENERAL SECTION. Specifically, if nat=force_rport in one section and nat=no in the +; other, then valid peers with settings differing from those in the general section will +; be discoverable. +; +; In addition to these settings, Asterisk *always* uses 'symmetric RTP' mode as defined by +; RFC 4961; Asterisk will always send RTP packets from the same port number it expects +; to receive them on. +; +; The IP address used for media (audio, video, and text) in the SDP can also be overridden by using +; the media_address configuration option. This is only applicable to the general section and +; can not be set per-user or per-peer. +; +; Note that 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 IP address. + +;media_address=172.16.42.1 + +; Through the use of the res_stun_monitor module, Asterisk has the ability to detect when the +; perceived external network address has changed. When the stun_monitor is installed and +; configured, chan_sip will renew all outbound registrations when the monitor detects any sort +; of network change has occurred. By default this option is enabled, but only takes effect once +; res_stun_monitor is configured. If res_stun_monitor is enabled and you wish to not +; generate all outbound registrations on a network change, use the option below to disable +; this feature. + +;subscribe_network_change_event=yes ; on by default + +; ICE/STUN/TURN usage can be enabled globally or on a per-peer basis using the icesupport +; configuration option. When set to yes ICE support is enabled. When set to no it is disabled. +; It is disabled by default. + +;icesupport=yes + +; Media Handling +; ---------------------------------------------------------- +; By default, Asterisk tries to re-invite media streams to an optimal path. If there's +; 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=nonat. + +;directmedia=yes ; Asterisk by default tries to redirect the + ; RTP media stream to go directly from + ; the caller to the callee. Some devices do not + ; support this (especially if one of them is behind a NAT). + ; The default setting is YES. If you have all clients + ; behind a NAT, or for some other reason want Asterisk to + ; stay in the audio path, you may want to turn this off. + ; This setting also affect direct RTP + ; at call setup (a new feature in 1.4 - setting up the + ; call directly between the endpoints instead of sending + ; a re-INVITE). + ; Additionally this option does not disable all reINVITE operations. + ; It only controls Asterisk generating reINVITEs for the specific + ; purpose of setting up a direct media path. If a reINVITE is + ; needed to switch a media stream to inactive (when placed on + ; hold) or to T.38, it will still be done, regardless of this + ; setting. Note that direct T.38 is not supported. +;directmedia=nonat ; An additional option is to allow media path redirection + ; (reinvite) but only when the peer where the media is being + ; sent is known to not be behind a NAT (as the RTP core can + ; determine it based on the apparent IP address the media + ; arrives from). +;directmedia=update ; Yet a third option... use UPDATE for media path redirection, + ; instead of INVITE. This can be combined with 'nonat', as + ; 'directmedia=update,nonat'. It implies 'yes'. +;directmedia=outgoing ; When sending directmedia reinvites, do not send an immediate + ; reinvite on an incoming call leg. This option is useful when + ; peered with another SIP user agent that is known to send + ; immediate direct media reinvites upon call establishment. Setting + ; the option in this situation helps to prevent potential glares. + ; Setting this option implies 'yes'. +;directrtpsetup=yes ; Enable the new experimental direct RTP setup. This sets up + ; the call directly with media peer-2-peer without re-invites. + ; Will not work for video and cases where the callee sends + ; RTP payloads and fmtp headers in the 200 OK that does not match the + ; callers INVITE. This will also fail if directmedia is enabled when + ; the device is actually behind NAT. +;directmediadeny=0.0.0.0/0 ; Use directmediapermit and directmediadeny to restrict +;directmediapermit=172.16.0.0/16; which RTP source IPs should be able to pass directmedia to + ; each other. Note that directmedia ACLs are not a global + ; setting, but must be defined per peer. + ; (There is no default setting, this is just an example) + ; Use this if some of your phones are on IP addresses that + ; can not reach each other directly. This way you can force + ; RTP to always flow through asterisk in such cases. +;directmediaacl=acl_example ; Use named ACLs defined in acl.conf +;ignoresdpversion=yes ; By default, Asterisk will honor the session version + ; number in SDP packets and will only modify the SDP + ; session if the version number changes. This option will + ; force asterisk to ignore the SDP session version number + ; and treat all SDP data as new data. This is required + ; for devices that send us non standard SDP packets + ; (observed with Microsoft OCS). By default this option is + ; off. +;sdpsession=Asterisk PBX ; Allows you to change the SDP session name string, (s=) + ; Like the useragent parameter, the default user agent string + ; also contains the Asterisk version. +;sdpowner=root ; Allows you to change the username field in the SDP owner string, (o=) + ; This field MUST NOT contain spaces +;encryption=no ; Whether to offer SRTP encrypted media (and only SRTP encrypted media) + ; on outgoing calls to a peer. Calls will fail with HANGUPCAUSE=58 if + ; the peer does not support SRTP. Defaults to no. +;encryption_taglen=80 ; Set the auth tag length offered in the INVITE either 32/80 default 80 +;avpf=yes ; Enable inter-operability with media streams using the AVPF RTP profile. + ; This will cause all offers and answers to use AVPF (or SAVPF). This + ; option may be specified at the global or peer scope. +;force_avp=yes ; Force 'RTP/AVP', 'RTP/AVPF', 'RTP/SAVP', and 'RTP/SAVPF' to be used for + ; media streams when appropriate, even if a DTLS stream is present. +;rtcp_mux=yes ; Enable support for RFC 5761 RTCP multiplexing which is required for + ; WebRTC support + +; Realtime Support +; ---------------------------------------------------------- +; For additional information on ARA, the Asterisk Realtime Architecture, +; please read https://wiki.asterisk.org/wiki/display/AST/Realtime+Database+Configuration + +;rtcachefriends=yes ; Cache realtime friends by adding them to the internal list + ; just like friends added from the config file only on a + ; as-needed basis? (yes|no) +;rtsavesysname=yes ; Save systemname in realtime database at registration + ; Default no +;rtupdate=yes ; Send registry updates to database using realtime? (yes|no) + ; If set to yes, when a SIP UA registers successfully, the ip address, + ; the origination port, the registration period, and the username of + ; the UA will be set to database via realtime. + ; If not present, defaults to 'yes'. Note: realtime peers will + ; probably not function across reloads in the way that you expect, if + ; you turn this option off. +;rtautoclear=yes ; Auto-Expire friends created on the fly on the same schedule + ; as if it had just registered? (yes|no|) + ; If set to yes, when the registration expires, the friend will + ; vanish from the configuration until requested again. If set + ; to an integer, friends expire within this number of seconds + ; instead of the registration interval. +;ignoreregexpire=yes ; Enabling this setting has two functions: + ; For non-realtime peers, when their registration expires, the + ; information will _not_ be removed from memory or the Asterisk database + ; if you attempt to place a call to the peer, the existing information + ; will be used in spite of it having expired + ; For realtime peers, when the peer is retrieved from realtime storage, + ; the registration information will be used regardless of whether + ; it has expired or not; if it expires while the realtime peer + ; is still in memory (due to caching or other reasons), the + ; information will not be removed from realtime storage + +; SIP Domain Support +; ---------------------------------------------------------- +; 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. +; +; Domains can be specified using: +; domain=[,] +; Examples: +; domain=myasterisk.dom +; domain=customer.com,customer-context +; +; In addition, all the 'default' domains associated with a server should be +; added if incoming request filtering is desired. +; autodomain=yes +; +; To disallow requests for domains not serviced by this server: +; allowexternaldomains=no + +;domain=mydomain.tld,mydomain-incoming + ; Add domain and configure incoming context + ; for external calls to this domain +;domain=1.2.3.4 ; Add IP address as local domain + ; You can have several "domain" settings +;allowexternaldomains=no ; Disable INVITE and REFER to non-local domains + ; Default is yes +;autodomain=yes ; Turn this on to have Asterisk add local host + ; name and local IP to domain list. +;fromdomain=mydomain.tld ; When making outbound SIP INVITEs to + ; non-peers, use your primary domain "identity" + ; for From: headers instead of just your IP + ; address. This is to be polite and + ; it may be a mandatory requirement for some + ; destinations which do not have a prior + ; account relationship with your server. + +; Jitter Buffer Configuration +; ---------------------------------------------------------- +;jbenable=yes ; Enables the use of a jitterbuffer on the receiving side of a + ; SIP channel. Defaults to "no". An enabled jitterbuffer will + ; be used only if the sending side can create and the receiving + ; side can not accept jitter. The SIP channel can accept jitter, + ; thus a jitterbuffer on the receive SIP side will be used only + ; if it is forced and enabled. +;jbforce=no ; Forces the use of a jitterbuffer on the receive side of a SIP + ; channel. Defaults to "no". +;jbmaxsize=200 ; Max length of the jitterbuffer in milliseconds. +;jbresyncthreshold=1000 ; Jump in the frame timestamps over which the jitterbuffer is + ; resynchronized. Useful to improve the quality of the voice, with + ; big jumps in/broken timestamps, usually sent from exotic devices + ; and programs. Defaults to 1000. +;jbimpl=fixed ; Jitterbuffer implementation, used on the receiving side of a SIP + ; channel. Two implementations are currently available - "fixed" + ; (with size always equals to jbmaxsize) and "adaptive" (with + ; variable size, actually the new jb of IAX2). Defaults to fixed. +;jbtargetextra=40 ; This option only affects the jb when 'jbimpl=adaptive' is set. + ; The option represents the number of milliseconds by which the new jitter buffer + ; will pad its size. the default is 40, so without modification, the new + ; jitter buffer will set its size to the jitter value plus 40 milliseconds. + ; increasing this value may help if your network normally has low jitter, + ; but occasionally has spikes. +;jblog=no ; Enables jitterbuffer frame logging. Defaults to "no". + +[authentication] +; Global credentials for outbound calls, i.e. when a proxy challenges your +; Asterisk server for authentication. These credentials override +; any credentials in peer/register definition if realm is matched. +; +; This way, Asterisk can authenticate for outbound calls to other +; realms. We match realm on the proxy challenge and pick an set of +; credentials from this list +; Syntax: +; auth=:@ +; auth=#@ +; Example: +;auth=mark:topsecret@digium.com +; +; You may also add auth= statements to [peer] definitions +; Peer auth= override all other authentication settings if we match on realm + +; Device Configuration +; ---------------------------------------------------------- +; SIP entities have a 'type' which determines their roles within Asterisk. +; * For entities with 'type=peer': +; Peers handle both inbound and outbound calls and are matched by ip/port, so for +; The case of incoming calls from the peer, the IP address must match in order for +; The invitation to work. This means calls made from either direction won't work if +; The peer is unregistered while host=dynamic or if the host is otherise not set to +; the correct IP of the sender. +; * For entities with 'type=user': +; Asterisk users handle inbound calls only (meaning they call Asterisk, Asterisk can't +; call them) and are matched by their authorization information (authname and secret). +; Asterisk doesn't rely on their IP and will accept calls regardless of the host setting +; as long as the incoming SIP invite authorizes successfully. +; * For entities with 'type=friend': +; Asterisk will create the entity as both a friend and a peer. Asterisk will accept +; calls from friends like it would for users, requiring only that the authorization +; matches rather than the IP address. Since it is also a peer, a friend entity can +; be called as long as its IP is known to Asterisk. In the case of host=dynamic, +; this means it is necessary for the entity to register before Asterisk can call it. +; +; Use remotesecret for outbound authentication, and secret for authenticating +; inbound requests. For historical reasons, if no remotesecret is supplied for an +; outbound registration or call, the secret will be used. +; +; For device names, we recommend using only a-z, numerics (0-9) and underscore +; +; For local phones, type=friend works most of the time +; +; If you have one-way audio, you probably have NAT problems. +; If Asterisk is on a public IP, and the phone is inside of a NAT device +; you will need to configure nat option for those phones. +; Also, turn on qualify=yes to keep the nat session open +; +; Configuration options available: +; +; context +; callingpres +; permit +; deny +; secret +; md5secret +; remotesecret +; transport +; dtmfmode +; directmedia +; nat +; callgroup +; pickupgroup +; language +; allow +; disallow +; autoframing +; insecure +; trustrpid +; trust_id_outbound +; progressinband +; promiscredir +; useclientcode +; accountcode +; setvar +; callerid +; amaflags +; callcounter +; busylevel +; allowoverlap +; allowsubscribe +; allowtransfer +; ignoresdpversion +; subscribecontext +; template +; videosupport +; maxcallbitrate +; rfc2833compensate +; Note: app_voicemail mailboxes must be in the form of mailbox@context. +; mailbox +; session-timers +; session-expires +; session-minse +; session-refresher +; t38pt_usertpsource +; fromdomain +; fromuser +; host +; port +; qualify +; keepalive +; defaultip +; defaultuser +; rtptimeout +; rtpholdtimeout +; sendrpid +; outboundproxy +; rfc2833compensate +; callbackextension +; timert1 +; timerb +; qualifyfreq +; t38pt_usertpsource +; contactpermit ; Limit what a host may register as (a neat trick +; contactdeny ; is to register at the same IP as a SIP provider, +; contactacl ; then call oneself, and get redirected to that +; ; same location). +; directmediapermit +; directmediadeny +; directmediaacl +; unsolicited_mailbox +; use_q850_reason +; maxforwards +; encryption +; description ; Used to provide a description of the peer in console output +; ignore_requested_pref ; Ignore the requested codec and determine the preferred codec +; ; from the peer's configuration. diff -durN asterisk-22.3.0.orig/configs/samples/sip_notify.conf.sample asterisk-22.3.0/configs/samples/sip_notify.conf.sample --- asterisk-22.3.0.orig/configs/samples/sip_notify.conf.sample 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.3.0/configs/samples/sip_notify.conf.sample 2025-04-17 11:11:22.827702849 +1200 @@ -0,0 +1,80 @@ +; RFC3842 +; put empty "Content=" at the end to have CRLF after last body line +[clear-mwi] +Event=message-summary +Content-Type=application/simple-message-summary +Content=Messages-Waiting: no +Content=Message-Account: sip:asterisk@127.0.0.1 +Content=Voice-Message: 0/0 (0/0) +Content= + +; Aastra +[aastra-check-sync] +Event=check-sync + +[aastra-xml] +Event=aastra-xml + +; Digium +[digium-check-sync] +Event=check-sync + +; Linksys +[linksys-cold-restart] +Event=reboot_now + +[linksys-warm-restart] +Event=restart_now + +; Polycom +[polycom-check-sync] +Event=check-sync + +; Sipura +[sipura-check-sync] +Event=resync + +[sipura-get-report] +Event=report + +; SNOM +[snom-check-sync] +Event=check-sync\;reboot=false + +[snom-reboot] +Event=check-sync\;reboot=true + +; Cisco +[cisco-check-sync] +Event=check-sync + +[cisco-restart] +Event=service-control +Subscription-State=active +Content-Type=text/plain +Content=action=restart +Content=RegisterCallId={${SIPPEER(${PEERNAME},regcallid)}} +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} +Content=HeadsetVersionStamp={0-0000000000} + +[cisco-reset] +Event=service-control +Subscription-State=active +Content-Type=text/plain +Content=action=reset +Content=RegisterCallId={${SIPPEER(${PEERNAME},regcallid)}} +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} +Content=HeadsetVersionStamp={0-0000000000} + +[cisco-prt-report] +Event=service-control +Subscription-State=active +Content-Type=text/plain +Content=action=prt-report +Content=RegisterCallId={${SIPPEER(${PEERNAME},regcallid)}} diff -durN asterisk-22.3.0.orig/contrib/realtime/mysql/mysql_config.sql asterisk-22.3.0/contrib/realtime/mysql/mysql_config.sql --- asterisk-22.3.0.orig/contrib/realtime/mysql/mysql_config.sql 2025-04-17 11:10:32.130053923 +1200 +++ asterisk-22.3.0/contrib/realtime/mysql/mysql_config.sql 2025-04-17 11:11:22.828702823 +1200 @@ -95,6 +95,9 @@ dynamic ENUM('yes','no'), path VARCHAR(256), supportpath ENUM('yes','no'), + donotdisturb ENUM('yes','no'), + callforward VARCHAR(40), + huntgroup ENUM('yes','no'), PRIMARY KEY (id), UNIQUE (name) ); diff -durN asterisk-22.3.0.orig/contrib/realtime/postgresql/postgresql_config.sql asterisk-22.3.0/contrib/realtime/postgresql/postgresql_config.sql --- asterisk-22.3.0.orig/contrib/realtime/postgresql/postgresql_config.sql 2025-04-17 11:10:32.133053843 +1200 +++ asterisk-22.3.0/contrib/realtime/postgresql/postgresql_config.sql 2025-04-17 11:11:22.829702796 +1200 @@ -115,6 +115,9 @@ dynamic yes_no_values, path VARCHAR(256), supportpath yes_no_values, + donotdisturb yes_no_values, + callforward VARCHAR(40), + huntgroup yes_no_values, PRIMARY KEY (id), UNIQUE (name) ); diff -durN asterisk-22.3.0.orig/include/asterisk/callerid.h asterisk-22.3.0/include/asterisk/callerid.h --- asterisk-22.3.0.orig/include/asterisk/callerid.h 2025-04-17 11:10:32.253050645 +1200 +++ asterisk-22.3.0/include/asterisk/callerid.h 2025-04-17 11:11:22.829702796 +1200 @@ -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 -durN asterisk-22.3.0.orig/include/asterisk/parking.h asterisk-22.3.0/include/asterisk/parking.h --- asterisk-22.3.0.orig/include/asterisk/parking.h 2025-04-17 11:10:32.292049606 +1200 +++ asterisk-22.3.0/include/asterisk/parking.h 2025-04-17 11:11:22.830702769 +1200 @@ -50,6 +50,7 @@ PARKED_CALL_UNPARKED, PARKED_CALL_FAILED, PARKED_CALL_SWAP, + PARKED_CALL_REMINDER, }; /*! diff -durN asterisk-22.3.0.orig/main/callerid.c asterisk-22.3.0/main/callerid.c --- asterisk-22.3.0.orig/main/callerid.c 2025-04-17 11:10:32.371047501 +1200 +++ asterisk-22.3.0/main/callerid.c 2025-04-17 11:11:22.831702743 +1200 @@ -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 -durN asterisk-22.3.0.orig/main/cel.c asterisk-22.3.0/main/cel.c --- asterisk-22.3.0.orig/main/cel.c 2025-04-17 11:10:32.380047261 +1200 +++ asterisk-22.3.0/main/cel.c 2025-04-17 11:11:22.832702716 +1200 @@ -1156,6 +1156,8 @@ case PARKED_CALL_SWAP: reason = "ParkedCallSwap"; break; + case PARKED_CALL_REMINDER: + return; } if (parked_payload->retriever) { diff -durN asterisk-22.3.0.orig/main/pbx.c asterisk-22.3.0/main/pbx.c --- asterisk-22.3.0.orig/main/pbx.c 2025-04-17 11:10:32.444045555 +1200 +++ asterisk-22.3.0/main/pbx.c 2025-04-17 11:11:22.833702689 +1200 @@ -8396,12 +8396,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 -durN asterisk-22.3.0.orig/main/presencestate.c asterisk-22.3.0/main/presencestate.c --- asterisk-22.3.0.orig/main/presencestate.c 2025-04-17 11:10:32.456045235 +1200 +++ asterisk-22.3.0/main/presencestate.c 2025-04-17 11:11:22.835702636 +1200 @@ -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 -durN asterisk-22.3.0.orig/res/parking/parking_applications.c asterisk-22.3.0/res/parking/parking_applications.c --- asterisk-22.3.0.orig/res/parking/parking_applications.c 2025-04-17 11:10:32.727038014 +1200 +++ asterisk-22.3.0/res/parking/parking_applications.c 2025-04-17 11:11:22.835702636 +1200 @@ -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 -durN asterisk-22.3.0.orig/res/parking/parking_bridge.c asterisk-22.3.0/res/parking/parking_bridge.c --- asterisk-22.3.0.orig/res/parking/parking_bridge.c 2025-04-17 11:10:32.728037987 +1200 +++ asterisk-22.3.0/res/parking/parking_bridge.c 2025-04-17 11:11:22.836702609 +1200 @@ -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 lid_num[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(lid_num, sizeof(lid_num), "%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(lid_num); + 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 -durN asterisk-22.3.0.orig/res/parking/parking_bridge_features.c asterisk-22.3.0/res/parking/parking_bridge_features.c --- asterisk-22.3.0.orig/res/parking/parking_bridge_features.c 2025-04-17 11:10:32.728037987 +1200 +++ asterisk-22.3.0/res/parking/parking_bridge_features.c 2025-04-17 11:11:22.837702583 +1200 @@ -681,6 +681,15 @@ 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 +738,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 -durN asterisk-22.3.0.orig/res/parking/parking_manager.c asterisk-22.3.0/res/parking/parking_manager.c --- asterisk-22.3.0.orig/res/parking/parking_manager.c 2025-04-17 11:10:32.729037960 +1200 +++ asterisk-22.3.0/res/parking/parking_manager.c 2025-04-17 11:11:22.838702556 +1200 @@ -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 -durN asterisk-22.3.0.orig/res/parking/parking_ui.c asterisk-22.3.0/res/parking/parking_ui.c --- asterisk-22.3.0.orig/res/parking/parking_ui.c 2025-04-17 11:10:32.730037934 +1200 +++ asterisk-22.3.0/res/parking/parking_ui.c 2025-04-17 11:11:22.838702556 +1200 @@ -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 -durN asterisk-22.3.0.orig/res/parking/res_parking.h asterisk-22.3.0/res/parking/res_parking.h --- asterisk-22.3.0.orig/res/parking/res_parking.h 2025-04-17 11:10:32.731037907 +1200 +++ asterisk-22.3.0/res/parking/res_parking.h 2025-04-17 11:11:22.839702529 +1200 @@ -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 -durN asterisk-22.3.0.orig/res/res_format_attr_h264.c asterisk-22.3.0/res/res_format_attr_h264.c --- asterisk-22.3.0.orig/res/res_format_attr_h264.c 2025-04-17 11:10:32.793036255 +1200 +++ asterisk-22.3.0/res/res_format_attr_h264.c 2025-04-17 11:11:22.839702529 +1200 @@ -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 -durN asterisk-22.3.0.orig/res/res_parking.c asterisk-22.3.0/res/res_parking.c --- asterisk-22.3.0.orig/res/res_parking.c 2025-04-17 11:10:32.812035749 +1200 +++ asterisk-22.3.0/res/res_parking.c 2025-04-17 11:11:22.840702503 +1200 @@ -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 -durN asterisk-22.3.0.orig/res/res_srtp.c asterisk-22.3.0/res/res_srtp.c --- asterisk-22.3.0.orig/res/res_srtp.c 2025-04-17 11:10:32.942032284 +1200 +++ asterisk-22.3.0/res/res_srtp.c 2025-04-17 11:11:22.841702476 +1200 @@ -1140,19 +1140,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;