diff -durN asterisk-22.2.0.orig/channels/Makefile asterisk-22.2.0/channels/Makefile --- asterisk-22.2.0.orig/channels/Makefile 2025-02-18 17:14:37.612147735 +1300 +++ asterisk-22.2.0/channels/Makefile 2025-02-18 17:14:46.766903764 +1300 @@ -29,4 +29,7 @@ chan_unistim.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION) chan_phone.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION) +$(call MOD_ADD_C,chan_sip,$(wildcard sip/*.c)) +chan_sip.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION) + $(call MOD_ADD_C,console_video.c vgrabbers.c console_board.c) diff -durN asterisk-22.2.0.orig/channels/chan_sip.c asterisk-22.2.0/channels/chan_sip.c --- asterisk-22.2.0.orig/channels/chan_sip.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/chan_sip.c 2025-02-18 17:14:46.769903684 +1300 @@ -0,0 +1,694 @@ +/* + * 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.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/pvt.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/message.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_debugaddr; + +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"); +} + +/* See if we pass debug IP filter */ +int sip_debug_test_addr(const struct ast_sockaddr *addr) +{ + /* Can't debug if sip_debug is not enabled */ + if (!sip_debug) { + return 0; + } + + /* A null debug_addr means we'll debug any address */ + if (ast_sockaddr_isnull(&sip_debugaddr)) { + return 1; + } + + /* If no port was specified for a debug address, just compare the addresses, otherwise compare the address and port */ + if (ast_sockaddr_port(&sip_debugaddr)) { + return !ast_sockaddr_cmp(&sip_debugaddr, addr); + } else { + return !ast_sockaddr_cmp_addr(&sip_debugaddr, addr); + } +} + +/* Test PVT for debugging output */ +int sip_debug_test_pvt(struct sip_pvt *pvt) +{ + if (!sip_debug) { + return 0; + } + + return sip_debug_test_addr(sip_pvt_real_dst(pvt)); +} + +/* 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; + time_t now; + int 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 start_qualify, end_qualify; + + ast_verb(1, "Reloading SIP\n"); + + sip_config_parse(sip_reload_reason); + ast_sched_dump(sip_sched_context); + + start_qualify = time(NULL); + /* Prune peers who still are supposed to be deleted */ + sip_peer_unlink_all(TRUE); + + ast_debug(4, "Done destroying pruned peers\n"); + + /* Send qualify (OPTIONS) to all peers */ + sip_peer_qualify_all(); + /* Register aliases now that all peers have been added */ + sip_peer_register_aliases_all(); + /* Send keepalive to all peers */ + sip_peer_keepalive_all(); + + /* Register with all services */ + sip_registry_send_all(); + sip_mwi_subscription_send_all(); + + end_qualify = time(0); + + ast_debug(4, "Reload finished. peer qualify/prune reg contact time %d sec.\n", (int) (end_qualify - start_qualify)); + + /* 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 for dialogs needing to be killed */ + now = time(NULL); + + /* Check dialogs with rtp and rtptimeout. All dialogs which have rtp are in sip_pvts_rtp_check.*/ + ao2_t_callback(sip_pvts_rtp_check, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, sip_pvt_rtp_check, &now, + "callback to check rtptimeout and hangup calls if necessary"); + + /* Check dialogs marked to be destroyed. All dialogs with needdestroy set are in sip_pvts_need_destroy. */ + ao2_t_callback(sip_pvts_need_destroy, OBJ_NODATA | OBJ_MULTIPLE, sip_pvt_need_destroy, + NULL, "callback to check dialogs which need to be destroyed"); + + /* 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 kill myself\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, "allocate peers"); + sip_peers_by_addr = ao2_t_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 563, sip_peer_hash_addr, NULL, sip_peer_cmp_addr, "allocate sip_peers_by_addr"); + + sip_pvts = ao2_t_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 1031, sip_pvt_hash, NULL, sip_pvt_cmp, "allocate sip_pvts"); + sip_pvts_need_destroy = ao2_t_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 11, NULL, NULL, NULL, "allocate sip_pvts_need_destroy"); + sip_pvts_rtp_check = ao2_t_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 563, sip_pvt_hash, NULL, sip_pvt_cmp, "allocate sip_pvts_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, + "allocate sip_tcptls_threads table"); + + sip_registry = ao2_t_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 23, sip_registry_hash, NULL, sip_registry_cmp, + "allocate sip_registry"); + sip_mwi_subscriptions = ao2_t_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, AO2_CONTAINER_ALLOC_OPT_INSERT_BEGIN, NULL, NULL, + "allocate sip_mwi_subscriptions"); + + if (!sip_peers || !sip_peers_by_addr || !sip_pvts || !sip_pvts_need_destroy || !sip_pvts_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_lock); + + if (!(sip_config.caps = 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.peer_rtupdate) { + 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_pvt *pvt; + 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, "iterate through tcp threads for 'sip show tcp'"))) { + thread->stop = TRUE; + pthread_kill(thread->threadid, SIGURG); + ao2_t_ref(thread, -1, "decrement ref from iterator"); + } + + ao2_iterator_destroy(&iter); + + /* Hangup all dialogs if they have an owner */ + iter = ao2_iterator_init(sip_pvts, 0); + + while ((pvt = ao2_t_iterator_next(&iter, "iterate thru dialogs"))) { + if (pvt->owner) { + ast_softhangup(pvt->owner, AST_SOFTHANGUP_APPUNLOAD); + } + + ao2_t_ref(pvt, -1, "toss dialog ptr from iterator_next"); + } + + 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_destroy_all(); + ast_mutex_destroy(&sip_auth_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_pvts, 0); + + while ((pvt = ao2_t_iterator_next(&iter, "iterate thru dialogs"))) { + sip_pvt_unlink(pvt); + ao2_t_ref(pvt, -1, "throw away iterator result"); + } + + 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.localaddr); + + 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, "unref sip registry table"); + ao2_t_cleanup(sip_mwi_subscriptions, "unref sip mwi subscription table"); + + ao2_t_cleanup(sip_bogus_peer, "Release the bogus peer."); + + ao2_t_cleanup(sip_peers, "unref the peers table"); + ao2_t_cleanup(sip_peers_by_addr, "unref the sip_peers_by_addr table"); + + ao2_t_cleanup(sip_pvts, "unref the dialogs table"); + ao2_t_cleanup(sip_pvts_need_destroy, "unref sip_pvts_need_destroy"); + ao2_t_cleanup(sip_pvts_rtp_check, "unref sip_pvts_rtp_check"); + + ao2_t_cleanup(sip_tcptls_threads, "unref the thread table"); + + 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_context_destroy_by_name(sip_used_context, "SIP"); + + 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, "unref tech capabilities"); + sip_tech.capabilities = NULL; + + ao2_t_cleanup(sip_config.caps, "unref config capabilities"); + sip_config.caps = 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.2.0.orig/channels/sip/auth.c asterisk-22.2.0/channels/sip/auth.c --- asterisk-22.2.0.orig/channels/sip/auth.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/auth.c 2025-02-18 17:14:46.771903631 +1300 @@ -0,0 +1,152 @@ +/* + * 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.h" + +/* Authentication container for realm authentication */ +struct sip_auth_head *sip_auths; + +/* Global authentication container protection while adjusting the references. */ +ast_mutex_t sip_auth_lock; + +/* Add realm authentication to credentials. */ +void sip_auth_build(struct sip_auth_head **auths, const char *config, int lineno) +{ + char *username = NULL, *realm = NULL, *secret = NULL, *md5secret = NULL; + struct sip_auth *auth; + + if (ast_strlen_zero(config)) { + /* Nothing to add */ + return; + } + + ast_debug(1, "Auth config: %s\n", config); + + 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'; + } else if ((md5secret = strchr(username, '#'))) { + *md5secret++ = '\0'; + } + + /* Create the continer if needed. */ + if (!*auths) { + *auths = ao2_t_alloc(sizeof(**auths), sip_auth_destroy, "Create realm auth container."); + + if (!*auths) { + /* Failed to create the credentials container. */ + return; + } + } + + /* Create the authentication credential entry. */ + if (!(auth = ast_calloc(1, sizeof(*auth)))) { + return; + } + + if (ast_string_field_init(auth, 256)) { + ast_free(auth); + return; + } + + ast_string_field_set(auth, username, username); + ast_string_field_set(auth, realm, realm); + + if (secret) { + ast_string_field_set(auth, secret, secret); + } + + if (md5secret) { + ast_string_field_set(auth, md5secret, md5secret); + } + + /* Add credential to container list. */ + AST_LIST_INSERT_TAIL(&(*auths)->list, auth, next); + + ast_verb(3, "Added authentication for realm %s\n", realm); +} + +/* Realm authentication container destructor. */ +void sip_auth_destroy(void *data) +{ + struct sip_auth_head *auths = data; + struct sip_auth *auth; + + while ((auth = AST_LIST_REMOVE_HEAD(&auths->list, next))) { + ast_string_field_free_memory(auth); + ast_free(auth); + } +} + +void sip_auth_destroy_all(void) +{ + ast_mutex_lock(&sip_auth_lock); + + if (sip_auths) { + ao2_t_ref(sip_auths, -1, "Removing old global authentication"); + sip_auths = NULL; + } + + ast_mutex_unlock(&sip_auth_lock); +} + +/* Find authentication for a specific realm. */ +struct sip_auth *sip_auth_find(struct sip_auth_head *auths, const char *realm) +{ + struct sip_auth *auth; + + if (auths) { + AST_LIST_TRAVERSE(&auths->list, auth, next) { + if (!strcasecmp(auth->realm, realm)) { + break; + } + } + } else { + auth = NULL; + } + + return auth; +} diff -durN asterisk-22.2.0.orig/channels/sip/callback.c asterisk-22.2.0/channels/sip/callback.c --- asterisk-22.2.0.orig/channels/sip/callback.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/callback.c 2025-02-18 17:14:46.774903551 +1300 @@ -0,0 +1,451 @@ +/* + * 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/pvt.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->stateid, NULL); + ao2_t_cleanup(peer, "sip_callback_destroy: removing callback ref"); + + ast_free(peer->callback->exten); + ast_free(peer->callback); +} + +/* Handle remotecc callback requests */ +int sip_remotecc_callback(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data) +{ + struct sip_pvt *target_pvt, *refer_pvt; + struct ast_channel *chan = NULL; + char *exten = NULL, *subtype = NULL, *message = NULL; + int exten_state, presence_state; + int res = -1; + struct ast_str *content = ast_str_alloca(8192); + int is79xx = strstr(sip_request_get_header(req, "User-Agent"), "CP79") != NULL; + + sip_send_response(pvt, "202 Accepted", req); + + if (!ast_strlen_zero(remotecc_data->dialogid.callid)) { + /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ + if (!(target_pvt = sip_pvt_find(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { + ast_debug(1, "dialogid call leg does not exist\n"); + goto cleanup; + } + + ao2_lock(target_pvt); + + if (!(chan = target_pvt->owner)) { + ast_debug(1, "no owner channel\n"); + + ao2_unlock(target_pvt); + ao2_t_cleanup(target_pvt, "drop target_pvt"); + + goto cleanup; + } + + ast_channel_ref(chan); + ao2_unlock(target_pvt); + ao2_t_cleanup(target_pvt, "drop target_pvt"); + + ast_channel_lock(chan); + exten = ast_strdupa(S_COR(ast_channel_connected(chan)->id.number.valid, ast_channel_connected(chan)->id.number.str, target_pvt->exten)); + + ast_channel_unlock(chan); + ast_channel_unref(chan); + + 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, "copying peer into callback struct"); + + if (!(peer->callback->stateid = ast_extension_state_add(peer->context, peer->callback->exten, sip_callback_event, peer))) { + ao2_t_cleanup(peer, "copying peer into callback struct failed"); + 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); + + ast_free(subtype); + ast_free(message); + + if (exten_state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY) || presence_state == AST_PRESENCE_DND) { + peer->callback->busy = TRUE; + } + + ast_channel_hangupcause_set(chan, AST_CAUSE_FAILURE); + ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); + res = 0; + + cleanup: + if (res) { + if (!(refer_pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + return 0; + } + + sip_pvt_copy_data(refer_pvt, pvt); + + 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", is79xx ? 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_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(refer_pvt, "bump down the count of refer_pvt since we're done with it."); + + return 0; + } + } else if (!ast_strlen_zero(remotecc_data->usercalldata) && peer->callback) { + if (!(refer_pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + return 0; + } + + sip_pvt_copy_data(refer_pvt, pvt); + + 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_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(refer_pvt, "bump down the count of refer_pvt since we're done with it."); + + if (!strcmp(remotecc_data->usercalldata, "Dial")) { + ast_str_reset(content); + + if (!(refer_pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + return 0; + } + + sip_pvt_copy_data(refer_pvt, pvt); + + 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_lineindex); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + sip_send_refer_with_content(refer_pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_t_cleanup(refer_pvt, "bump down the count of refer_pvt since we're done with it."); + + sip_callback_destroy(peer); + peer->callback = NULL; + } else if (!strcmp(remotecc_data->usercalldata, "Cancel")) { + sip_callback_destroy(peer); + peer->callback = NULL; + } + + return 0; + } + + if (!(refer_pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + return 0; + } + + sip_pvt_copy_data(refer_pvt, pvt); + + 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", is79xx ? 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", is79xx ? 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_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(refer_pvt, "bump down the count of refer_pvt since we're done with it."); + + 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 = data; + struct sip_pvt *pvt; + struct ast_str *content = ast_str_alloca(8192); + int is79xx = strstr(peer->useragent, "CP79") != NULL; + struct timeval tv; + struct ast_tm tm; + char date[32]; + + 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; + } + + tv = ast_tvnow(); + ast_strftime(date, sizeof(date), "%X %x", ast_localtime(&tv, &tm, NULL)); + + if (!((pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0)))) { + return 0; + } + + sip_socket_set_transport(&pvt->socket, 0); + + if (sip_pvt_build_from_peer(pvt, peer)) { + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "sip_pvt_build_from_peer failed"); + 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(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(pvt, "bump down the count of pvt since we're done with it."); + + ast_str_reset(content); + + if (!((pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0)))) { + return 0; + } + + sip_socket_set_transport(&pvt->socket, 0); + + if (sip_pvt_build_from_peer(pvt, peer)) { + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "sip_pvt_build_from_peer failed"); + + 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", is79xx ? 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", is79xx ? 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", is79xx ? 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(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(pvt, "bump down the count of pvt since we're done with it."); + + return 0; +} diff -durN asterisk-22.2.0.orig/channels/sip/chan_tech.c asterisk-22.2.0/channels/sip/chan_tech.c --- asterisk-22.2.0.orig/channels/sip/chan_tech.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/chan_tech.c 2025-02-18 17:14:46.780903391 +1300 @@ -0,0 +1,1917 @@ +/* + * 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/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/pvt.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/message.h" +#include "include/dialplan_funcs.h" +#include "include/conference.h" + +static struct ast_frame *sip_read_rtp(struct ast_channel *chan, struct sip_pvt *pvt, int *faxdetect); + +/* 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 */ + .devicestate = sip_devicestate, /* called with chan unlocked (not chan-specific) */ + .presencestate = sip_presencestate, /* called with chan unlocked (not chan-specific) */ + .call = sip_call, /* called with chan locked */ + .send_html = sip_send_html, + .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, + .early_bridge = ast_rtp_instance_early_bridge, + .send_text = sip_send_text, /* called with chan locked */ + .func_channel_read = sip_func_channel_read, + .setoption = sip_setoption, + .queryoption = sip_queryoption, + .get_pvt_uniqueid = sip_get_pvt_uniqueid, +}; + +struct ast_sip_api_tech chan_sip_api_tech = { + .version = AST_SIP_API_VERSION, + .name = "chan_sip", + .sipinfo_send = sip_sipinfo_send, +}; + +/* PBX interface function -build SIP pvt 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/IPorHost + * or SIP/username@domain//IPorHost + * and there is an optional [!dnid] argument you can append to alter the + * To: header. And after that, a [![fromuser][@fromdomain]] argument. + * Leave those blank to use the defaults. */ +struct ast_channel *sip_requester(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, + const struct ast_channel *requestor, const char *dest, int *cause) +{ + struct sip_pvt *pvt; + struct ast_channel *chan = NULL; + char *exten = NULL, *host, *parse; + struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + char *dnid, *secret = NULL, *md5secret = NULL, *authname = NULL, *transportname = NULL, *remote_address; + enum ast_transport transport = 0; + ast_callid callid; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(peerorhost); + AST_APP_ARG(exten); + AST_APP_ARG(remote_address); + ); + + if (ast_format_cap_empty(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; + } + + ast_debug(1, "Asked to create a SIP channel with formats: %s\n", ast_format_cap_get_names(cap, &codec_buf)); + + if (ast_strlen_zero(dest)) { + 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 (!(pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_INVITE, NULL, callid))) { + ast_log(LOG_ERROR, "Unable to build sip pvt data for '%s' (Out of memory or socket error)\n", dest); + *cause = AST_CAUSE_SWITCH_CONGESTION; + + return NULL; + } + + pvt->outgoing_call = TRUE; + ast_string_field_build(pvt, dialstring, "%s/%s", type, dest); + + if (!(pvt->options = ast_calloc(1, sizeof(*pvt->options)))) { + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "unref dialog pvt from mem fail"); + + ast_log(LOG_ERROR, "Unable to build option SIP data structure - Out of memory\n"); + *cause = AST_CAUSE_SWITCH_CONGESTION; + + return NULL; + } + + /* Save the destination, the SIP dial string */ + parse = ast_strdupa(dest); + + /* Find optional DNID (SIP to-uri) and From-CLI (SIP from-uri) and strip it from the dial string: + * [!touser[@todomain][![fromuser][@fromdomain]]] For historical reasons, the touser@todomain is passed as dnid while + * fromuser@fromdomain are split immediately. Passing a todomain without touser will create an invalid SIP message. */ + if ((dnid = strchr(parse, '!'))) { + char *fromuser; + + *dnid++ = '\0'; + + if ((fromuser = strchr(dnid, '!'))) { + char *sep; + char *fromdomain; + + *fromuser++ = '\0'; + + /* Cut it at a trailing NUL or trailing '!' for forward compatibility with extra arguments in the future. */ + if ((sep = strchr(fromuser, '!'))) { + /* Ignore the rest.. */ + *sep = '\0'; + } + + if ((fromdomain = strchr(fromuser, '@'))) { + *fromdomain++ = '\0'; + + /* Set fromdomain. */ + if (!ast_strlen_zero(fromdomain)) { + ast_string_field_set(pvt, fromdomain, fromdomain); + } + } + + /* Set fromuser. */ + if (!ast_strlen_zero(fromuser)) { + ast_string_field_set(pvt, fromuser, fromuser); + } + } + + /* Set DNID (touser/todomain). */ + if (!ast_strlen_zero(dnid)) { + ast_string_field_set(pvt, todnid, dnid); + } + } + + /* If stripping the DNID left us with nothing, bail out */ + if (ast_strlen_zero(parse)) { + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "unref dialog pvt from bad destination"); + + *cause = AST_CAUSE_DESTINATION_OUT_OF_ORDER; + + return NULL; + } + + /* Divvy up the items separated by slashes */ + AST_NONSTANDARD_APP_ARGS(args, parse, '/'); + host = strchr(args.peerorhost, '@'); /* Find @ sign */ + + if (host) { + *host++ = '\0'; + exten = args.peerorhost; + secret = strchr(exten, ':'); + } + + if (secret) { + *secret++ = '\0'; + md5secret = strchr(secret, ':'); + } + + if (md5secret) { + *md5secret++ = '\0'; + authname = strchr(md5secret, ':'); + } + + if (authname) { + *authname++ = '\0'; + transportname = strchr(authname, ':'); + } + + if (transportname) { + *transportname++ = '\0'; + + if (!strcasecmp(transportname, "tcp")) { + transport = AST_TRANSPORT_TCP; + } else if (!strcasecmp(transportname, "tls")) { + transport = AST_TRANSPORT_TLS; + } else { + if (strcasecmp(transportname, "udp")) { + ast_log(LOG_WARNING, "'%s' is not a valid transport option to Dial() for SIP calls, using udp by default.\n", transportname); + } + + transport = AST_TRANSPORT_UDP; + } + } else { /* use default */ + transport = AST_TRANSPORT_UDP; + } + + if (!host) { + exten = args.exten; + host = args.peerorhost; + remote_address = args.remote_address; + } else { + remote_address = args.remote_address; + + if (!ast_strlen_zero(args.exten)) { + ast_log(LOG_NOTICE, "Conflicting extension values given. Using '%s' and not '%s'\n", exten, args.exten); + } + } + + if (!ast_strlen_zero(remote_address)) { + pvt->options->outboundproxy = sip_proxy_build(remote_address, 0, NULL); + + if (!pvt->options->outboundproxy) { + ast_log(LOG_WARNING, "Unable to parse outboundproxy %s. We will not use this remote IP address\n", remote_address); + } + } + + sip_socket_set_transport(&pvt->socket, transport); + + /* We now have: host = peer name, DNS host name or DNS domain (for SRV). ext = extension (user part of URI). dnid = destination of + * the call (applies to the To: header) */ + if (sip_pvt_build(pvt, host, NULL, TRUE)) { + ast_debug(3, "Cant create SIP call - target device not registered\n"); + + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "unref pvt UNREGISTERED"); + + *cause = AST_CAUSE_UNREGISTERED; + + return NULL; + } + + if (ast_strlen_zero(pvt->peername) && exten) { + ast_string_field_set(pvt, peername, exten); + } + + /* Recalculate our side, and recalculate Call ID */ + sip_pvt_set_ouraddrfor(pvt, &pvt->sa, &pvt->ourip); + /* 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_pvt_check_for_nat(pvt, &pvt->sa); + + /* 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 (pvt->relatedpeer) { + if (!ast_strlen_zero(pvt->relatedpeer->fullcontact) && !pvt->natdetected && + ((ast_test_flag(&pvt->flags[2], SIP_NAT_AUTO_RPORT) && !ast_test_flag(&pvt->flags[0], SIP_NAT_FORCE_RPORT)) || + (ast_test_flag(&pvt->flags[2], SIP_NAT_AUTO_COMEDIA) && !ast_test_flag(&pvt->flags[1], SIP_SYMMETRICRTP)))) { + /* 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 tmpaddr; + + sip_parse_contact(pvt->relatedpeer->fullcontact, &tmpaddr); + sip_pvt_check_for_nat(pvt, &tmpaddr); + } + + sip_pvt_set_nat(pvt, pvt->relatedpeer); + } + + sip_pvt_set_rtp_nat(pvt); + sip_pvt_build_via(pvt); + + /* Change the dialog callid. */ + sip_pvt_change_callid(pvt, NULL); + + /* 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 (exten) { + ast_string_field_set(pvt, username, exten); + ast_string_field_set(pvt, fullcontact, NULL); + } + + if (secret && !ast_strlen_zero(secret)) { + ast_string_field_set(pvt, peersecret, secret); + } + + if (md5secret && !ast_strlen_zero(md5secret)) { + ast_string_field_set(pvt, peermd5secret, md5secret); + } + + if (authname && !ast_strlen_zero(authname)) { + ast_string_field_set(pvt, authname, authname); + } + + ast_format_cap_append_from_cap(pvt->prefcaps, cap, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_get_compatible(cap, pvt->caps, pvt->jointcaps); + + ao2_lock(pvt); + chan = sip_pvt_channel_alloc(pvt, AST_STATE_DOWN, host, assignedids, requestor, callid); /* Place the call */ + ao2_unlock(pvt); + + if (!chan) { + sip_pvt_unlink(pvt); + } else { + ast_channel_unlock(chan); + } + + ao2_t_cleanup(pvt, "toss pvt ptr at end of sip_requester"); + + ast_update_use_count(); + sip_monitor_restart(); + + return chan; +} + +/* Initiate SIP call from PBX */ +int sip_call(struct ast_channel *chan, const char *dest, int timeout) +{ + int res; + struct sip_pvt *pvt; + struct ast_var_t *var; + char uri[SIP_BUFFER_SIZE] = ""; + + /* chan is locked, so the reference cannot go away */ + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_log(LOG_WARNING, "Asked call channel %s without tech pvt; ignoring\n", ast_channel_name(chan)); + return -1; + } + + if (ast_channel_state(chan) != AST_STATE_DOWN && ast_channel_state(chan) != AST_STATE_RESERVED) { + ast_log(LOG_WARNING, "sip_call called on %s, neither down nor reserved\n", ast_channel_name(chan)); + return -1; + } + + if (pvt->donotdisturb && ast_test_flag(&pvt->flags[2], SIP_DND_BUSY)) { + ast_queue_control(pvt->owner, AST_CONTROL_BUSY); + return 0; + } else if (!ast_strlen_zero(pvt->callforward)) { + ast_channel_call_forward_set(chan, pvt->callforward); + ast_queue_control(pvt->owner, AST_CONTROL_BUSY); + + return 0; + } + + AST_LIST_TRAVERSE(ast_channel_varshead(chan), var, entries) { + if (!pvt->options->uri_options && !strcmp(ast_var_name(var), "SIP_URI_OPTIONS")) { + pvt->options->uri_options = ast_var_value(var); + } else if (!pvt->options->addsipheaders && !strncmp(ast_var_name(var), "SIPADDHEADER", 12)) { + /* Check whether there is a variable with a name starting with SIPADDHEADER */ + pvt->options->addsipheaders = TRUE; + } else if (!strcmp(ast_var_name(var), "SIPFROMDOMAIN")) { + ast_string_field_set(pvt, fromdomain, ast_var_value(var)); + } else if (!strcmp(ast_var_name(var), "SIPTRANSFER_REPLACES")) { + /* We're replacing a call. */ + pvt->options->replaces = ast_var_value(var); + } else if (!strcmp(ast_var_name(var), "SIP_MAX_FORWARDS")) { + if (sscanf(ast_var_value(var), "%30d", &pvt->maxforwards) != 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 (pvt->req_secure_signaling && pvt->socket.type != AST_TRANSPORT_TLS) { + ast_log(LOG_WARNING, "Encrypted signaling is required\n"); + ast_channel_hangupcause_set(chan, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); + + return -1; + } + + if (ast_test_flag(&pvt->flags[0], SIP_USE_SRTP)) { + if (ast_test_flag(&pvt->flags[0], SIP_REINVITE)) { + ast_debug(1, "Direct media not possible when using SRTP, ignoring canreinvite setting\n"); + ast_clear_flag(&pvt->flags[0], SIP_REINVITE); + } + + if (pvt->rtp && !pvt->srtp && !(pvt->srtp = ast_sdp_srtp_alloc())) { + ast_log(LOG_WARNING, "SRTP audio setup failed\n"); + return -1; + } + + if (pvt->vrtp && !pvt->vsrtp && !(pvt->vsrtp = ast_sdp_srtp_alloc())) { + ast_log(LOG_WARNING, "SRTP video setup failed\n"); + return -1; + } + + if (pvt->trtp && !pvt->tsrtp && !(pvt->tsrtp = ast_sdp_srtp_alloc())) { + ast_log(LOG_WARNING, "SRTP text setup failed\n"); + return -1; + } + } + + res = 0; + ast_set_flag(&pvt->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(&pvt->flags[1], SIP_FAX_DETECT_T38); + + ast_debug(1, "Outgoing Call for %s\n", pvt->username); + res = sip_pvt_update_call_counter(pvt, SIP_INC_CALL_RINGING); + + if (res == -1) { + ast_channel_hangupcause_set(chan, AST_CAUSE_USER_BUSY); + return res; + } + + pvt->callingpres = ast_party_id_presentation(&ast_channel_caller(chan)->id); + + ast_rtp_instance_available_formats(pvt->rtp, pvt->caps, pvt->prefcaps, pvt->jointcaps); + pvt->jointnoncodeccapability = pvt->noncodeccapability; + + /* If there are no formats left to offer, punt */ + if (ast_format_cap_empty(pvt->jointcaps)) { + ast_log(LOG_WARNING, "No format found to offer. Cancelling call to %s\n", pvt->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(pvt->caps) || ast_format_cap_has_type(pvt->caps, AST_MEDIA_TYPE_AUDIO)) && + (ast_format_cap_empty(pvt->prefcaps) || ast_format_cap_has_type(pvt->prefcaps, AST_MEDIA_TYPE_AUDIO)) && + !ast_format_cap_has_type(pvt->jointcaps, AST_MEDIA_TYPE_AUDIO)) { + ast_log(LOG_WARNING, "No audio format found to offer. Cancelling call to %s\n", pvt->username); + res = -1; + } else { + struct ast_party_connected_line connected; + struct ast_set_party_connected_line update_connected; + + ao2_lock(pvt); + + /* Supply initial connected line information if available. */ + memset(&update_connected, 0, sizeof(update_connected)); + ast_party_connected_line_init(&connected); + + if (!ast_strlen_zero(pvt->cid_num) || (pvt->callingpres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + update_connected.id.number = TRUE; + connected.id.number.valid = TRUE; + connected.id.number.str = (char *) pvt->cid_num; + connected.id.number.presentation = pvt->callingpres; + } + + if (!ast_strlen_zero(pvt->cid_name) || (pvt->callingpres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + update_connected.id.name = TRUE; + connected.id.name.valid = TRUE; + connected.id.name.str = (char *) pvt->cid_name; + connected.id.name.presentation = pvt->callingpres; + } + + if (update_connected.id.number || update_connected.id.name) { + /* Invalidate any earlier private connected id representation */ + ast_set_party_id_all(&update_connected.priv); + + connected.id.tag = (char *) pvt->cid_tag; + connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; + ast_channel_queue_connected_line_update(chan, &connected, &update_connected); + } + + if (sip_send_invite(pvt, SIP_INVITE, TRUE, SIP_INIT_REQUEST, uri) == -1) { + ao2_unlock(pvt); + return -1; + } + + pvt->invitestate = SIP_INVITE_CALLING; + + /* Initialize auto-congest time */ + AST_SCHED_REPLACE_UNREF(pvt->initid, sip_sched_context, pvt->timer_b, sip_pvt_auto_congest, pvt, + ao2_t_cleanup(_data, "dialog ptr dec when SCHED_REPLACE del op succeeded"), + ao2_t_cleanup(pvt, "dialog ptr dec when SCHED_REPLACE add failed"), + ao2_t_bump(pvt, "dialog ptr inc when SCHED_REPLACE add succeeded")); + + ao2_unlock(pvt); + } + + return res; +} + +/* Hangup SIP call */ +int sip_hangup(struct ast_channel *chan) +{ + struct sip_pvt *pvt; + struct ast_channel *oldowner = chan; + int needcancel = FALSE; + int needdestroy = FALSE; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_log(LOG_WARNING, "Asked hangup channel %s without tech pvt; ignoring\n", ast_channel_name(chan)); + return 0; + } + + if (ast_channel_hangupcause(chan) == AST_CAUSE_ANSWERED_ELSEWHERE) { + ast_debug(1, "This call was answered elsewhere\n"); + sip_history_append(pvt, "Cancel", "Call answered elsewhere"); + + pvt->answered_elsewhere = TRUE; + } + + /* Store hangupcause locally in PVT so we still have it before disconnect */ + if (pvt->owner) { + pvt->hangupcause = ast_channel_hangupcause(pvt->owner); + } + + if (ast_test_flag(&pvt->flags[0], SIP_DEFER_BYE_ON_TRANSFER)) { + if (ast_test_flag(&pvt->flags[0], SIP_INC_COUNT) || ast_test_flag(&pvt->flags[1], SIP_CALL_ONHOLD)) { + if (sip_debug) { + ast_debug(1, "%s: decrement call limit counter on hangup\n", pvt->username); + } + + sip_pvt_update_call_counter(pvt, SIP_DEC_CALL_LIMIT); + } + + ast_debug(4, "SIP Transfer: Not hanging up right now... Rescheduling hangup for %s.\n", pvt->callid); + + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + ast_clear_flag(&pvt->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Really hang up next time */ + + if (pvt->owner) { + ao2_lock(pvt); + oldowner = pvt->owner; + + sip_pvt_set_owner(pvt, NULL); /* Owner will be gone after we return, so take it away */ + ao2_unlock(pvt); + + ao2_t_cleanup(ast_channel_tech_pvt(oldowner), "unref oldowner->tech_pvt"); + ast_channel_tech_pvt_set(oldowner, NULL); + } + + sip_module_unref(); + return 0; + } + + ast_debug(1, "Hangup call %s, SIP callid %s\n", ast_channel_name(chan), pvt->callid); + + ao2_lock(pvt); + + if (ast_test_flag(&pvt->flags[0], SIP_INC_COUNT) || ast_test_flag(&pvt->flags[1], SIP_CALL_ONHOLD)) { + if (sip_debug) { + ast_debug(1, "%s: decrement call limit counter on hangup\n", pvt->username); + } + + sip_pvt_update_call_counter(pvt, SIP_DEC_CALL_LIMIT); + + if (!ast_test_flag(&pvt->flags[0], SIP_INC_COUNT) && pvt->relatedpeer) { + ao2_lock(pvt->relatedpeer); + sip_selected_destroy_all(pvt->relatedpeer); + ao2_unlock(pvt->relatedpeer); + } + } + + /* Determine how to disconnect */ + if (pvt->owner != chan) { + ast_log(LOG_WARNING, "Huh? We aren't the owner? Can't hangup call.\n"); + ao2_unlock(pvt); + + 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 (pvt->invitestate < SIP_INVITE_COMPLETED && ast_channel_state(pvt->owner) != AST_STATE_UP) { + needcancel = TRUE; + ast_debug(4, "Hanging up channel in state %s (not UP)\n", ast_state2str(ast_channel_state(chan))); + } + + sip_pvt_stop_rtp(pvt); /* Immediately stop RTP, VRTP and UDPTL as applicable */ + sip_history_append(pvt, needcancel ? "Cancel" : "Hangup", "Cause %s", ast_cause2str(pvt->hangupcause)); + + /* Disconnect */ + sip_pvt_set_dsp_detect(pvt, FALSE); + + sip_pvt_set_owner(pvt, NULL); + ast_channel_tech_pvt_set(chan, NULL); + + sip_module_unref(); + + /* Do not destroy this pvt until we have timeout or get an answer to the BYE or INVITE/CANCEL. If we get no answer during + * retransmit period, drop the call anyway. (Sorry, mother-in-law, you can't deny a hangup by sending 603 declined to BYE...) */ + if (pvt->alreadygone) { + needdestroy = TRUE; /* Set destroy flag at end of this function */ + } else if (pvt->invitestate != SIP_INVITE_CALLING) { + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + } + + /* Start the process if it's not already started */ + if (!pvt->alreadygone && pvt->initreq.data && ast_str_strlen(pvt->initreq.data)) { + if (needcancel) { /* Outgoing call, not up */ + if (ast_test_flag(&pvt->flags[0], SIP_OUTGOING)) { + /* if we can't send right now, mark it pending */ + if (pvt->invitestate == SIP_INVITE_CALLING) { + /* We can't send anything in CALLING state */ + ast_set_flag(&pvt->flags[0], SIP_PENDINGBYE); + + /* 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_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + sip_history_append(pvt, "DELAY", "Not sending cancel, waiting for timeout"); + } else { + struct sip_packet *packet; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&pvt->packet_queue, packet, next) { + sip_packet_semi_ack(pvt, packet->seqno, packet->is_resp, + packet->method ? packet->method : sip_method_find(ast_str_buffer(packet->data))); + } + AST_LIST_TRAVERSE_SAFE_END; + + pvt->invitestate = SIP_INVITE_CANCELLED; + + /* Send a new request: CANCEL */ + sip_send_request(pvt, SIP_CANCEL, pvt->lastinvite, SIP_SEND_RELIABLE, FALSE); + + /* 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. */ + needdestroy = FALSE; + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + } + } else { /* Incoming call, not up */ + const char *res; + + sip_pvt_cancel_provisional_keepalive(pvt); + + if (ast_test_flag(&pvt->flags[1], SIP_TRANSFER_RESPONSE)) { + sip_send_response_reliable(pvt, "500 Internal Server Error", &pvt->initreq); + } else if (pvt->hangupcause && (res = sip_cause2hangup(pvt->hangupcause))) { + sip_send_response_reliable(pvt, res, &pvt->initreq); + } else { + sip_send_response_reliable(pvt, "603 Declined", &pvt->initreq); + } + + pvt->invitestate = SIP_INVITE_TERMINATED; + } + } else { /* Call is in UP state, send BYE */ + if (pvt->stimer) { + sip_stimer_stop(pvt); + } + + if (!pvt->pendinginvite) { + char *quality; + char buf[AST_MAX_USER_FIELD]; + + if (pvt->rtp) { + struct ast_rtp_instance *rtp = pvt->rtp; + + ao2_ref(rtp, +1); + ast_channel_unlock(oldowner); + + ao2_unlock(pvt); + ast_rtp_instance_set_stats_vars(oldowner, rtp); + + ao2_ref(rtp, -1); + ast_channel_lock(oldowner); + + ao2_lock(pvt); + } + + /* The channel variables are set below just to get the AMI VarSet event because the channel is being hungup. */ + if (pvt->rtp || pvt->vrtp || pvt->trtp) { + ast_channel_stage_snapshot(oldowner); + } + + if (pvt->rtp && (quality = ast_rtp_instance_get_quality(pvt->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, buf, sizeof(buf)))) { + if (pvt->recordhistory) { + sip_history_append(pvt, "RTCPaudio", "Quality:%s", quality); + } + + pbx_builtin_setvar_helper(oldowner, "RTPAUDIOQOS", quality); + } + + if (pvt->vrtp && (quality = ast_rtp_instance_get_quality(pvt->vrtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, buf, sizeof(buf)))) { + if (pvt->recordhistory) { + sip_history_append(pvt, "RTCPvideo", "Quality:%s", quality); + } + + pbx_builtin_setvar_helper(oldowner, "RTPVIDEOQOS", quality); + } + + if (pvt->trtp && (quality = ast_rtp_instance_get_quality(pvt->trtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, buf, sizeof(buf)))) { + if (pvt->recordhistory) { + sip_history_append(pvt, "RTCPtext", "Quality:%s", quality); + } + + pbx_builtin_setvar_helper(oldowner, "RTPTEXTQOS", quality); + } + + if (pvt->rtp || pvt->vrtp || pvt->trtp) { + ast_channel_stage_snapshot_done(oldowner); + } + + /* Send a hangup */ + if (ast_channel_state(oldowner) == AST_STATE_UP) { + sip_send_request_with_auth(pvt, SIP_BYE, 0, SIP_SEND_RELIABLE, TRUE); + } + } 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(&pvt->flags[0], SIP_PENDINGBYE); + ast_clear_flag(&pvt->flags[0], SIP_NEEDREINVITE); + + sip_pvt_stop_reinvite_retry(pvt); + sip_pvt_cancel_destroy(pvt); + + /* 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 (pvt->ongoing_reinvite && pvt->reinviteid < 0) { + pvt->reinviteid = ast_sched_add(sip_sched_context, 32 * pvt->timer_t1, + sip_pvt_reinvite_timeout, ao2_t_bump(pvt, "Schedule reinviteid")); + if (pvt->reinviteid < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(pvt, "Failed to schedule reinviteid"); + } + } + } + } + } + + if (needdestroy) { + sip_pvt_set_need_destroy(pvt, "hangup"); + } + + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "unref ast->tech_pvt"); + + return 0; +} + +/* Answer SIP call, send 200 OK on Invite */ +int sip_answer(struct ast_channel *chan) +{ + int res = 0; + struct sip_pvt *pvt; + int oldsdp = FALSE; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_log(LOG_WARNING, "Asked to answer channel %s without tech pvt; ignoring\n", ast_channel_name(chan)); + return res; + } + + ao2_lock(pvt); + + if (ast_channel_state(chan) != AST_STATE_UP) { + sip_pvt_try_suggested_codec(pvt); + + if (ast_test_flag(&pvt->flags[0], SIP_PROGRESS_SENT)) { + oldsdp = TRUE; + } + + ast_setstate(chan, AST_STATE_UP); + + ast_debug(1, "SIP answering channel: %s\n", ast_channel_name(chan)); + + ast_rtp_instance_update_source(pvt->rtp); + + res = sip_send_response_with_sdp(pvt, "200 OK", &pvt->initreq, SIP_SEND_CRITICAL, oldsdp, TRUE); + ast_set_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED); + + /* RFC says the session timer starts counting on 200, not on INVITE. */ + if (pvt->stimer) { + sip_stimer_restart(pvt); + } + } + + ao2_unlock(pvt); + + return res; +} + +/* Read SIP RTP from channel */ +struct ast_frame *sip_read(struct ast_channel *chan) +{ + struct ast_frame *frame; + struct sip_pvt *pvt; + int faxdetected = FALSE; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_log(LOG_WARNING, "Asked to read from channel %s without tech pvt; ignoring\n", ast_channel_name(chan)); + return &ast_null_frame; + } + + ao2_lock(pvt); + + frame = sip_read_rtp(chan, pvt, &faxdetected); + pvt->lastrtprx = time(NULL); + + /* If we detect a CNG tone and fax detection is enabled then send us off to the fax extension */ + if (faxdetected && ast_test_flag(&pvt->flags[1], SIP_FAX_DETECT_CNG)) { + if (strcmp(ast_channel_exten(chan), "fax")) { + const char *target_context = ast_channel_context(chan); + + /* 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(pvt); + ast_channel_unlock(chan); + + ast_frfree(frame); + frame = &ast_null_frame; + + if (ast_exists_extension(chan, target_context, "fax", 1, + S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { + ast_verb(2, "Redirecting '%s' to fax extension due to CNG detection\n", ast_channel_name(chan)); + pbx_builtin_setvar_helper(chan, "FAXEXTEN", ast_channel_exten(chan)); + + if (ast_async_goto(chan, target_context, "fax", 1)) { + ast_log(LOG_NOTICE, "Failed to async goto '%s' into fax of '%s'\n", ast_channel_name(chan), target_context); + } + } else { + ast_log(LOG_NOTICE, "FAX CNG detected but no fax extension\n"); + } + + ast_channel_lock(chan); + ao2_lock(pvt); + } + } + + /* Only allow audio through if they sent progress with SDP, or if the channel is actually answered */ + if (frame && frame->frametype == AST_FRAME_VOICE && pvt->invitestate != SIP_INVITE_EARLY_MEDIA && ast_channel_state(chan) != AST_STATE_UP) { + ast_frfree(frame); + frame = &ast_null_frame; + } + + ao2_unlock(pvt); + + return frame; +} + +/* Read RTP from network */ +static struct ast_frame *sip_read_rtp(struct ast_channel *chan, struct sip_pvt *pvt, int *faxdetect) +{ + /* Retrieve audio/etc from channel. Assumes p->lock is already held. */ + struct ast_frame *frame; + + if (!pvt->rtp) { + /* We have no RTP allocated for this channel */ + return &ast_null_frame; + } + + switch (ast_channel_fdno(chan)) { + case 0: + frame = ast_rtp_instance_read(pvt->rtp, 0); /* RTP Audio */ + break; + case 1: + frame = ast_rtp_instance_read(pvt->rtp, 1); /* RTCP Control Channel */ + break; + case 2: + frame = ast_rtp_instance_read(pvt->vrtp, 0); /* RTP Video */ + break; + case 3: + frame = ast_rtp_instance_read(pvt->vrtp, 1); /* RTCP Control Channel for video */ + break; + case 4: + frame = ast_rtp_instance_read(pvt->trtp, 0); /* RTP Text */ + break; + case 5: + frame = ast_udptl_read(pvt->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(&pvt->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 (!pvt->owner || (frame && frame->frametype != AST_FRAME_VOICE)) { + return frame; + } + + if (frame && ast_format_cap_iscompatible_format(ast_channel_nativeformats(pvt->owner), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { + struct ast_format_cap *caps; + + if (ast_format_cap_iscompatible_format(pvt->jointcaps, 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(pvt->owner)); + ast_frfree(frame); + + return &ast_null_frame; + } + + ast_debug(1, "Oooh, format changed to %s\n", ast_format_get_name(frame->subclass.format)); + + if ((caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + ast_format_cap_append_from_cap(caps, ast_channel_nativeformats(pvt->owner), AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_AUDIO); + + ast_format_cap_append(caps, frame->subclass.format, 0); + ast_channel_nativeformats_set(pvt->owner, caps); + + ao2_ref(caps, -1); + } + + ast_set_read_format(pvt->owner, ast_channel_readformat(pvt->owner)); + ast_set_write_format(pvt->owner, ast_channel_writeformat(pvt->owner)); + } + + if (frame && pvt->dsp) { + frame = ast_dsp_process(pvt->owner, pvt->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(chan)); + *faxdetect = TRUE; + + /* If we only needed this DSP for fax detection purposes we can just drop it now */ + if (ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) { + ast_dsp_set_features(pvt->dsp, DSP_FEATURE_DIGIT_DETECT); + } else { + ast_dsp_free(pvt->dsp); + pvt->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 *chan, struct ast_frame *frame) +{ + struct sip_pvt *pvt; + int res = 0; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_log(LOG_WARNING, "Asked to write to channel %s without tech pvt; ignoring\n", ast_channel_name(chan)); + return res; + } + + switch (frame->frametype) { + case AST_FRAME_VOICE: + if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(chan), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { + struct ast_str *codec_buf = 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(chan), &codec_buf), + ast_format_get_name(ast_channel_readformat(chan)), + ast_format_get_name(ast_channel_writeformat(chan))); + + return 0; + } + + if (pvt) { + ao2_lock(pvt); + + if (pvt->t38.state == SIP_T38_ENABLED) { + /* drop frame, can't sent VOICE frames while in T.38 mode */ + ao2_unlock(pvt); + break; + } else if (pvt->rtp) { + /* If channel is not up, activate early media session */ + if ((ast_channel_state(chan) != AST_STATE_UP) && + !ast_test_flag(&pvt->flags[0], SIP_PROGRESS_SENT) && + !ast_test_flag(&pvt->flags[0], SIP_OUTGOING)) { + ast_rtp_instance_update_source(pvt->rtp); + + if (!sip_config.prematuremediafilter) { + pvt->invitestate = SIP_INVITE_EARLY_MEDIA; + + sip_send_response_provisional(pvt, "183 Session Progress", &pvt->initreq, TRUE); + ast_set_flag(&pvt->flags[0], SIP_PROGRESS_SENT); + } + } + + if (pvt->invitestate > SIP_INVITE_EARLY_MEDIA || + (pvt->invitestate == SIP_INVITE_EARLY_MEDIA && ast_test_flag(&pvt->flags[0], SIP_PROGRESS_SENT))) { + pvt->lastrtptx = time(NULL); + res = ast_rtp_instance_write(pvt->rtp, frame); + } + } + + ao2_unlock(pvt); + } + + break; + case AST_FRAME_VIDEO: + if (pvt) { + ao2_lock(pvt); + + if (pvt->vrtp) { + /* Activate video early media */ + if ((ast_channel_state(chan) != AST_STATE_UP) && + !ast_test_flag(&pvt->flags[0], SIP_PROGRESS_SENT) && + !ast_test_flag(&pvt->flags[0], SIP_OUTGOING)) { + pvt->invitestate = SIP_INVITE_EARLY_MEDIA; + + sip_send_response_provisional(pvt, "183 Session Progress", &pvt->initreq, TRUE); + ast_set_flag(&pvt->flags[0], SIP_PROGRESS_SENT); + } + + if (pvt->invitestate > SIP_INVITE_EARLY_MEDIA || + (pvt->invitestate == SIP_INVITE_EARLY_MEDIA && ast_test_flag(&pvt->flags[0], SIP_PROGRESS_SENT))) { + pvt->lastrtptx = time(NULL); + res = ast_rtp_instance_write(pvt->vrtp, frame); + } + } + + ao2_unlock(pvt); + } + + break; + case AST_FRAME_TEXT: + if (pvt) { + ao2_lock(pvt); + + if (pvt->red) { + ast_rtp_red_buffer(pvt->trtp, frame); + } else { + if (pvt->trtp) { + /* Activate text early media */ + if ((ast_channel_state(chan) != AST_STATE_UP) && + !ast_test_flag(&pvt->flags[0], SIP_PROGRESS_SENT) && + !ast_test_flag(&pvt->flags[0], SIP_OUTGOING)) { + pvt->invitestate = SIP_INVITE_EARLY_MEDIA; + + sip_send_response_provisional(pvt, "183 Session Progress", &pvt->initreq, TRUE); + ast_set_flag(&pvt->flags[0], SIP_PROGRESS_SENT); + } + + if (pvt->invitestate > SIP_INVITE_EARLY_MEDIA || + (pvt->invitestate == SIP_INVITE_EARLY_MEDIA && ast_test_flag(&pvt->flags[0], SIP_PROGRESS_SENT))) { + pvt->lastrtptx = time(NULL); + res = ast_rtp_instance_write(pvt->trtp, frame); + } + } + } + + ao2_unlock(pvt); + } + + break; + case AST_FRAME_IMAGE: + return 0; + break; + case AST_FRAME_MODEM: + if (pvt) { + ao2_lock(pvt); + + /* 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(chan) == AST_STATE_UP && pvt->udptl && pvt->t38.state == SIP_T38_ENABLED) { + res = ast_udptl_write(pvt->udptl, frame); + } + + ao2_unlock(pvt); + } + break; + default: + ast_log(LOG_WARNING, "Can't 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 *oldchan, struct ast_channel *newchan) +{ + struct sip_pvt *pvt; + int ret = -1; + + if (newchan && ast_test_flag(ast_channel_flags(newchan), AST_FLAG_ZOMBIE)) { + ast_debug(1, "New channel is zombie\n"); + } + + if (oldchan && ast_test_flag(ast_channel_flags(oldchan), AST_FLAG_ZOMBIE)) { + ast_debug(1, "Old channel is zombie\n"); + } + + if (!newchan || !(pvt = ast_channel_tech_pvt(newchan))) { + if (!newchan) { + ast_log(LOG_WARNING, "No new channel! Fixup of %s failed.\n", ast_channel_name(oldchan)); + } else { + ast_log(LOG_WARNING, "No SIP tech_pvt! Fixup of %s failed.\n", ast_channel_name(oldchan)); + } + + return -1; + } + + ao2_lock(pvt); + + sip_history_append(pvt, "Masq", "Old channel: %s\n", ast_channel_name(oldchan)); + sip_history_append(pvt, "Masq (cont)", "...new owner: %s\n", ast_channel_name(newchan)); + + if (pvt->owner != oldchan) { + ast_log(LOG_WARNING, "old channel wasn't %p but was %p\n", oldchan, pvt->owner); + } else { + sip_pvt_set_owner(pvt, newchan); + + /* 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(newchan, NULL, NULL, NULL, NULL, 0); + ret = 0; + } + + ast_debug(3, "New owner for dialogue %s: %s (Old parent: %s)\n", pvt->callid, ast_channel_name(pvt->owner), ast_channel_name(oldchan)); + ao2_unlock(pvt); + + return ret; +} + +int sip_send_digit_begin(struct ast_channel *chan, char digit) +{ + struct sip_pvt *pvt; + int res = 0; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_log(LOG_WARNING, "Asked to begin DTMF digit on channel %s with no pvt; ignoring\n", ast_channel_name(chan)); + return res; + } + + ao2_lock(pvt); + + switch (ast_test_flag(&pvt->flags[0], SIP_DTMF)) { + case SIP_DTMF_INBAND: + res = -1; /* Tell Asterisk to generate inband indications */ + break; + case SIP_DTMF_RFC2833: + if (pvt->rtp) { + ast_rtp_instance_dtmf_begin(pvt->rtp, digit); + } + + break; + default: + break; + } + + ao2_unlock(pvt); + + 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 *chan, char digit, unsigned int duration) +{ + struct sip_pvt *pvt; + int res = 0; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_log(LOG_WARNING, "Asked to end DTMF digit on channel %s with no pvt; ignoring\n", ast_channel_name(chan)); + return res; + } + + ao2_lock(pvt); + + switch (ast_test_flag(&pvt->flags[0], SIP_DTMF)) { + case SIP_DTMF_RFC2833: + if (pvt->rtp) { + ast_rtp_instance_dtmf_end_with_duration(pvt->rtp, digit, duration); + } + + break; + case SIP_DTMF_INBAND: + res = -1; /* Tell Asterisk to stop inband indications */ + break; + } + + ao2_unlock(pvt); + + return res; +} + +/* Send SIP MESSAGE text within a call. Called from PBX core sendtext() application */ +int sip_send_text(struct ast_channel *chan, const char *text) +{ + struct sip_pvt *pvt; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_log(LOG_WARNING, "Asked to send text on channel %s with no pvt; ignoring\n", ast_channel_name(chan)); + return 0; + } + + /* NOT ast_strlen_zero, because a zero-length message is specifically allowed by RFC 3428 (See section 10, Examples) */ + if (!text) { + return 0; + } + + if (!sip_method_allowed(&pvt->allowed_methods, SIP_MESSAGE)) { + ast_debug(2, "Trying to send MESSAGE to device that does not support it.\n"); + return 0; + } + + if (sip_debug_test_pvt(pvt)) { + ast_verb(3, "Sending text \"%s\" on %s\n", text, ast_channel_name(chan)); + } + + /* Setup to send text message */ + ao2_lock(pvt); + + sip_msg_free_headers(pvt); + ast_string_field_set(pvt, msg_body, text); + + sip_send_message(pvt, FALSE, FALSE); + ao2_unlock(pvt); + + return 0; +} + +/* Set an option on a SIP dialog */ +int sip_setoption(struct ast_channel *chan, int option, void *data, int datalen) +{ + int res = -1; + struct sip_pvt *pvt; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_log(LOG_WARNING, "Asked to set option on channel %s with no pvt; ignoring\n", ast_channel_name(chan)); + return res; + } + + ao2_lock(pvt); + + switch (option) { + case AST_OPTION_FORMAT_READ: + if (pvt->rtp) { + res = ast_rtp_instance_set_read_format(pvt->rtp, *(struct ast_format **) data); + } + + break; + case AST_OPTION_FORMAT_WRITE: + if (pvt->rtp) { + res = ast_rtp_instance_set_write_format(pvt->rtp, *(struct ast_format **) data); + } + break; + case AST_OPTION_DIGIT_DETECT: + if (ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_INBAND || ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + char *cp = (char *) data; + + ast_debug(1, "%sabling digit detection on %s\n", *cp ? "En" : "Dis", ast_channel_name(chan)); + + sip_pvt_set_dsp_detect(pvt, *cp ? TRUE : FALSE); + res = 0; + } + break; + case AST_OPTION_SECURE_SIGNALING: + pvt->req_secure_signaling = *(unsigned int *) data; + res = 0; + + break; + case AST_OPTION_SECURE_MEDIA: + ast_set2_flag(&pvt->flags[0], *(unsigned int *) data, SIP_USE_SRTP); + res = 0; + + break; + default: + break; + } + + ao2_unlock(pvt); + + return res; +} + +/* Query an option on a SIP dialog */ +int sip_queryoption(struct ast_channel *chan, int option, void *data, int *datalen) +{ + int res = -1; + enum ast_t38_state state = T38_STATE_UNAVAILABLE; + struct sip_pvt *pvt; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_log(LOG_WARNING, "Asked to query option on channel %s with no pvt; ignoring\n", ast_channel_name(chan)); + return res; + } + + ao2_lock(pvt); + + switch (option) { + case AST_OPTION_T38_STATE: + /* Make sure we got an ast_t38_state enum passed in */ + if (*datalen != sizeof(enum ast_t38_state)) { + ast_log(LOG_ERROR, "Invalid datalen for AST_OPTION_T38_STATE option. Expected %d, got %d\n", (int) sizeof(enum ast_t38_state), *datalen); + 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(&pvt->flags[1], SIP_T38SUPPORT)) { + switch (pvt->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; + } + } + + *((enum ast_t38_state *) data) = state; + res = 0; + + break; + case AST_OPTION_DIGIT_DETECT: + *((char *) data) = pvt->dsp ? TRUE : FALSE; + + ast_debug(1, "Reporting digit detection %sabled on %s\n", pvt->dsp ? "en" : "dis", ast_channel_name(chan)); + + break; + case AST_OPTION_SECURE_SIGNALING: + *((unsigned int *) data) = pvt->req_secure_signaling; + res = 0; + + break; + case AST_OPTION_SECURE_MEDIA: + *((unsigned int *) data) = !!ast_test_flag(&pvt->flags[0], SIP_USE_SRTP); + res = 0; + + break; + case AST_OPTION_DEVICE_NAME: + if (pvt && pvt->outgoing_call) { + ast_copy_string((char *) data, pvt->dialstring, *datalen); + res = 0; + } + + /* We purposely break with a return of -1 in the implied else case here */ + break; + default: + break; + } + + ao2_unlock(pvt); + + return res; +} + +/* Send message with Access-URL header, if this is an HTML URL only! */ +int sip_send_html(struct ast_channel *chan, int subclass, const char *data, int datalen) +{ + struct sip_pvt *pvt; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_log(LOG_WARNING, "Asked to send html on channel %s with no pvt; ignoring\n", ast_channel_name(chan)); + return -1; + } + + if (subclass != AST_HTML_URL) { + return -1; + } + + ast_string_field_build(pvt, url, "<%s>;mode=active", data); + + if (sip_debug_test_pvt(pvt)) { + ast_debug(1, "Send URL %s\n", data); + } + + switch (ast_channel_state(chan)) { + case AST_STATE_RING: + sip_send_response(pvt, "100 Trying", &pvt->initreq); + break; + case AST_STATE_RINGING: + sip_send_response(pvt, "180 Ringing", &pvt->initreq); + break; + case AST_STATE_UP: + if (!pvt->pendinginvite) { /* We are up, and have no outstanding invite */ + sip_send_reinvite_with_sdp(pvt, FALSE, FALSE); + } else if (!ast_test_flag(&pvt->flags[0], SIP_PENDINGBYE)) { + ast_set_flag(&pvt->flags[0], SIP_NEEDREINVITE); + } + + break; + default: + ast_log(LOG_WARNING, "Don't know how to send URI when state is %u\n", ast_channel_state(chan)); + break; + } + + return 0; +} + +/* Transfer SIP call */ +int sip_transfer(struct ast_channel *chan, const char *dest) +{ + struct sip_pvt *pvt = ast_channel_tech_pvt(chan); + int res = 0; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_log(LOG_WARNING, "Asked to transfer channel %s with no pvt; ignoring\n", ast_channel_name(chan)); + return -1; + } + + if (dest == NULL) { /* functions below do not take a NULL */ + dest = ""; + } + + ao2_lock(pvt); + + /* 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(chan) == AST_STATE_RING) { + char *user, *domain; + + user = ast_strdupa(dest); + user = strsep(&domain, "@"); + + if (ast_strlen_zero(user)) { + ast_log(LOG_ERROR, "Missing mandatory argument: extension\n"); + ao2_unlock(pvt); + + return 0; + } + + /* we'll issue the redirect message here */ + if (!domain) { + char *to, *sep; + + to = ast_strdupa(sip_request_get_header(&pvt->initreq, "To")); + + if (ast_strlen_zero(to)) { + ast_log(LOG_ERROR, "Cannot retrieve the 'To' header from the original SIP request!\n"); + res = -1; + } + + if (((sep = strcasestr(to, "sip:")) || (sep = strcasestr(to, "sips:"))) && (sep = strchr(to, '@'))) { + char tmp[256]; + + memset(tmp, 0, sizeof(tmp)); + /* Will copy no more than 255 chars plus null terminator. */ + sscanf(sep + 1, "%255[^<>; ]", tmp); + + if (ast_strlen_zero(tmp)) { + ast_log(LOG_ERROR, "Can't find the host address\n"); + ao2_unlock(pvt); + + return 0; + } + + domain = ast_strdupa(tmp); + } + } + + ast_string_field_build(pvt, our_contact, "", user, domain); + sip_send_response_reliable(pvt, "302 Moved Temporarily", &pvt->initreq); + + sip_pvt_sched_destroy(pvt, SIP_TIMEOUT); /* Make sure we stop send this reply. */ + sip_pvt_set_already_gone(pvt); + + if (pvt->owner) { + enum ast_control_transfer message = AST_TRANSFER_SUCCESS; + + ast_queue_control_data(pvt->owner, AST_CONTROL_TRANSFER, &message, sizeof(message)); + } + } else { + res = sip_send_refer(pvt, dest); + } + + ao2_unlock(pvt); + + 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 *chan, int condition, const void *data, size_t datalen) +{ + struct sip_pvt *pvt; + int res = 0; + struct ast_aoc_decoded *decoded; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_log(LOG_WARNING, "Asked to indicate condition on channel %s with no pvt; ignoring\n", ast_channel_name(chan)); + return res; + } + + ao2_lock(pvt); + + switch (condition) { + case AST_CONTROL_RINGING: + if (ast_channel_state(chan) == AST_STATE_RING) { + pvt->invitestate = SIP_INVITE_EARLY_MEDIA; + + if (!ast_test_flag(&pvt->flags[0], SIP_PROGRESS_SENT) || ast_test_flag(&pvt->flags[0], SIP_PROG_INBAND) == SIP_PROG_INBAND_NEVER) { + /* Send 180 ringing if out-of-band seems reasonable */ + sip_send_response_provisional(pvt, "180 Ringing", &pvt->initreq, FALSE); + ast_set_flag(&pvt->flags[0], SIP_RINGING); + + if (ast_test_flag(&pvt->flags[0], SIP_PROG_INBAND) != SIP_PROG_INBAND_YES) { + break; + } + } + } + + res = -1; + break; + case AST_CONTROL_BUSY: + if (ast_channel_state(chan) != AST_STATE_UP) { + sip_send_response_reliable(pvt, "486 Busy Here", &pvt->initreq); + pvt->invitestate = SIP_INVITE_COMPLETED; + + sip_pvt_set_already_gone(pvt); + ast_softhangup_nolock(chan, AST_SOFTHANGUP_DEV); + + break; + } + + res = -1; + break; + case AST_CONTROL_CONGESTION: + if (ast_channel_state(chan) != AST_STATE_UP) { + sip_send_response_reliable(pvt, "503 Service Unavailable", &pvt->initreq); + pvt->invitestate = SIP_INVITE_COMPLETED; + + sip_pvt_set_already_gone(pvt); + ast_softhangup_nolock(chan, AST_SOFTHANGUP_DEV); + + break; + } + + res = -1; + break; + case AST_CONTROL_INCOMPLETE: + if (ast_channel_state(chan) != AST_STATE_UP) { + switch (ast_test_flag(&pvt->flags[1], SIP_ALLOWOVERLAP)) { + case SIP_ALLOWOVERLAP_YES: + sip_send_response_reliable(pvt, "484 Address Incomplete", &pvt->initreq); + pvt->invitestate = SIP_INVITE_COMPLETED; + + sip_pvt_set_already_gone(pvt); + ast_softhangup_nolock(chan, AST_SOFTHANGUP_DEV); + + break; + case SIP_ALLOWOVERLAP_DTMF: + /* Just wait for inband DTMF digits */ + break; + default: + /* it actually means no support for overlap */ + sip_send_response_reliable(pvt, "404 Not Found", &pvt->initreq); + pvt->invitestate = SIP_INVITE_COMPLETED; + + sip_pvt_set_already_gone(pvt); + ast_softhangup_nolock(chan, AST_SOFTHANGUP_DEV); + break; + } + } + + break; + case AST_CONTROL_PROCEEDING: + if (ast_channel_state(chan) != AST_STATE_UP && + !ast_test_flag(&pvt->flags[0], SIP_PROGRESS_SENT) && !ast_test_flag(&pvt->flags[0], SIP_OUTGOING)) { + sip_send_response(pvt, "100 Trying", &pvt->initreq); + pvt->invitestate = SIP_INVITE_PROCEEDING; + + break; + } + + res = -1; + break; + case AST_CONTROL_PROGRESS: + if (ast_channel_state(chan) != AST_STATE_UP && + !ast_test_flag(&pvt->flags[0], SIP_PROGRESS_SENT) && !ast_test_flag(&pvt->flags[0], SIP_OUTGOING)) { + pvt->invitestate = SIP_INVITE_EARLY_MEDIA; + + /* SIP_PROG_INBAND_NEVER means sending 180 ringing in place of a 183 */ + if (ast_test_flag(&pvt->flags[0], SIP_PROG_INBAND) != SIP_PROG_INBAND_NEVER) { + sip_send_response_provisional(pvt, "183 Session Progress", &pvt->initreq, TRUE); + ast_set_flag(&pvt->flags[0], SIP_PROGRESS_SENT); + } else if (ast_channel_state(chan) == AST_STATE_RING && !ast_test_flag(&pvt->flags[0], SIP_RINGING)) { + sip_send_response_provisional(pvt, "180 Ringing", &pvt->initreq, FALSE); + ast_set_flag(&pvt->flags[0], SIP_RINGING); + } + + break; + } + + res = -1; + break; + case AST_CONTROL_HOLD: + ast_rtp_instance_update_source(pvt->rtp); + ast_moh_start(chan, data, pvt->mohinterpret); + + break; + case AST_CONTROL_UNHOLD: + ast_rtp_instance_update_source(pvt->rtp); + ast_moh_stop(chan); + + break; + case AST_CONTROL_VIDUPDATE: /* Request a video frame update */ + if (pvt->vrtp && !pvt->novideo) { + /* 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(chan), 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(pvt->vrtp, &frame); + } else { + sip_send_info_with_vidupdate(pvt); + } + } else { + res = -1; + } + + break; + case AST_CONTROL_T38_PARAMETERS: + res = -1; + + if (datalen != sizeof(struct ast_control_t38_parameters)) { + ast_log(LOG_ERROR, "Invalid datalen for AST_CONTROL_T38_PARAMETERS. Expected %d, got %d\n", + (int) sizeof(struct ast_control_t38_parameters), (int) datalen); + } else { + const struct ast_control_t38_parameters *parms = data; + + if (!sip_pvt_setup_udptl(pvt)) { + res = sip_pvt_update_t38_capabilities(pvt, parms); + } + } + + break; + case AST_CONTROL_SRCUPDATE: + ast_rtp_instance_update_source(pvt->rtp); + break; + case AST_CONTROL_SRCCHANGE: + ast_rtp_instance_change_source(pvt->rtp); + break; + case AST_CONTROL_CONNECTED_LINE: + sip_pvt_update_connected_line(pvt, data, datalen); + break; + case AST_CONTROL_REDIRECTING: + sip_pvt_update_redirecting(pvt, data, datalen); + break; + case AST_CONTROL_AOC: + if (!(decoded = ast_aoc_decode((struct ast_aoc_encoded *) data, datalen, chan))) { + ast_log(LOG_ERROR, "Error decoding indicated AOC data\n"); + res = -1; + + break; + } + + switch (ast_aoc_get_msg_type(decoded)) { + case AST_AOC_REQUEST: + if (ast_aoc_get_termination_request(decoded)) { + /* 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(pvt->owner)); + ast_softhangup_nolock(pvt->owner, 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); + 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(pvt); + + 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 *data) +{ + char *host, *sep; + struct sip_peer *peer; + int res = AST_DEVICE_INVALID; + + /* make sure data is not null. Maybe unnecessary, but better be safe */ + host = ast_strdupa(data ? data : ""); + + if ((sep = strchr(host, '@'))) { + host = sep + 1; + } + + ast_debug(3, "Checking device state for peer %s\n", host); + + /* 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(host, NULL, FALSE, TRUE, 0))) { + /* We have an address for the peer */ + if (!ast_sockaddr_isnull(&peer->addr) || !ast_sockaddr_isnull(&peer->defaddr)) { + /* 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->maxms && (peer->lastms > peer->maxms || peer->lastms < 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, "ao2_t_cleanup, from sip_devicestate, release ref from sip_peer_find"); + } + + return res; +} + +int sip_presencestate(const char *data, char **subtype, char **message) +{ + char *host, *sep; + struct sip_peer *peer; + int res = AST_PRESENCE_INVALID; + + /* make sure data is not null. Maybe unnecessary, but better be safe */ + host = ast_strdupa(data ? data : ""); + + if ((sep = strchr(host, '@'))) { + host = sep + 1; + } + + ast_debug(3, "Checking presence state for peer %s\n", host); + + if ((peer = sip_peer_find(host, NULL, FALSE, TRUE, 0))) { + if (!ast_sockaddr_isnull(&peer->addr) || !ast_sockaddr_isnull(&peer->defaddr)) { + if (peer->donotdisturb) { + res = AST_PRESENCE_DND; + } else { + res = AST_PRESENCE_AVAILABLE; + } + } + + ao2_t_cleanup(peer, "ao2_t_cleanup, from sip_presencestate, release ref from sip_peer_find"); + } + + return res; +} + +/* Deliver SIP call ID for the call */ +const char *sip_get_pvt_uniqueid(struct ast_channel *chan) +{ + struct sip_pvt *pvt = ast_channel_tech_pvt(chan); + + return pvt ? pvt->callid : ""; +} + +int sip_sipinfo_send(struct ast_channel *chan, struct ast_variable *headers, const char *content_type, + const char *content, const char *useragent_filter) +{ + struct sip_pvt *pvt; + struct ast_variable *var; + struct sip_request req; + int res = -1; + + ast_channel_lock(chan); + + if (ast_channel_tech(chan) != &sip_tech) { + ast_log(LOG_WARNING, "Attempted to send a custom INFO on a non-SIP channel %s\n", ast_channel_name(chan)); + ast_channel_unlock(chan); + return res; + } + + pvt = ast_channel_tech_pvt(chan); + ao2_lock(pvt); + + if (!(ast_strlen_zero(useragent_filter))) { + if (strstr(pvt->useragent, useragent_filter)) { + goto cleanup; + } + } + + sip_request_prepare(&req, pvt, SIP_INFO, 0, TRUE); + + for (var = headers; var; var = var->next) { + sip_request_add_header(&req, var->name, var->value); + } + + if (!ast_strlen_zero(content) && !ast_strlen_zero(content_type)) { + sip_request_add_header(&req, "Content-Type", content_type); + sip_request_add_content(&req, content); + } + + res = sip_request_send(pvt, &req, SIP_SEND_RELIABLE, pvt->ocseq); + +cleanup: + ao2_unlock(pvt); + ast_channel_unlock(chan); + + return res; +} diff -durN asterisk-22.2.0.orig/channels/sip/cli.c asterisk-22.2.0/channels/sip/cli.c --- asterisk-22.2.0.orig/channels/sip/cli.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/cli.c 2025-02-18 17:14:46.784903284 +1300 @@ -0,0 +1,2275 @@ +/* + * 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.h" +#include "include/domain.h" +#include "include/peers.h" +#include "include/registry.h" +#include "include/pvt.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_channelstats, "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_donotdisturb, "Enables/Disables do not disturb on a SIP peer"), + AST_CLI_DEFINE(sip_cli_callforward, "Sets/Removes the call forwarding extension for a SIP peer"), + AST_CLI_DEFINE(sip_cli_huntgroup, "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_callid(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) +{ + char *result = NULL; + int wordlen = strlen(word); + int which = 0; + struct ao2_iterator iter; + struct sip_peer *peer; + + iter = ao2_iterator_init(sip_peers, 0); + + while ((peer = ao2_t_iterator_next(&iter, "iterate thru peers table"))) { + /* locking of the object is not required because only the name and flags are being compared */ + if (!strncasecmp(word, peer->name, wordlen) && + (!flags || ast_test_flag(&peer->flags[1], flags)) && + ++which > state) { + result = ast_strdup(peer->name); + } + + ao2_t_cleanup(peer, "toss iterator peer ptr before break"); + + if (result) { + break; + } + } + + ao2_iterator_destroy(&iter); + + return result; +} + +/* 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_callid(const char *line, const char *word, int pos, int state) +{ + int which = 0; + struct sip_pvt *pvt; + char *callid = NULL; + int wordlen = strlen(word); + struct ao2_iterator iter; + + if (pos != 3) { + return NULL; + } + + iter = ao2_iterator_init(sip_pvts, 0); + + while ((pvt = ao2_t_iterator_next(&iter, "iterate thru dialogs"))) { + ao2_lock(pvt); + + if (!strncasecmp(word, pvt->callid, wordlen) && ++which > state) { + callid = ast_strdup(pvt->callid); + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "drop ref in iterator loop break"); + + break; + } + + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "drop ref in iterator loop"); + } + + ao2_iterator_destroy(&iter); + + return callid; +} + +/* Support routine for 'sip notify' CLI */ +static char *sip_cli_complete_notify(const char *line, const char *word, int pos, int state) +{ + int which = 0; + char *category = NULL; + int wordlen = strlen(word); + + 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, wordlen) && ++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_pvt *pvt; + size_t len; + int found = 0; + 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_callid(args->line, args->word, args->pos, args->n); + } + + if (args->argc != 4) { + return CLI_SHOWUSAGE; + } + + len = strlen(args->argv[3]); + iter = ao2_iterator_init(sip_pvts, 0); + + while ((pvt = ao2_t_iterator_next(&iter, "iterate thru dialogs"))) { + ao2_lock(pvt); + + if (!strncasecmp(pvt->callid, args->argv[3], len)) { + struct ast_str *path; + struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + const char *subtype; + + ast_cli(args->fd, "\n"); + + if (pvt->subscribed != SIP_SUBSCRIBED_NONE) { + switch (pvt->subscribed) { + case SIP_SUBSCRIBED_DIALOG_INFO_XML: + subtype = "dialog-info"; + break; + case SIP_SUBSCRIBED_PIDF_XML: + subtype = "pidf-xml"; + break; + case SIP_SUBSCRIBED_MESSAGE_SUMMARY: + subtype = "mwi"; + break; + case SIP_SUBSCRIBED_FEATURE_EVENTS: + subtype = "feature-events"; + break; + default: + subtype = ""; + break; + } + + ast_cli(args->fd, " Type: Subscription (%s)\n", subtype); + } else { + ast_cli(args->fd, " Type: Call\n"); + } + + ast_cli(args->fd, " Curr. Trans. Direction: %s\n", ast_test_flag(&pvt->flags[0], SIP_OUTGOING) ? "Outgoing" : "Incoming"); + ast_cli(args->fd, " Call-ID: %s\n", pvt->callid); + ast_cli(args->fd, " Owner channel ID: %s\n", pvt->owner ? ast_channel_name(pvt->owner) : ""); + ast_cli(args->fd, " Our Codec Capability: %s\n", ast_format_cap_get_names(pvt->caps, &codec_buf)); + ast_cli(args->fd, " Non-Codec Capability (DTMF): %d\n", pvt->noncodeccapability); + ast_cli(args->fd, " Their Codec Capability: %s\n", ast_format_cap_get_names(pvt->peercaps, &codec_buf)); + ast_cli(args->fd, " Joint Codec Capability: %s\n", ast_format_cap_get_names(pvt->jointcaps, &codec_buf)); + ast_cli(args->fd, " Format: %s\n", pvt->owner ? ast_format_cap_get_names(ast_channel_nativeformats(pvt->owner), &codec_buf) : "(nothing)" ); + ast_cli(args->fd, " T.38 support %s\n", AST_CLI_YESNO(pvt->udptl != NULL)); + ast_cli(args->fd, " Video support %s\n", AST_CLI_YESNO(pvt->vrtp != NULL)); + ast_cli(args->fd, " MaxCallBR: %d kbps\n", pvt->maxcallbitrate); + ast_cli(args->fd, " Theoretical Address: %s\n", ast_sockaddr_stringify(&pvt->sa)); + ast_cli(args->fd, " Received Address: %s\n", ast_sockaddr_stringify(&pvt->recv)); + ast_cli(args->fd, " Allow Transfer: %s\n", AST_CLI_YESNO(pvt->allowtransfer)); + ast_cli(args->fd, " Force Rport: %s\n", sip_force_rport2str(pvt->flags)); + + if (ast_sockaddr_isnull(&pvt->redirip)) { + ast_cli(args->fd, + " Audio IP: %s (local)\n", ast_sockaddr_stringify_addr(&pvt->ourip)); + } else { + ast_cli(args->fd, + " Audio IP: %s (Outside bridge)\n", ast_sockaddr_stringify_addr(&pvt->redirip)); + } + + ast_cli(args->fd, " Our Tag: %s\n", pvt->tag); + ast_cli(args->fd, " Their Tag: %s\n", pvt->theirtag); + ast_cli(args->fd, " SIP User agent: %s\n", pvt->useragent); + + if (!ast_strlen_zero(pvt->username)) { + ast_cli(args->fd, " Username: %s\n", pvt->username); + } + + if (!ast_strlen_zero(pvt->peername)) { + ast_cli(args->fd, " Peername: %s\n", pvt->peername); + } + + if (!ast_strlen_zero(pvt->uri)) { + ast_cli(args->fd, " Original URI: %s\n", pvt->uri); + } + + if (!ast_strlen_zero(pvt->cid_num)) { + ast_cli(args->fd, " Caller-ID: %s\n", pvt->cid_num); + } + + ast_cli(args->fd, " Need Destroy: %s\n", AST_CLI_YESNO(pvt->needdestroy)); + ast_cli(args->fd, " Last Message: %s\n", pvt->lastmsg); + ast_cli(args->fd, " Promiscuous Redir: %s\n", AST_CLI_YESNO(ast_test_flag(&pvt->flags[0], SIP_PROMISCREDIR))); + + if ((path = sip_route_list(&pvt->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(&pvt->flags[0], SIP_DTMF))); + ast_cli(args->fd, " SIP Options: "); + + if (pvt->sipoptions) { + int i; + + for (i = 0 ; sip_options[i].id != SIP_OPT_UNKNOWN; i++) { + if (pvt->sipoptions & sip_options[i].id) { + ast_cli(args->fd, "%s ", sip_options[i].name); + } + } + + ast_cli(args->fd, "\n"); + } else { + ast_cli(args->fd, "(none)\n"); + } + + if (!pvt->stimer) { + ast_cli(args->fd, " Session-Timer: Uninitiallized\n"); + } else { + ast_cli(args->fd, " Session-Timer: %s\n", pvt->stimer->active ? "Active" : "Inactive"); + + if (pvt->stimer->active) { + ast_cli(args->fd, " S-Timer Interval: %d\n", pvt->stimer->interval); + ast_cli(args->fd, " S-Timer Refresher: %s\n", sip_stimer_refresher2str(pvt->stimer->refresher)); + ast_cli(args->fd, " S-Timer Sched Id: %d\n", pvt->stimer->schedid); + ast_cli(args->fd, " S-Timer Peer Sts: %s\n", pvt->stimer->peer_active ? "Active" : "Inactive"); + ast_cli(args->fd, " S-Timer Cached Min-SE: %d\n", pvt->stimer->cached_min_se); + ast_cli(args->fd, " S-Timer Cached SE: %d\n", pvt->stimer->cached_max_se); + ast_cli(args->fd, " S-Timer Cached Ref: %s\n", sip_stimer_refresher2str(pvt->stimer->cached_refresher)); + ast_cli(args->fd, " S-Timer Cached Mode: %s\n", sip_stimer_mode2str(pvt->stimer->cached_mode)); + } + } + + /* add transport and media types */ + ast_cli(args->fd, " Transport: %s\n", ast_transport2str(pvt->socket.type)); + ast_cli(args->fd, " Media: %s\n", pvt->srtp ? "SRTP" : pvt->rtp ? "RTP" : "None"); + + ast_cli(args->fd, "\n"); + found++; + } + + ao2_unlock(pvt); + ao2_t_ref(pvt, -1, "toss dialog ptr set by iterator_next"); + } + + 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 *varlist; + struct ast_channel *chan; + struct ast_str *str; + 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; + } + + varlist = ast_variable_browse(sip_notify_types, args->argv[2]); + + if (!varlist) { + ast_cli(args->fd, "Unable to find notify type '%s'\n", args->argv[2]); + return CLI_FAILURE; + } + + if (!(chan = ast_dummy_channel_alloc())) { + ast_cli(args->fd, "Cannot allocate the channel for variables substitution\n"); + return CLI_FAILURE; + } + + if (!(str = ast_str_create(32))) { + ast_channel_release(chan); + ast_cli(args->fd, "Cannot allocate the string for variables substitution\n"); + + return CLI_FAILURE; + } + + for (i = 3; i < args->argc; i++) { + struct sip_pvt *pvt; + char buf[512]; + struct ast_variable *var, *header, *headers = NULL; + + if (!(pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_NOTIFY, NULL, 0))) { + ast_log(LOG_WARNING, "Unable to build sip pvt data for notify (memory/socket error)\n"); + return CLI_FAILURE; + } + + if (sip_pvt_build(pvt, args->argv[i], NULL, TRUE)) { + /* Maybe they're not registered, etc. */ + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "unref dialog inside for loop" ); + /* sip_destroy(p); */ + ast_cli(args->fd, "Could not create address for '%s'\n", args->argv[i]); + + continue; + } + + /* Notify is outgoing call */ + ast_set_flag(&pvt->flags[0], SIP_OUTGOING); + sip_notify_alloc(pvt); + + /* Recalculate our side, and recalculate Call ID */ + sip_pvt_set_ouraddrfor(pvt, &pvt->sa, &pvt->ourip); + sip_pvt_change_callid(pvt, NULL); + + /* Set the name of the peer being sent the notification so it can be used in ${} functions */ + pbx_builtin_setvar_helper(chan, "PEERNAME", pvt->peername); + + for (var = varlist; var; var = var->next) { + ast_copy_string(buf, var->value, sizeof(buf)); + ast_unescape_semicolon(buf); + ast_str_substitute_variables(&str, 0, chan, buf); + + if (!strcasecmp(var->name, "Content")) { + if (ast_str_strlen(pvt->notify->content)) { + ast_str_append(&pvt->notify->content, 0, "\r\n"); + } + + ast_str_append(&pvt->notify->content, 0, "%s", ast_str_buffer(str)); + } else if (!strcasecmp(var->name, "Content-Length")) { + ast_log(LOG_WARNING, "it is not necessary to specify Content-Length in sip_notify.conf, ignoring\n"); + } else { + header = ast_variable_new(var->name, ast_str_buffer(str), ""); + + if (headers) { + headers->next = header; + } else { + pvt->notify->headers = header; + } + + headers = header; + } + } + + ast_cli(args->fd, "Sending NOTIFY of type '%s' to '%s'\n", args->argv[2], args->argv[i]); + + sip_pvt_sched_destroy(pvt, SIP_TIMEOUT); + sip_send_invite(pvt, SIP_NOTIFY, FALSE, SIP_INIT_REQUEST, NULL); + + ao2_t_cleanup(pvt, "bump down the count of pvt since we're done with it."); + } + + ast_channel_release(chan); + ast_free(str); + + return CLI_SUCCESS; +} + +/* Enable/Disable DoNotDisturb on a peer */ +char *sip_cli_donotdisturb(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_peer *peer; + struct sip_alias *alias; + int donotdisturb; + + 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")) { + donotdisturb = TRUE; + } else if (!strcasecmp(args->argv[2], "off")) { + donotdisturb = FALSE; + } else { + return CLI_SHOWUSAGE; + } + + if (!(peer = sip_peer_find(args->argv[3], NULL, TRUE, FALSE, 0))) { + 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, donotdisturb ? "enabled" : "disabled"); + + peer->donotdisturb = donotdisturb; + 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->donotdisturb = peer->donotdisturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", alias->peer->name); + } + } + + if (!peer->is_realtime) { + ast_db_put("SIP/DoNotDisturb", peer->name, donotdisturb ? "yes" : "no"); + } else if (sip_config.peer_rtupdate && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name, "donotdisturb", donotdisturb ? "yes" : "no", SENTINEL); + } + + sip_peer_send_donotdisturb(peer); + ao2_t_cleanup(peer, "unref after sip_peer_find"); + + return CLI_SUCCESS; +} + +/* Login to/Logout from huntgroup for a peer */ +char *sip_cli_huntgroup(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_peer *peer; + struct sip_alias *alias; + int huntgroup; + + 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")) { + huntgroup = TRUE; + } else if (!strcasecmp(args->argv[2], "off")) { + huntgroup = FALSE; + } else { + return CLI_SHOWUSAGE; + } + + if (!(peer = sip_peer_find(args->argv[3], NULL, TRUE, FALSE, 0))) { + 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", huntgroup ? "login" : "logout", peer->name); + peer->huntgroup = huntgroup; + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (alias->peer) { + alias->peer->huntgroup = peer->huntgroup; + } + } + + if (!peer->is_realtime) { + ast_db_put("SIP/HuntGroup", peer->name, huntgroup ? "yes" : "no"); + } else if (sip_config.peer_rtupdate && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name, "huntgroup", huntgroup ? "yes" : "no", SENTINEL); + } + + sip_peer_send_huntgroup(peer); + ao2_t_cleanup(peer, "unref after sip_peer_find"); + + return CLI_SUCCESS; +} + +/* Sets/Removes the call fowarding extension for a peer */ +char *sip_cli_callforward(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_peer *peer; + const char *callforward; + + 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; + } + + callforward = args->argv[4]; + } else if (!strcasecmp(args->argv[2], "off")) { + callforward = ""; + } else { + return CLI_SHOWUSAGE; + } + + if (!(peer = sip_peer_find(args->argv[3], NULL, TRUE, FALSE, 0))) { + ast_cli(args->fd, "No such peer '%s'\n", args->argv[3]); + return CLI_FAILURE; + } + + if (ast_strlen_zero(callforward)) { + 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, callforward); + } + + ast_string_field_set(peer, callforward, callforward); + + if (!peer->is_realtime) { + if (ast_strlen_zero(peer->callforward)) { + ast_db_del("SIP/CallForward", peer->name); + } else { + ast_db_put("SIP/CallForward", peer->name, callforward); + } + } else if (sip_config.peer_rtupdate && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name, "callforward", peer->callforward, SENTINEL); + } + + sip_peer_send_callforward(peer); + ao2_t_cleanup(peer, "unref after sip_peer_find"); + + 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_pvt *pvt; + size_t len; + int found = 0; + 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_callid(args->line, args->word, args->pos, args->n); + } + + if (args->argc != 4) { + return CLI_SHOWUSAGE; + } + + if (!sip_config.recordhistory) { + ast_cli(args->fd, "\n***Note: History recording is currently DISABLED. Use 'sip set history on' to ENABLE.\n"); + } + + len = strlen(args->argv[3]); + iter = ao2_iterator_init(sip_pvts, 0); + + while ((pvt = ao2_t_iterator_next(&iter, "iterate thru dialogs"))) { + ao2_lock(pvt); + + if (!strncasecmp(pvt->callid, args->argv[3], len)) { + struct sip_history *history; + int i = 0; + + ast_cli(args->fd, "\n"); + + if (pvt->subscribed != SIP_SUBSCRIBED_NONE) { + ast_cli(args->fd, " Subscription\n"); + } else { + ast_cli(args->fd, " Call\n"); + } + + if (pvt->history) { + AST_LIST_TRAVERSE(pvt->history, history, next) + ast_cli(args->fd, "%d. %s\n", ++i, history->event); + } + + if (i == 0) { + ast_cli(args->fd, "Call '%s' has no history\n", pvt->callid); + + } + + found++; + } + + ao2_unlock(pvt); + ao2_t_ref(pvt, -1, "toss dialog ptr from iterator_next"); + } + + 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 oldsip_debug = sip_debug & SIP_DEBUG_CONSOLE; + + 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; + } + + if (args->argc == entry->args) { /* on/off */ + if (!strcasecmp(args->argv[3], "on")) { + sip_debug |= SIP_DEBUG_CONSOLE; + memset(&sip_debugaddr, 0, sizeof(sip_debugaddr)); + + ast_cli(args->fd, "SIP Debugging %senabled\n", oldsip_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_debugaddr, args->argv[4], 0, 0)) { + return CLI_SHOWUSAGE; + } + + ast_cli(args->fd, "SIP Debugging Enabled for IP: %s\n", ast_sockaddr_stringify_addr(&sip_debugaddr)); + sip_debug |= SIP_DEBUG_CONSOLE; + } else if (!strcasecmp(args->argv[3], "peer")) { + struct sip_peer *peer = sip_peer_find(args->argv[4], NULL, TRUE, FALSE, 0); + + if (!peer) { + ast_cli(args->fd, "No such peer '%s'\n", args->argv[4]); + } else if (ast_sockaddr_isnull(&peer->addr)) { + ast_cli(args->fd, "Unable to get IP address of peer '%s'\n", args->argv[4]); + } else { + ast_sockaddr_copy(&sip_debugaddr, &peer->addr); + + ast_cli(args->fd, "SIP Debugging Enabled for IP: %s\n", ast_sockaddr_stringify_addr(&sip_debugaddr)); + sip_debug |= SIP_DEBUG_CONSOLE; + } + + if (peer) { + ao2_t_cleanup(peer, "sip_cli_set_debug: ao2_t_cleanup, from sip_peer_find call"); + } + } + + 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.recordhistory = TRUE; + ast_cli(args->fd, "SIP History Recording Enabled (use 'sip show history')\n"); + } else if (!strcasecmp(args->argv[3], "off")) { + sip_config.recordhistory = 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 prunepeer = FALSE; + int multi = FALSE; + const char *name = NULL; + regex_t regexbuf; + int havepattern = 0; + struct ao2_iterator iter; + + if (cmd == CLI_INIT) { + entry->command = "sip prune realtime [peer|all]"; + entry->usage = "Usage: sip prune realtime [peer [|all|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 == 4 && !strcasecmp(args->argv[3], "peer")) { + const char *choices[] = {"all", "like", NULL}; + char *result = ast_cli_complete(args->word, choices, args->n); + + if (!result) { + result = sip_cli_complete_peer(args->word, args->n - sizeof(choices), SIP_RTCACHEFRIENDS); + } + + return result; + } + + if (args->pos == 5 && !strcasecmp(args->argv[4], "like")) { + return sip_cli_complete_peer(args->word, args->n, SIP_RTCACHEFRIENDS); + } + + return NULL; + } + + if (args->argc == 4) { + name = args->argv[3]; + + /* we accept a name in position 3, but keywords are not good. */ + if (!strcasecmp(name, "peer") || !strcasecmp(name, "like")) { + return CLI_SHOWUSAGE; + } + + prunepeer = TRUE; + + if (!strcasecmp(name, "all")) { + multi = TRUE; + name = NULL; + } + } else if (args->argc == 5) { + /* sip prune realtime {peer|like} name */ + name = args->argv[4]; + + if (!strcasecmp(args->argv[3], "peer")) { + prunepeer = TRUE; + } else if (!strcasecmp(args->argv[3], "like")) { + prunepeer = TRUE; + multi = TRUE; + } else { + return CLI_SHOWUSAGE; + } + + if (!strcasecmp(name, "like")) { + return CLI_SHOWUSAGE; + } + + if (!multi && !strcasecmp(name, "all")) { + multi = TRUE; + name = NULL; + } + } else if (args->argc == 6) { + name = args->argv[5]; + multi = TRUE; + + /* sip prune realtime {peer} like name */ + if (strcasecmp(args->argv[4], "like")) { + return CLI_SHOWUSAGE; + } + + if (!strcasecmp(args->argv[3], "peer")) { + prunepeer = TRUE; + } else { + return CLI_SHOWUSAGE; + } + } else { + return CLI_SHOWUSAGE; + } + + if (multi && name) { + if (regcomp(®exbuf, name, REG_EXTENDED | REG_NOSUB)) { + return CLI_SHOWUSAGE; + } + + havepattern = TRUE; + } + + if (multi) { + if (prunepeer) { + int pruned = 0; + + iter = ao2_iterator_init(sip_peers, 0); + + while ((peer = ao2_t_iterator_next(&iter, "iterate thru peers table"))) { + ao2_lock(peer); + + if (name && regexec(®exbuf, peer->name, 0, NULL, 0)) { + ao2_unlock(peer); + ao2_t_cleanup(peer, "toss iterator peer ptr before continue"); + + continue; + }; + + if (ast_test_flag(&peer->flags[1], SIP_RTCACHEFRIENDS)) { + peer->removed = TRUE; + pruned++; + } + + ao2_unlock(peer); + ao2_t_cleanup(peer, "toss iterator peer ptr"); + } + + ao2_iterator_destroy(&iter); + + if (pruned) { + sip_peer_unlink_all(TRUE); + ast_cli(args->fd, "%d peers pruned.\n", pruned); + } else { + ast_cli(args->fd, "No peers found to prune.\n"); + } + } + } else { + if (prunepeer) { + struct sip_peer tmp_peer = {.name = name}; + + if ((peer = ao2_t_find(sip_peers, &tmp_peer, OBJ_POINTER | OBJ_UNLINK, "finding to unlink from peers"))) { + if (!ast_sockaddr_isnull(&peer->addr)) { + ao2_t_unlink(sip_peers_by_addr, peer, "unlinking peer from peers_by_addr also"); + } + + if (!ast_test_flag(&peer->flags[1], SIP_RTCACHEFRIENDS)) { + ast_cli(args->fd, "Peer '%s' is not a Realtime peer, cannot be pruned.\n", name); + /* put it back! */ + ao2_t_link(sip_peers, peer, "link peer into peer table"); + + if (!ast_sockaddr_isnull(&peer->addr)) { + ao2_t_link(sip_peers_by_addr, peer, "link peer into peers_by_addr table"); + } + } else { + ast_cli(args->fd, "Peer '%s' pruned.\n", name); + } + + ao2_t_cleanup(peer, "sip_prune_realtime: ao2_t_cleanup: tossing temp peer ptr"); + } else { + ast_cli(args->fd, "Peer '%s' not found.\n", name); + } + } + } + + if (havepattern) { + regfree(®exbuf); + } + + return CLI_SUCCESS; +} + +char *sip_cli_show_sched(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct ast_str *buf = ast_str_alloca(2048); + struct ast_cb_names cbnames = { + 10, + {"sip_packet_resend", "sip_auto_destruct", "sip_peer_expire_register", "sip_pvt_auto_congest", "sip_registy_timeout", "__sip_peer_qualify", + "sip_peer_qualify_now", "sip_peer_qualify_noanswer", "sip_registry_send", "sip_pvt_start_reinvite_retry"}, + {sip_packet_resend, sip_pvt_auto_destruct, sip_peer_expire_register, sip_pvt_auto_congest, sip_registry_timeout, __sip_peer_qualify, + sip_peer_qualify_now, sip_peer_qualify_noanswer, sip_registry_send, sip_pvt_start_reinvite_retry} + }; + + 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; + } + + ast_cli(args->fd, "\n"); + ast_sched_report(sip_sched_context, &buf, &cbnames); + ast_cli(args->fd, "%s", ast_str_buffer(buf)); + + 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, "iterate through tcp threads for 'sip show tcp'"))) { + ast_cli(args->fd, "%-47.47s %9.9s %6.6s\n", + ast_sockaddr_stringify(&thread->tcptls_session->remote_address), + ast_transport2str(thread->type), + (thread->tcptls_session->client ? "Client" : "Server")); + + ao2_t_ref(thread, -1, "decrement ref from iterator"); + } + + 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], NULL, load_realtime, FALSE, 0))) { + sip_peer_qualify(peer, TRUE); + sip_publish_qualify_peer("", args->argv[3]); + + ao2_t_cleanup(peer, "qualify: done with 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 total = 0, mon_offline = 0, mon_online = 0, unmon_offline = 0, unmon_online = 0; + struct sip_peer **peerlist; + int i; + int realtimepeers; + char name[256]; + char status[30]; + int online; + int havepattern = FALSE; + regex_t regexbuf; + + 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; + } + + if (args->argc == 5) { + if (!strcasecmp(args->argv[3], "like")) { + if (regcomp(®exbuf, args->argv[4], REG_EXTENDED | REG_NOSUB)) { + return CLI_SHOWUSAGE; + } + + havepattern = TRUE; + } else { + return CLI_SHOWUSAGE; + } + } else if (args->argc != 3) { + return CLI_SHOWUSAGE; + } + + realtimepeers = ast_check_realtime("sippeers"); + + ast_cli(args->fd, "%-25.25s %-39.39s %-3.3s %-10.10s %-10.10s %-3.3s %-9s %-11s %-32.32s %s\n", + "Name/Username", "Host", "Dyn", "ForceRport", "Comedia", "ACL", "Port", "Status", "Description", (realtimepeers ? "Realtime" : "")); + + ao2_lock(sip_peers); + + if (!(peerlist = 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); + + while ((peer = ao2_t_iterator_next(&iter, "iterate thru peers table"))) { + ao2_lock(peer); + + if (havepattern && regexec(®exbuf, peer->name, 0, NULL, 0)) { + ao2_unlock(peer); + ao2_t_cleanup(peer, "toss iterator peer ptr before continue"); + + continue; + } + + peerlist[total++] = peer; + ao2_unlock(peer); + } + + ao2_iterator_destroy(&iter); + qsort(peerlist, total, sizeof(peer), sip_cli_peer_cmp); + + for (i = 0; i < total; i++) { + peer = peerlist[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)); + } + + online = sip_peer_get_status(peer, status, sizeof(status)); + + if (online) { + mon_online++; + } else if (!online) { + mon_offline++; + } else { + if (ast_sockaddr_isnull(&peer->addr) || !ast_sockaddr_port(&peer->addr)) { + unmon_offline++; + } else { + unmon_online++; + } + } + + ast_cli(args->fd, "%-25.25s %-39.39s %-3.3s %-10.10s %-10.10s %-3.3s %-9s %-11s %-32.32s %s\n", + name, + !ast_sockaddr_isnull(&peer->addr) ? ast_sockaddr_stringify_addr(&peer->addr) : "(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->addr) ? ast_strdupa(ast_sockaddr_stringify_port(&peer->addr)) : "0", + status, + peer->description ? peer->description : "", + realtimepeers ? (peer->is_realtime ? "Cached RT" : "") : ""); + + ao2_unlock(peer); + ao2_t_cleanup(peer, "toss iterator peer ptr"); + } + + ast_cli(args->fd, "%d sip peer%s. Monitored: %d online, %d offline. Unmonitored: %d online, %d offline\n", + total, ESS(total), mon_online, mon_offline, unmon_online, unmon_offline); + + if (havepattern) { + regfree(®exbuf); + } + + ast_free(peerlist); + + 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 status[30]; + char cbuf[256]; + char group[256]; + struct sip_peer *peer; + struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + struct ast_variable *var; + int load_realtime, realtimepeers; + struct sip_alias *alias; + struct sip_subscription *subscription; + struct ast_str *mailboxes = ast_str_alloca(512); + struct ast_str *namedgroups = ast_str_alloca(1024); + struct ast_str *path; + struct sip_auth_head *peerauth; + + 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) { + static const char * const 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; + } + + realtimepeers = ast_check_realtime("sippeers"); + load_realtime = (args->argc == 5 && !strcmp(args->argv[4], "load")); + + if (!(peer = sip_peer_find(args->argv[3], NULL, load_realtime, FALSE, 0))) { + 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 (realtimepeers) { /* Realtime is enabled */ + ast_cli(args->fd, " Realtime Peer: %s\n", peer->is_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->remotesecret) ? "" : ""); + + if ((peerauth = peer->auth)) { + struct sip_auth *auth; + const char *secret; + + ao2_t_ref(peerauth, +1, "Ref peer auth for show"); + + AST_LIST_TRAVERSE(&peerauth->list, auth, next) { + if (!ast_strlen_zero(auth->secret)) { + secret = ""; + } else if (!ast_strlen_zero(auth->md5secret)) { + secret = ""; + } else { + secret = ""; + } + + ast_cli(args->fd, " Realm-auth: Realm %-15.15s User %-10.20s %s\n", + auth->realm, auth->username, secret); + } + + ao2_t_ref(peerauth, -1, "Unref peer auth for show"); + } + + ast_cli(args->fd, " Context: %s\n", peer->context); + ast_cli(args->fd, " Subscr.Cont.: %s\n", S_OR(peer->subscribecontext, "")); + ast_cli(args->fd, " Language: %s\n", peer->language); + ast_cli(args->fd, " Tonezone: %s\n", peer->zone[0] != '\0' ? 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(peer->allowtransfer)); + ast_cli(args->fd, " CallingPres: %s\n", ast_describe_caller_presentation(peer->callingpres)); + + if (!ast_strlen_zero(peer->fromuser)) { + ast_cli(args->fd, " FromUser: %s\n", peer->fromuser); + } + + if (!ast_strlen_zero(peer->fromdomain)) { + ast_cli(args->fd, " FromDomain: %s Port %d\n", peer->fromdomain, (peer->fromdomainport) ? peer->fromdomainport : 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)); + + 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->mohsuggest); + + 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->vmexten); + ast_cli(args->fd, " LastMsgsSent: %d/%d\n", peer->newmsgs, peer->oldmsgs); + ast_cli(args->fd, " Call Limit: %d\n", peer->call_limit); + ast_cli(args->fd, " Max Forwards: %d\n", peer->maxforwards); + + 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(cbuf, sizeof(cbuf), peer->cid_name, peer->cid_num, "")); + ast_cli(args->fd, " MaxCallBR: %d kbps\n", peer->maxcallbitrate); + ast_cli(args->fd, " Expire: %ld\n", ast_sched_when(sip_sched_context, peer->expire)); + 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, " ContactACL: %s\n", AST_CLI_YESNO(ast_acl_list_is_empty(peer->contactacl) == 0)); + ast_cli(args->fd, " DirectMedACL: %s\n", AST_CLI_YESNO(ast_acl_list_is_empty(peer->directmediaacl) == 0)); + ast_cli(args->fd, " T.38 support: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_T38SUPPORT))); + ast_cli(args->fd, " T.38 EC mode: %s\n", sip_t38_ecmode2str(ast_test_flag(&peer->flags[1], SIP_T38SUPPORT))); + ast_cli(args->fd, " T.38 MaxDtgrm: %u\n", peer->t38_maxdatagram); + 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_PROMISCREDIR))); + ast_cli(args->fd, " User=Phone: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_USEREQPHONE))); + ast_cli(args->fd, " Video Support: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_VIDEOSUPPORT) || ast_test_flag(&peer->flags[1], SIP_VIDEOSUPPORT_ALWAYS))); + ast_cli(args->fd, " Text Support: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_TEXTSUPPORT))); + ast_cli(args->fd, " Ign SDP Ver: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_IGNORESDPVERSION))); + ast_cli(args->fd, " Trust RPID: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_TRUSTRPID))); + ast_cli(args->fd, " Send RPID: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_SENDRPID))); + ast_cli(args->fd, " Path Support: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_USEPATH))); + + 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_ALLOWSUBSCRIBE))); + ast_cli(args->fd, " Overlap Dial: %s\n", sip_allowoverlap2str(ast_test_flag(&peer->flags[1], SIP_ALLOWOVERLAP))); + + if (peer->outboundproxy) { + ast_cli(args->fd, " Outb. Proxy: %s %s\n", + ast_strlen_zero(peer->outboundproxy->host) ? "" : peer->outboundproxy->host, + peer->outboundproxy->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->tohost); + ast_cli(args->fd, " Addr->IP: %s\n", ast_sockaddr_stringify(&peer->addr)); + ast_cli(args->fd, " Defaddr->IP: %s\n", ast_sockaddr_stringify(&peer->defaddr)); + ast_cli(args->fd, " Prim.Transp.: %s\n", ast_transport2str(peer->socket.type)); + ast_cli(args->fd, " Allowed.Trsp: %s\n", sip_transports2str(peer->transports)); + + if (!ast_strlen_zero(sip_config.regcontext)) { + ast_cli(args->fd, " Reg. Exten: %s\n", peer->regexten); + } + + ast_cli(args->fd, " Def. Username: %s\n", peer->username); + ast_cli(args->fd, " SIP Options: "); + + if (peer->sipoptions) { + int i; + + for (i = 0 ; sip_options[i].id != SIP_OPT_UNKNOWN; i++) { + if (peer->sipoptions & sip_options[i].id) { + ast_cli(args->fd, "%s ", sip_options[i].name); + } + } + } else { + ast_cli(args->fd, ""); + } + + ast_cli(args->fd, "\n"); + ast_cli(args->fd, " Codecs: %s\n", ast_format_cap_get_names(peer->caps, &codec_buf)); + ast_cli(args->fd, " Auto-Framing: %s\n", AST_CLI_YESNO(peer->autoframing)); + + sip_peer_get_status(peer, status, sizeof(status)); + + ast_cli(args->fd, " Status: %s\n", status); + ast_cli(args->fd, " Useragent: %s\n", peer->useragent); + ast_cli(args->fd, " Reg. Contact: %s\n", peer->fullcontact); + ast_cli(args->fd, " Qualify Freq: %d ms\n", peer->qualifyfreq); + ast_cli(args->fd, " Keepalive: %d ms\n", peer->keepalive * 1000); + + if (peer->chanvars) { + ast_cli(args->fd, " Variables: "); + + for (var = peer->chanvars; var; var = var->next) { + ast_cli(args->fd, "%s%s = %s\n", var != peer->chanvars ? " " : "", var->name, var->value); + } + } + + ast_cli(args->fd, " Sess-Timers: %s\n", sip_stimer_refresher2str(peer->stimer.mode)); + ast_cli(args->fd, " Sess-Refresh: %s\n", sip_stimer_refresher2str(peer->stimer.refresher)); + ast_cli(args->fd, " Sess-Expires: %d secs\n", peer->stimer.max_se); + ast_cli(args->fd, " Min-Sess: %d secs\n", peer->stimer.min_se); + ast_cli(args->fd, " RTP Engine: %s\n", peer->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->donotdisturb)); + ast_cli(args->fd, " CallFwd Ext.: %s\n", peer->callforward); + ast_cli(args->fd, " Hunt Group: %s\n", AST_CLI_YESNO(peer->huntgroup)); + + 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_devicename); + ast_cli(args->fd, " Active Load: %s\n", peer->cisco_activeload); + ast_cli(args->fd, " Inactive Load: %s\n", peer->cisco_inactiveload); + + 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->lineindex); + } + } + + 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, "sip_show_peer: ao2_t_cleanup: done with peer ptr"); + + 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, "sip_show_mwi iter"))) { + 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, "sip_show_mwi iter"); + } + + 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], regtime[80]; + struct ast_tm tm; + int total = 0; + int len; + 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); + + while ((registry = ao2_t_iterator_next(&iter, "sip_show_registry iter"))) { + 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)) { + len = strlen(user); + snprintf(user + len, sizeof(user) - len, "@%s", registry->domain); + } + + if (registry->domainport) { + len = strlen(user); + snprintf(user + len, sizeof(user) - len, ":%d", registry->domainport); + } + + if (registry->time.tv_sec) { + ast_localtime(®istry->time, &tm, NULL); + ast_strftime(regtime, sizeof(regtime), "%a, %d %b %Y %T", &tm); + } else { + regtime[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->refresh, sip_registry_state2str(registry->state), regtime); + + ao2_unlock(registry); + ao2_t_ref(registry, -1, "sip_show_registry iter"); + + total++; + } + + ao2_iterator_destroy(&iter); + ast_cli(args->fd, "%d SIP registration%s.\n", total, ESS(total)); + + 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; + int load_realtime = 0; + + 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], NULL, load_realtime, TRUE, 0))) { + if (peer->expire > -1) { + AST_SCHED_DEL_UNREF(sip_sched_context, peer->expire, ao2_t_cleanup(peer, "remove register expire ref")); + + sip_peer_expire_register(ao2_t_bump(peer, "ref for sip_peer_expire_register")); + 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, "sip_unregister: ao2_t_cleanup via sip_unregister: done with peer from sip_peer_find call"); + } 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_pvt *pvt; + struct ao2_iterator iter; + int subscriptions; + int numchans = 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_pvts, 0); + + while ((pvt = ao2_iterator_next(&iter))) { + const struct ast_sockaddr *dst; + + ao2_lock(pvt); + dst = sip_pvt_real_dst(pvt); + + if (pvt->subscribed == SIP_SUBSCRIBED_NONE && !subscriptions) { + /* set if SIP transfer in progress */ + struct ast_str *codec_buf = 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(dst), + S_OR(pvt->username, S_OR(pvt->cid_num, "")), + pvt->callid, + pvt->owner ? ast_format_cap_get_names(ast_channel_nativeformats(pvt->owner), &codec_buf) : "", + AST_CLI_YESNO(ast_test_flag(&pvt->flags[1], SIP_CALL_ONHOLD)), + pvt->needdestroy ? "*" : "", + pvt->lastmsg , + pvt->refer ? sip_refer_status2str(pvt->refer->status) : "", + pvt->relatedpeer ? pvt->relatedpeer->name : ""); + + numchans++; + } + + if (pvt->subscribed != SIP_SUBSCRIBED_NONE && subscriptions) { + struct ast_str *mailboxes = ast_str_alloca(512); + char *subtype; + + if (pvt->subscribed == SIP_SUBSCRIBED_MESSAGE_SUMMARY && pvt->relatedpeer) { + sip_peer_get_mailboxes(pvt->relatedpeer, &mailboxes); + } + + switch (pvt->subscribed) { + case SIP_SUBSCRIBED_DIALOG_INFO_XML: + subtype = "dialog-info"; + break; + case SIP_SUBSCRIBED_PIDF_XML: + subtype = "pidf-xml"; + break; + case SIP_SUBSCRIBED_MESSAGE_SUMMARY: + subtype = "mwi"; + break; + case SIP_SUBSCRIBED_FEATURE_EVENTS: + subtype = "feature-events"; + break; + default: + subtype = ""; + 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(dst), + S_OR(pvt->username, S_OR(pvt->cid_num, "")), + pvt->callid, + /* the 'complete' exten/context is hidden in the refer_to field for subscriptions */ + pvt->subscribed == SIP_SUBSCRIBED_DIALOG_INFO_XML || pvt->subscribed == SIP_SUBSCRIBED_PIDF_XML ? pvt->subscribeuri : "--", + pvt->subscribed == SIP_SUBSCRIBED_DIALOG_INFO_XML || pvt->subscribed == SIP_SUBSCRIBED_PIDF_XML ? ast_extension_state2str(pvt->last_exten_state) : "", + subtype, + pvt->subscribed == SIP_SUBSCRIBED_MESSAGE_SUMMARY ? S_OR(ast_str_buffer(mailboxes), "") : "", + pvt->expiry); + + numchans++; + } + + ao2_unlock(pvt); + ao2_ref(pvt, -1); + } + + ao2_iterator_destroy(&iter); + ast_cli(args->fd, "%d active SIP %s%s\n", numchans, (subscriptions ? "subscription" : "dialog"), ESS(numchans)); + + return CLI_SUCCESS; +} + +/* SIP show channelstats CLI (main function) */ +char *sip_cli_show_channelstats(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args) +{ + struct sip_pvt *pvt; + struct ao2_iterator iter; + int numchans = 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_pvts, 0); + + while ((pvt = ao2_iterator_next(&iter))) { + struct ast_rtp_instance_stats stats; + char durbuf[10]; + + ao2_lock(pvt); + + if (pvt->subscribed != SIP_SUBSCRIBED_NONE) { + /* Subscriptions */ + ao2_unlock(pvt); + ao2_ref(pvt, -1); + + continue; + } + + if (!pvt->rtp) { + if (sip_debug) { + ast_cli(args->fd, "%-15.15s %-11.11s (inv state: %s) -- %s\n", + ast_sockaddr_stringify_addr(&pvt->sa), pvt->callid, + sip_invitestate2str(pvt->invitestate), "-- No RTP active"); + } + + ao2_unlock(pvt); + ao2_ref(pvt, -1); + + continue; + } + + if (ast_rtp_instance_get_stats(pvt->rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) { + ast_log(LOG_WARNING, "Could not get RTP stats.\n"); + + ao2_unlock(pvt); + ao2_ref(pvt, -1); + + continue; + } + + if (pvt->owner) { + ast_format_duration_hh_mm_ss(ast_channel_get_duration(pvt->owner), durbuf, sizeof(durbuf)); + } else { + durbuf[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(&pvt->sa), + pvt->callid, + durbuf, + 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); + + numchans++; + + ao2_unlock(pvt); + ao2_ref(pvt, -1); + } + + ao2_iterator_destroy(&iter); + ast_cli(args->fd, "%d active SIP channel%s\n", numchans, ESS(numchans)); + + 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[40]; + char inuse[40]; + int showall = FALSE; + 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; + } + + if (args->argc == 4 && !strcmp(args->argv[3], "all")) { + showall = 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, "iterate thru peer table"))) { + 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 (showall || 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, "toss iterator pointer"); + } + + 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_pvt *pvt; + + 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, "sip_show_objects iter"))) { + 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, "sip_show_objects iter"); + } + + ao2_iterator_destroy(&iter); + + ast_cli(args->fd, "Peer objects by IP:\n\n"); + + iter = ao2_iterator_init(sip_peers_by_addr, 0); + + while ((peer = ao2_t_iterator_next(&iter, "sip_show_objects iter"))) { + 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, "sip_show_objects iter"); + } + + 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, "sip_show_objects iter"))) { + ao2_lock(registry); + ast_cli(args->fd, "Name: %s\n", registry->config); + + ao2_unlock(registry); + ao2_t_ref(registry, -1, "sip_show_objects iter"); + } + + ao2_iterator_destroy(&iter); + + ast_cli(args->fd, "Dialog objects:\n\n"); + + iter = ao2_iterator_init(sip_pvts, 0); + + while ((pvt = ao2_t_iterator_next(&iter, "sip_show_objects iter"))) { + ao2_lock(pvt); + ast_cli(args->fd, "Call-ID: %s\nRefcount: %d\n\n", pvt->callid, ao2_ref(pvt, 0)); + + ao2_unlock(pvt); + ao2_t_ref(pvt, -1, "sip_show_objects iter"); + } + + 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 realtimepeers; + int realtimeregs; + struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + const char *msg; /* temporary msg pointer */ + struct sip_auth_head *auths; + 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; + } + + realtimepeers = ast_check_realtime("sippeers"); + realtimeregs = ast_check_realtime("sipregs"); + + ast_mutex_lock(&sip_auth_lock); + + if ((auths = sip_auths)) { + ao2_t_ref(auths, +1, "Ref global auth for show"); + } + + ast_mutex_unlock(&sip_auth_lock); + + ast_cli(args->fd, "\n\nGlobal Settings:\n"); + ast_cli(args->fd, "----------------\n"); + ast_cli(args->fd, " UDP Bindaddress: %s\n", ast_sockaddr_stringify(&sip_config.bindaddr)); + + if (ast_sockaddr_is_ipv6(&sip_config.bindaddr) && ast_sockaddr_is_any(&sip_config.bindaddr)) { + 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 Bindaddress: %s\n", + sip_config.tcp_enabled != FALSE ? ast_sockaddr_stringify(&sip_tcp_session.local_address) : "Disabled"); + ast_cli(args->fd, " TLS SIP Bindaddress: %s\n", + sip_tls_config.enabled != FALSE ? ast_sockaddr_stringify(&sip_tls_session.local_address) : "Disabled"); + ast_cli(args->fd, " RTP Bindaddress: %s\n", + !ast_sockaddr_isnull(&sip_config.rtpbindaddr) ? ast_sockaddr_stringify_addr(&sip_config.rtpbindaddr) : "Disabled"); + + ast_cli(args->fd, " Video Support: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[1], SIP_VIDEOSUPPORT))); + ast_cli(args->fd, " Text Support: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[1], SIP_TEXTSUPPORT))); + ast_cli(args->fd, " Ignore SDP Sess. Ber.: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[1], SIP_IGNORESDPVERSION))); + 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_ALLOWSUBSCRIBE))); + ast_cli(args->fd, " Allow Overlap Dialing: %s\n", sip_allowoverlap2str(ast_test_flag(&sip_config.flags[1], SIP_ALLOWOVERLAP))); + ast_cli(args->fd, " Allow Promisc. Redir: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[0], SIP_PROMISCREDIR))); + ast_cli(args->fd, " Enable Call Counters: %s\n", AST_CLI_YESNO(sip_config.callcounter)); + 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_USEPATH))); + ast_cli(args->fd, " Realm. Auth: %s\n", AST_CLI_YESNO(auths != NULL)); + + if (auths) { + struct sip_auth *auth; + + AST_LIST_TRAVERSE(&auths->list, auth, next) { + ast_cli(args->fd, " Realm. Auth Entry: Realm %-15.15s User %-10.20s %s\n", + auth->realm, + auth->username, + !ast_strlen_zero(auth->secret) + ? "" + : (!ast_strlen_zero(auth->md5secret) + ? "" : "")); + } + + ao2_t_ref(auths, -1, "Unref global auth for show"); + } + + 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.domainsasrealm)); + 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_USEREQPHONE))); + ast_cli(args->fd, " Always Auth Rejects: %s\n", AST_CLI_YESNO(sip_config.alwaysauthreject)); + ast_cli(args->fd, " Direct RTP Setup: %s\n", AST_CLI_YESNO(sip_config.directrtpsetup)); + 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.sdpsession) ? "-" : sip_config.sdpsession); + ast_cli(args->fd, " SDP Owner Name: %s\n", ast_strlen_zero(sip_config.sdpowner) ? "-" : sip_config.sdpowner); + ast_cli(args->fd, " Reg. Context: %s\n", S_OR(sip_config.regcontext, "")); + ast_cli(args->fd, " Regexten on Qualify: %s\n", AST_CLI_YESNO(sip_config.regextenonqualify)); + ast_cli(args->fd, " Trust RPID: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[0], SIP_TRUSTRPID))); + ast_cli(args->fd, " Send RPID: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[0], SIP_SENDRPID))); + 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.fromdomainport) && (sip_config.fromdomainport != SIP_STANDARD_PORT)) { + ast_cli(args->fd, " From: Domain: %s:%d\n", sip_config.fromdomain, sip_config.fromdomainport); + } else { + ast_cli(args->fd, " From: Domain: %s\n", sip_config.fromdomain); + } + + ast_cli(args->fd, " Record SIP History: %s\n", AST_CLI_ONOFF(sip_config.recordhistory)); + ast_cli(args->fd, " Auth. Failure Events: %s\n", AST_CLI_ONOFF(sip_config.authfailureevents)); + + ast_cli(args->fd, " T.38 Support: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[1], SIP_T38SUPPORT))); + ast_cli(args->fd, " T.38 EC Mode: %s\n", sip_t38_ecmode2str(ast_test_flag(&sip_config.flags[1], SIP_T38SUPPORT))); + ast_cli(args->fd, " T.38 MaxDtgrm: %u\n", sip_config.t38_maxdatagram); + ast_cli(args->fd, " SIP Realtime: %s\n", realtimepeers && realtimeregs ? "Enabld" : "Disabled"); + ast_cli(args->fd, " Qualify Freq : %d ms\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.localaddr == NULL) { + msg = "Disabled, no localnet list"; + } else if (ast_sockaddr_isnull(&sip_config.externaddr)) { + msg = "Disabled"; + } else if (!ast_strlen_zero(sip_config.externhost)) { + msg = "Enabled using externhost"; + } else { + msg = "Enabled using externaddr"; + } + + ast_cli(args->fd, " SIP Address Remapping: %s\n", msg); + ast_cli(args->fd, " Externhost: %s\n", S_OR(sip_config.externhost, "")); + ast_cli(args->fd, " Externaddr: %s\n", ast_sockaddr_stringify(&sip_config.externaddr)); + ast_cli(args->fd, " Externrefresh: %d\n", sip_config.externrefresh); + + for (ha = sip_config.localaddr; ha != NULL; ha = ha->next) { + const char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr)); + const char *mask = ast_strdupa(ast_sockaddr_stringify_addr(&ha->netmask)); + + ast_cli(args->fd, " %-24s%s/%s\n", (ha == sip_config.localaddr) ? "Localnet:" : "", addr, mask); + } + + ast_cli(args->fd, "\nGlobal Signalling Settings:\n"); + ast_cli(args->fd, "---------------------------\n"); + ast_cli(args->fd, " Codecs: %s\n", ast_format_cap_get_names(sip_config.caps, &codec_buf)); + ast_cli(args->fd, " Relax DTMF: %s\n", AST_CLI_YESNO(sip_config.relaxdtmf)); + 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.rtpkeepalive, sip_config.rtpkeepalive ? "" : "(disabled)"); + ast_cli(args->fd, " RTP Timeout: %d %s\n", sip_config.rtptimeout, sip_config.rtptimeout ? "" : "(disabled)" ); + ast_cli(args->fd, " RTP Hold Timeout: %d %s\n", sip_config.rtpholdtimeout, sip_config.rtpholdtimeout ? "" : "(disabled)"); + ast_cli(args->fd, " DNS SRV Lookup: %s\n", AST_CLI_YESNO(sip_config.srvlookup)); + ast_cli(args->fd, " Pedantic SIP Support: %s\n", AST_CLI_YESNO(sip_config.pedanticsipchecking)); + ast_cli(args->fd, " Reg. Min Duration %d secs\n", sip_config.min_expiry); + ast_cli(args->fd, " Reg. Max Duration: %d secs\n", sip_config.max_expiry); + ast_cli(args->fd, " Reg. Default Duration: %d secs\n", sip_config.default_expiry); + ast_cli(args->fd, " Sub. Min Duration %d secs\n", sip_config.min_subexpiry); + ast_cli(args->fd, " Sub. Max Duration: %d secs\n", sip_config.max_subexpiry); + ast_cli(args->fd, " Outbound Reg. Timeout: %d secs\n", sip_config.reg_timeout); + ast_cli(args->fd, " Outbound Reg. Attempts: %d\n", sip_config.regattempts_max); + ast_cli(args->fd, " Outbound Reg. Retry 403: %s\n", AST_CLI_YESNO(sip_config.reg_retry_403)); + ast_cli(args->fd, " Notify Ringing State: %s\n", AST_CLI_YESNO(sip_config.notifyringing)); + + if (sip_config.notifyringing) { + ast_cli(args->fd, " Include CID: %s%s\n", + AST_CLI_YESNO(sip_config.notifycid), + sip_config.notifycid == SIP_NOTIFYCID_IGNORE_CONTEXT ? " (Ignoring context)" : ""); + } + + ast_cli(args->fd, " Notify Hold State: %s\n", AST_CLI_YESNO(sip_config.notifyhold)); + ast_cli(args->fd, " Allow Transfer: %s\n", AST_CLI_YESNO(sip_config.allowtransfer)); + ast_cli(args->fd, " Max Call Bitrate: %d kbps\n", sip_config.maxcallbitrate); + ast_cli(args->fd, " Auto-Framing: %s\n", AST_CLI_YESNO(sip_config.autoframing)); + ast_cli(args->fd, " Outb. Proxy: %s %s\n", + ast_strlen_zero(sip_config.outboundproxy.host) ? "" : sip_config.outboundproxy.host, sip_config.outboundproxy.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: %d secs\n", sip_config.max_se); + ast_cli(args->fd, " Session Min-SE: %d secs\n", sip_config.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.t1min); + 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.prematuremediafilter)); + 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_maxms); + ast_cli(args->fd, " Keepalive: %d\n", sip_config.keepalive); + ast_cli(args->fd, " Progress Inband: %s\n", + ast_test_flag(&sip_config.flags[0], SIP_PROG_INBAND) == SIP_PROG_INBAND_NEVER ? "Never" : AST_CLI_YESNO(ast_test_flag(&sip_config.flags[0], SIP_PROG_INBAND) != SIP_PROG_INBAND_NO)); + ast_cli(args->fd, " Language: %s\n", sip_config.language); + ast_cli(args->fd, " Tone Zone: %s\n", sip_config.zone[0] != '\0' ? sip_config.zone : ""); + ast_cli(args->fd, " MOH Interpret: %s\n", sip_config.mohinterpret); + ast_cli(args->fd, " MOH Suggest: %s\n", sip_config.mohsuggest); + ast_cli(args->fd, " Voice Mail Extension: %s\n", sip_config.vmexten); + ast_cli(args->fd, " RTCP Multiplexing: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[2], SIP_RTCP_MUX))); + + if (realtimepeers || realtimeregs) { + ast_cli(args->fd, "\nRealtime SIP Settings:\n"); + ast_cli(args->fd, "----------------------\n"); + ast_cli(args->fd, " Realtime Peers: %s\n", AST_CLI_YESNO(realtimepeers)); + ast_cli(args->fd, " Realtime Regs: %s\n", AST_CLI_YESNO(realtimeregs)); + ast_cli(args->fd, " Cache Friends: %s\n", AST_CLI_YESNO(ast_test_flag(&sip_config.flags[1], SIP_RTCACHEFRIENDS))); + ast_cli(args->fd, " Update: %s\n", AST_CLI_YESNO(sip_config.peer_rtupdate)); + ast_cli(args->fd, " Ignore Reg. Expire: %s\n", AST_CLI_YESNO(sip_config.ignore_regexpire)); + ast_cli(args->fd, " Save Sys. Name: %s\n", AST_CLI_YESNO(sip_config.rtsave_sysname)); + ast_cli(args->fd, " Save Path Header: %s\n", AST_CLI_YESNO(sip_config.rtsave_path)); + ast_cli(args->fd, " Auto Clear: %d (%s)\n", sip_config.rtautoclear, + ast_test_flag(&sip_config.flags[1], SIP_RTAUTOCLEAR) ? "Enabled" : "Disabled"); + } + + ast_cli(args->fd, "\n"); + + return CLI_SUCCESS; +} diff -durN asterisk-22.2.0.orig/channels/sip/conference.c asterisk-22.2.0/channels/sip/conference.c --- asterisk-22.2.0.orig/channels/sip/conference.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/conference.c 2025-02-18 17:14:46.786903231 +1300 @@ -0,0 +1,958 @@ +/* + * 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 "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/pvt.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_pvt *pvt; + AST_LIST_HEAD_NOLOCK(, sip_selected) selected; + int joining:1; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(callid); + AST_STRING_FIELD(tag); + AST_STRING_FIELD(theirtag); + AST_STRING_FIELD(join_callid); + AST_STRING_FIELD(join_tag); + AST_STRING_FIELD(join_theirtag); + ); +}; + +static void sip_conference_destroy(void *data); +static void *sip_conference_thread(void *data); +static int sip_conference_alloc(struct sip_pvt *pvt); +static int sip_conference_talk_detector(struct ast_bridge_channel *chan, void *hook_pvt, int talking); +static int sip_conference_leave(struct ast_bridge_channel *chan, void *hook_pvt); +static int sip_conference_join(struct sip_conference *conference, struct ast_channel *chan, int administrator); + +/* The ad-hoc conference list */ +struct sip_conferences_head sip_conferences; + +/* Handle conference request */ +int sip_remotecc_conference(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data) +{ + pthread_t threadid; + struct sip_request notify_req; + struct sip_conference_data *conference_data; + struct sip_selected *selected; + + if (!(conference_data = ast_calloc_with_stringfields(1, struct sip_conference_data, 128))) { + return -1; + } + + ao2_t_bump(pvt, "copying dialog pvt into conference_data struct"); + + conference_data->pvt = pvt; + conference_data->joining = !strcmp(remotecc_data->softkeyevent, "Join"); + + ast_string_field_set(conference_data, callid, remotecc_data->dialogid.callid); + ast_string_field_set(conference_data, tag, remotecc_data->dialogid.remotetag); + ast_string_field_set(conference_data, theirtag, remotecc_data->dialogid.localtag); + + if (!conference_data->joining) { + ast_string_field_set(conference_data, join_callid, remotecc_data->consultdialogid.callid); + ast_string_field_set(conference_data, join_tag, remotecc_data->consultdialogid.remotetag); + ast_string_field_set(conference_data, join_theirtag, remotecc_data->consultdialogid.localtag); + } 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->pvt, "thread creation failed"); + 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(pvt, "202 Accepted", req); + + if (!conference_data->joining) { + ast_set_flag(&pvt->flags[0], SIP_OUTGOING); + ast_set_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED); + + pvt->subscribed = SIP_SUBSCRIBED_REMOTECC_XML; + pvt->expiry = sip_config.min_expiry; + + sip_request_copy(&pvt->initreq, req); + sip_request_init(¬ify_req, pvt, SIP_NOTIFY, NULL); + + sip_request_add_header(¬ify_req, "Event", "refer"); + sip_request_build_header(¬ify_req, "Subscription-State", "active;expires=%d", pvt->expiry); + + sip_request_send(pvt, ¬ify_req, SIP_SEND_RELIABLE, pvt->ocseq); + } + + return 0; +} + +/* Handle conflist and confdetails requests */ +int sip_remotecc_conflist(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data) +{ + struct sip_pvt *refer_pvt; + struct sip_conference *conference = NULL; + struct sip_participant *participant; + struct ast_str *content; + int is79xx = strstr(sip_request_get_header(req, "User-Agent"), "CP79") != NULL; + + if (!ast_strlen_zero(remotecc_data->dialogid.callid)) { + struct sip_pvt *target_pvt; + + if (!(target_pvt = sip_pvt_find(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { + ast_debug(1, "dialogid call leg does not exist\n"); + return -1; + } + + ao2_lock(target_pvt); + + if ((conference = target_pvt->conference)) { + ao2_ref(conference, +1); + } + + ao2_unlock(target_pvt); + ao2_t_cleanup(target_pvt, "drop pvt"); + } else if (remotecc_data->confid) { + AST_LIST_LOCK(&sip_conferences); + + AST_LIST_TRAVERSE(&sip_conferences, conference, next) { + if (conference->confid == remotecc_data->confid) { + 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(pvt, "202 Accepted", req); + + if (!ast_strlen_zero(remotecc_data->usercalldata) && strcmp(remotecc_data->usercalldata, "Update")) { + int callid; + + if (!strcmp(remotecc_data->usercalldata, "Remove") || !strcmp(remotecc_data->usercalldata, "Mute")) { + ast_string_field_set(peer, cisco_softkey, remotecc_data->usercalldata); + ao2_ref(conference, -1); + + return 0; + } + + /* Default action is to mute/unmute */ + if (ast_strlen_zero(peer->cisco_softkey)) { + ast_string_field_set(peer, cisco_softkey, "Mute"); + } + + callid = atoi(remotecc_data->usercalldata); + ao2_lock(conference); + + AST_LIST_TRAVERSE(&conference->participants, participant, next) { + if (participant->callid == callid) { + if (!strcmp(peer->cisco_softkey, "Remove")) { + ast_verb(3, "%s is being removed from ad-hoc conference %d\n", ast_channel_name(participant->chan), conference->confid); + + ast_bridge_remove(conference->bridge, participant->chan); + participant->removed = TRUE; + } else if (!strcmp(peer->cisco_softkey, "Mute")) { + ast_verb(3, "%s is being %s in ad-hoc conference %d\n", + ast_channel_name(participant->chan), participant->muted ? "unmuted" : "muted", conference->confid); + participant->muted = !participant->muted; + + ast_channel_lock(participant->chan); + ast_channel_internal_bridge_channel(participant->chan)->features->mute = participant->muted; + ast_channel_unlock(participant->chan); + } + + break; + } + } + + ao2_unlock(conference); + ast_string_field_set(peer, cisco_softkey, ""); + } + + if (!(content = ast_str_create(8192))) { + ao2_ref(conference, -1); + return 0; + } + + if (!(refer_pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + ao2_ref(conference, -1); + ast_free(content); + return 0; + } + + sip_pvt_copy_data(refer_pvt, pvt); + + 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->confid); + 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, *callerid = NULL; + + if (participant->removed) { + continue; + } + + if (participant->muted) { + status = "- "; + } else if (participant->talking) { + status = "+ "; + } else { + status = ""; + } + + ast_channel_lock(participant->chan); + + if (ast_strlen_zero(callerid) && ast_channel_caller(participant->chan)->id.name.valid) { + callerid = ast_strdupa(ast_channel_caller(participant->chan)->id.name.str); + } + + if (ast_strlen_zero(callerid) && ast_channel_caller(participant->chan)->id.number.valid) { + callerid = ast_strdupa(ast_channel_caller(participant->chan)->id.number.str); + } + + ast_channel_unlock(participant->chan); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s%s\n", status, S_OR(callerid, "Anonymous")); + ast_str_append(&content, 0, "UserCallData:%d:0:%d:0:%d\n", SIP_REMOTECC_CONFLIST, conference->confid, participant->callid); + ast_str_append(&content, 0, "\n"); + + ast_channel_unlock(participant->chan); + } + + ao2_unlock(conference); + + 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", is79xx ? 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", is79xx ? 1 : 2); + ast_str_append(&content, 0, "UserCallDataSoftKey:Select:%d:0:%d:0:Remove\n", SIP_REMOTECC_CONFLIST, conference->confid); + 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", is79xx ? 2 : 3); + ast_str_append(&content, 0, "UserCallDataSoftKey:Select:%d:0:%d:0:Mute\n", SIP_REMOTECC_CONFLIST, conference->confid); + 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", is79xx ? 4 : 4); + ast_str_append(&content, 0, "UserCallDataSoftKey:Update:%d:0:%d:0:Update\n", SIP_REMOTECC_CONFLIST, conference->confid); + 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_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(refer_pvt, "bump down the count of refer_pvt since we're done with it."); + + ao2_ref(conference, -1); + ast_free(content); + + return 0; +} + +/* Handle remove last conference participant requests */ +int sip_remotecc_rmlastconf(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data) +{ + struct sip_pvt *target_pvt; + struct sip_conference *conference = NULL; + struct sip_participant *participant; + + if (!(target_pvt = sip_pvt_find(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { + ast_debug(1, "dialogid call leg does not exist\n"); + return -1; + } + + ao2_lock(target_pvt); + + if (target_pvt->conference) { + conference = target_pvt->conference; + ao2_ref(conference, +1); + } + + ao2_unlock(target_pvt); + ao2_t_cleanup(target_pvt, "drop target_pvt"); + + if (!conference) { + ast_debug(1, "Not in a conference\n"); + return -1; + } + + sip_send_response(pvt, "202 Accepted", req); + ao2_lock(conference); + + if ((participant = AST_LIST_FIRST(&conference->participants))) { + ast_bridge_remove(conference->bridge, participant->chan); + } + + ao2_unlock(conference); + ao2_ref(conference, -1); + + return 0; +} + +/* Handle remotecc join requests */ +int sip_remotecc_join(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data) +{ + return sip_remotecc_conference(pvt, req, peer, remotecc_data); +} + +/* Handle remotecc select requests */ +int sip_remotecc_select(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data) +{ + struct sip_selected *selected; + int found = FALSE; + + ao2_lock(peer); + + AST_LIST_TRAVERSE(&peer->selected, selected, next) { + if (!strcmp(remotecc_data->dialogid.callid, selected->callid) && !strcmp(remotecc_data->dialogid.remotetag, selected->tag) && !strcmp(remotecc_data->dialogid.localtag, selected->theirtag)) { + found = TRUE; + break; + } + } + + ao2_unlock(peer); + + if (!found) { + if (!(selected = ast_calloc_with_stringfields(1, struct sip_selected, 128))) { + return -1; + } + + ast_string_field_set(selected, callid, remotecc_data->dialogid.callid); + ast_string_field_set(selected, tag, remotecc_data->dialogid.remotetag); + ast_string_field_set(selected, theirtag, remotecc_data->dialogid.localtag); + + ao2_lock(peer); + AST_LIST_INSERT_TAIL(&peer->selected, selected, next); + ao2_unlock(peer); + } + + sip_send_response(pvt, "202 Accepted", req); + + return 0; +} + +/* Handle remotecc unselect requests */ +int sip_remotecc_unselect(struct sip_pvt *pvt, struct sip_request *req, 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->dialogid.callid, selected->callid) && !strcmp(remotecc_data->dialogid.remotetag, selected->tag) && !strcmp(remotecc_data->dialogid.localtag, selected->theirtag)) { + AST_LIST_REMOVE_CURRENT(next); + sip_selected_destroy(selected); + + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + ao2_unlock(peer); + sip_send_response(pvt, "202 Accepted", req); + + return 0; +} + +/* destroy conference callback for ao2_alloc */ +static void sip_conference_destroy(void *data) +{ + struct sip_conference *conference = data; + + ast_verb(3, "Destroying ad-hoc conference %d\n", conference->confid); + + 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_pvt */ +static int sip_conference_alloc(struct sip_pvt *pvt) +{ + struct sip_conference *conference; + static int next_confid = 0; + + 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->confid = ++next_confid; + conference->keep = !!ast_test_flag(&pvt->flags[2], SIP_CISCO_KEEP_CONFERENCE); + conference->multiadmin = !!ast_test_flag(&pvt->flags[2], SIP_CISCO_MULTIADMIN_CONFERENCE); + + AST_LIST_HEAD_INIT_NOLOCK(&conference->participants); + pvt->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->confid); + + return 0; +} + +static int sip_conference_talk_detector(struct ast_bridge_channel *chan, void *hook_pvt, int talking) +{ + struct sip_participant *participant = hook_pvt; + struct sip_conference *conference = participant->conference; + + ast_debug(1, "%s %s talking in ad-hoc conference %d\n", ast_channel_name(participant->chan), talking ? "started" : "stopped", conference->confid); + participant->talking = talking; + + return 0; +} + +/* cleanup participant structure after leaving bridge */ +static int sip_conference_leave(struct ast_bridge_channel *chan, void *hook_pvt) +{ + struct sip_participant *participant = hook_pvt; + struct sip_conference *conference = participant->conference; + + ast_verb(3, "%s left ad-hoc conference %d\n", ast_channel_name(participant->chan), conference->confid); + + 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->chan), NULL, "confbridge-leave", NULL); + } + } + + if (conference->administrators + conference->users == 1) { + struct sip_participant *participant; + + ast_verb(3, "Only one participant left in ad-hoc conference %d, removing.\n", conference->confid); + + participant = AST_LIST_FIRST(&conference->participants); + ast_bridge_remove(conference->bridge, participant->chan); + } 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->confid); + + AST_LIST_TRAVERSE(&conference->participants, participant, next) { + ast_bridge_remove(conference->bridge, participant->chan); + } + } + + ast_channel_unref(participant->chan); + 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 *chan, int administrator) +{ + struct sip_participant *participant; + struct ast_bridge_channel *bridge_chan; + + if (!administrator && conference->multiadmin) { + ast_channel_lock(chan); + if (ast_channel_tech(chan) == &sip_tech) { + struct sip_pvt *pvt = ast_channel_tech_pvt(chan); + + ao2_lock(pvt); + + if (ast_test_flag(&pvt->flags[1], SIP_CISCO_USECALLMANAGER) && ast_test_flag(&pvt->flags[2], SIP_CISCO_MULTIADMIN_CONFERENCE)) { + ao2_ref(conference, +1); + pvt->conference = conference; + + administrator = TRUE; + } + + ao2_unlock(pvt); + } + + ast_channel_unlock(chan); + } + + if (!(participant = ast_calloc(1, sizeof(*participant)))) { + return -1; + } + + ast_channel_ref(chan); + participant->chan = chan; + + ao2_ref(conference, +1); + participant->conference = conference; + + participant->callid = ++conference->next_callid; + participant->administrator = administrator; + + bridge_chan = ast_channel_internal_bridge_channel(chan); + ao2_ref(bridge_chan, +1); + + bridge_chan->inhibit_colp = TRUE; + + if (ast_bridge_move(conference->bridge, ast_channel_internal_bridge(chan), chan, NULL, 0)) { + ao2_ref(bridge_chan, -1); + ao2_ref(conference, -1); + + ast_channel_unref(chan); + ast_free(participant); + return -1; + } + + ast_bridge_features_remove(bridge_chan->features, AST_BRIDGE_HOOK_REMOVE_ON_PULL); + ast_bridge_leave_hook(bridge_chan->features, sip_conference_leave, participant, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL); + + ast_bridge_talk_detector_hook(bridge_chan->features, sip_conference_talk_detector, participant, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL); + ao2_ref(bridge_chan, -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->chan), NULL, "confbridge-join", NULL); + } + } + + ast_verb(3, "%s joined ad-hoc conference %d\n", ast_channel_name(chan), conference->confid); + + if (administrator) { + struct ast_party_connected_line connected; + + ast_party_connected_line_init(&connected); + + connected.id.name.str = ast_strdup("Conference"); + connected.id.name.valid = TRUE; + + connected.id.number.str = ast_strdup(""); + connected.id.number.valid = TRUE; + + connected.id.name.presentation = connected.id.number.presentation = AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_PASSED_SCREEN; + connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE; + + ast_channel_update_connected_line(chan, &connected, NULL); + ast_party_connected_line_free(&connected); + } + + return 0; +} + +/* add channels to conference */ +static void *sip_conference_thread(void *data) +{ + struct sip_conference_data *conference_data = data; + struct sip_pvt *pvt; + struct ast_channel *chan, *bridge_chan; + struct sip_conference *conference = NULL; + struct ast_str *content = ast_str_alloca(8192); + int res = -1; + + if (!(pvt = sip_pvt_find(conference_data->callid, conference_data->tag, conference_data->theirtag))) { + ast_debug(1, "call leg does not exist\n"); + goto cleanup; + } + + ao2_lock(pvt); + + /* Is this a new ad-hoc conference? */ + if (!pvt->conference) { + if (sip_conference_alloc(pvt)) { + ast_debug(1, "unable to create conference\n"); + + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "drop pvt"); + + goto cleanup; + } + + /* Increase ref on conference so we don't need to keep a ref on it's parent dialog */ + conference = pvt->conference; + ao2_ref(conference, +1); + + if (!(chan = pvt->owner)) { + ast_debug(1, "no owner channel\n"); + + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "drop pvt"); + + goto cleanup; + } + + ast_channel_ref(chan); + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "drop pvt"); + + if (!(bridge_chan = ast_channel_bridge_peer(chan))) { + ast_debug(1, "no bridged channel\n"); + ast_channel_unref(chan); + + goto cleanup; + } + + if (sip_conference_join(conference, chan, TRUE)) { + ast_channel_unref(chan); + ast_channel_unref(bridge_chan); + + goto cleanup; + } + + ast_indicate(bridge_chan, AST_CONTROL_UNHOLD); + + if (sip_conference_join(conference, bridge_chan, FALSE)) { + ast_channel_unref(chan); + ast_channel_unref(bridge_chan); + + goto cleanup; + } + + ast_channel_unref(chan); + ast_channel_unref(bridge_chan); + } else { + conference = pvt->conference; + ao2_ref(conference, +1); + + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "drop pvt"); + } + + if (!conference_data->joining) { + if (!(pvt = sip_pvt_find(conference_data->join_callid, conference_data->join_tag, conference_data->join_theirtag))) { + ast_debug(1, "join call leg does not exist\n"); + goto cleanup; + } + + ao2_lock(pvt); + + if (!(chan = pvt->owner)) { + ast_debug(1, "no owner channel\n"); + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "drop pvt"); + + goto cleanup; + } + + ast_channel_ref(chan); + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "drop pvt"); + + if (!(bridge_chan = ast_channel_bridge_peer(chan))) { + ast_debug(1, "no bridged channel\n"); + ast_channel_unref(chan); + + goto cleanup; + } + + ast_indicate(bridge_chan, AST_CONTROL_UNHOLD); + + if (sip_conference_join(conference, bridge_chan, FALSE)) { + ast_channel_unref(chan); + ast_channel_unref(bridge_chan); + + goto cleanup; + } + + ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); + + ast_channel_unref(chan); + ast_channel_unref(bridge_chan); + + 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 (!(pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + goto cleanup; + } + + sip_pvt_copy_data(pvt, conference_data->pvt); + + 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->callid); + ast_str_append(&content, 0, "%s\n", conference_data->theirtag); + ast_str_append(&content, 0, "%s\n", conference_data->tag); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + ao2_lock(pvt); + sip_send_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_unlock(pvt); + + ao2_t_cleanup(pvt, "bump down the count of pvt since we're done with it."); + } 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->callid, selected->callid) && !strcmp(conference_data->tag, selected->tag) && !strcmp(conference_data->theirtag, selected->theirtag)) { + continue; + } + + if (!(pvt = sip_pvt_find(selected->callid, selected->tag, selected->theirtag))) { + ast_debug(1, "call leg does not exist\n"); + continue; + } + + ao2_lock(pvt); + + if (!(chan = pvt->owner)) { + ast_debug(1, "no owner channel\n"); + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "drop pvt"); + + goto cleanup; + } + + ast_channel_ref(chan); + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "drop pvt"); + + if (!(bridge_chan = ast_channel_bridge_peer(chan))) { + ast_debug(1, "no bridged channel\n"); + ast_channel_unref(chan); + + goto cleanup; + } + + ast_indicate(bridge_chan, AST_CONTROL_UNHOLD); + + if (sip_conference_join(conference, bridge_chan, 0)) { + ast_channel_unref(chan); + ast_channel_unref(bridge_chan); + + goto cleanup; + } + + ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); + + ast_channel_unref(chan); + ast_channel_unref(bridge_chan); + } + + res = 0; + } + +cleanup: + if (conference) { + ao2_ref(conference, -1); + } + + ast_str_reset(content); + + if (!conference_data->joining) { + struct sip_request req; + + ao2_lock(conference_data->pvt); + sip_request_prepare(&req, conference_data->pvt, SIP_NOTIFY, 0, TRUE); + sip_request_add_header(&req, "Event", "refer"); + sip_request_add_header(&req, "Subscription-State", "terminated;reason=noresource"); + sip_request_add_header(&req, "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(&req, ast_str_buffer(content)); + sip_request_send(conference_data->pvt, &req, SIP_SEND_RELIABLE, conference_data->pvt->ocseq); + ao2_unlock(conference_data->pvt); + } else { + if ((pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + sip_pvt_copy_data(pvt, conference_data->pvt); + + 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->callid); + ast_str_append(&content, 0, "%s\n", conference_data->theirtag); + ast_str_append(&content, 0, "%s\n", conference_data->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->callid); + ast_str_append(&content, 0, "%s\n", conference_data->theirtag); + ast_str_append(&content, 0, "%s\n", conference_data->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(pvt); + sip_send_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_unlock(pvt); + + ao2_t_cleanup(pvt, "bump down the count of pvt since we're done with it."); + } + } + + 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->pvt, "drop conference_data->pvt"); + ast_string_field_free_memory(conference_data); + ast_free(conference_data); + + return NULL; +} + +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.2.0.orig/channels/sip/config.c asterisk-22.2.0/channels/sip/config.c --- asterisk-22.2.0.orig/channels/sip/config.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/config.c 2025-02-18 17:14:46.788903178 +1300 @@ -0,0 +1,1574 @@ +/* + * 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.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_T1MIN 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_REGISTRATION_TIMEOUT 20 + +#define SIP_DEFAULT_TCPAUTHLIMIT 100 +#define SIP_DEFAULT_TCPAUTHTIMEOUT 30 + +#define SIP_DEFAULT_USERAGENT "Asterisk PBX" /* Default Useragent: header unless re-defined in sip.conf */ +#define SIP_DEFAULT_SDPSESSION "Asterisk PBX" /* Default SDP session name, (s=) header unless re-defined in sip.conf */ +#define SIP_DEFAULT_SDPOWNER "root" /* Default SDP username field in (o=) header unless re-defined in sip.conf */ +#define SIP_DEFAULT_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_MOHINTERPRET "default" /* The default music class */ +#define SIP_DEFAULT_MOHSUGGEST "" +#define SIP_DEFAULT_VMEXTEN "asterisk" /* Default voicemail extension */ +#define SIP_DEFAULT_CALLERID "asterisk" /* Default caller ID */ +#define SIP_DEFAULT_MWI_FROM "" + +#define SIP_DEFAULT_MAX_FORWARDS 70 + +#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_MAXMS 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_ALLOWTRANSFER TRUE +#define SIP_DEFAULT_ALLOWSUBSCRIBE TRUE +#define SIP_DEFAULT_CALLCOUNTER FALSE /* Do not enable call counters by default */ +#define SIP_DEFAULT_SRVLOOKUP FALSE /* Recommended setting is ON */ +#define SIP_DEFAULT_SEND_DIVERSION TRUE +#define SIP_DEFAULT_RTPKEEPALIVE FALSE /* Default RTPkeepalive setting */ + +#define SIP_DEFAULT_ALLOW_EXTERNAL_DOMAINS TRUE /* Allow external domains */ +#define SIP_DEFAULT_REALM "asterisk" /* Realm for HTTP digest authentication */ +#define SIP_DEFAULT_DOMAINSASREALM FALSE /* Use the domain option to guess the realm for registration and invite requests */ +#define SIP_DEFAULT_NOTIFYRINGING TRUE /* Notify devicestate system on ringing state */ +#define SIP_DEFAULT_NOTIFYCID SIP_NOTIFYCID_DISABLED /* Include CID with ringing notifications */ +#define SIP_DEFAULT_PEDANTIC_CHECKING TRUE /* Follow SIP standards for dialog matching */ +#define SIP_DEFAULT_MATCHEXTERNADDRLOCALLY FALSE /* Match extern IP locally default setting */ +#define SIP_DEFAULT_REGEXTENONQUALIFY FALSE + +#define SIP_DEFAULT_ALWAYSAUTHREJECT 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_MAX_SE 1800 /* Session-Timer Default Session-Expires period (RFC 4028) */ +#define SIP_DEFAULT_MIN_SE 90 /* Session-Timer Default Min-SE period (RFC 4028) */ + +#define SIP_DEFAULT_MAXCALLBITRATE 384 /* Max bitrate for video */ + +#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_remove_stale_contexts(char *newcontexts, char *oldcontexts); + +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 */ +char sip_used_context[AST_MAX_CONTEXT]; /* name of automatically created context for unloading */ + +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_internip 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_internip; + +/* Handle T.38 configuration options common to users and peers */ +int sip_config_parse_t38(struct ast_flags *flags, struct ast_flags *mask, struct ast_variable *var, unsigned int *maxdatagram) +{ + int res = 1; + + if (!strcasecmp(var->name, "t38pt_udptl")) { + char *udptl = ast_strdupa(var->value); + char *option, *next = udptl; + + ast_set_flag(&mask[1], SIP_T38SUPPORT); + + while ((option = strsep(&next, ","))) { + if (ast_true(option) || !strcasecmp(option, "fec")) { + ast_clear_flag(&flags[1], SIP_T38SUPPORT); + ast_set_flag(&flags[1], SIP_T38SUPPORT_UDPTL_FEC); + } else if (!strcasecmp(option, "redundancy")) { + ast_clear_flag(&flags[1], SIP_T38SUPPORT); + ast_set_flag(&flags[1], SIP_T38SUPPORT_UDPTL_REDUNDANCY); + } else if (!strcasecmp(option, "none")) { + ast_clear_flag(&flags[1], SIP_T38SUPPORT); + ast_set_flag(&flags[1], SIP_T38SUPPORT_UDPTL); + } else if (!strncasecmp(option, "maxdatagram=", 12)) { + if (ast_parse_arg(option + 12, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, maxdatagram, sip_config.t38_maxdatagram, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid maxdatagram '%s' at line %d\n", var->value, var->lineno); + } + } + } + } else if (!strcasecmp(var->name, "t38pt_usertpsource")) { + ast_set_flag(&mask[1], SIP_UDPTL_DESTINATION); + ast_set2_flag(&flags[1], ast_true(var->value), SIP_UDPTL_DESTINATION); + } else { + res = 0; + } + + return res; +} + +/* Handle flag-type options common to configuration of devices - peers */ +int sip_config_parse_common(struct ast_flags *flags, struct ast_flags *mask, struct ast_variable *var) +{ + int res = 1; + + if (!strcasecmp(var->name, "trustrpid")) { + ast_set_flag(&mask[0], SIP_TRUSTRPID); + ast_set2_flag(&flags[0], ast_true(var->value), SIP_TRUSTRPID); + } else if (!strcasecmp(var->name, "supportpath")) { + ast_set_flag(&mask[0], SIP_USEPATH); + ast_set2_flag(&flags[0], ast_true(var->value), SIP_USEPATH); + } else if (!strcasecmp(var->name, "sendrpid")) { + ast_set_flag(&mask[0], SIP_SENDRPID); + + if (!strcasecmp(var->value, "rpid")) { + ast_set_flag(&flags[0], SIP_SENDRPID_RPID); + } else if (!strcasecmp(var->value, "pai")) { + ast_set_flag(&flags[0], SIP_SENDRPID_PAI); + } else if (ast_true(var->value)) { + ast_set_flag(&flags[0], SIP_SENDRPID_RPID); + } + } else if (!strcasecmp(var->name, "rpid_update")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d (always enabled).\n", var->name, var->lineno); + } else if (!strcasecmp(var->name, "rpid_immediate")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d (always enabled).\n", var->name, var->lineno); + } else if (!strcasecmp(var->name, "trust_id_outbound")) { + ast_set_flag(&mask[1], SIP_TRUST_ID_OUTBOUND); + ast_clear_flag(&flags[1], SIP_TRUST_ID_OUTBOUND); + + if (ast_true(var->value)) { + ast_set_flag(&flags[1], SIP_TRUST_ID_OUTBOUND_YES); + } else if (ast_false(var->value)) { + ast_set_flag(&flags[1], SIP_TRUST_ID_OUTBOUND_NO); + } else { + ast_log(LOG_WARNING, "Unknown trust_id_outbound mode '%s' on line %d.\n", var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "g726nonstandard")) { + ast_set_flag(&mask[0], SIP_G726_NONSTANDARD); + ast_set2_flag(&flags[0], ast_true(var->value), SIP_G726_NONSTANDARD); + } else if (!strcasecmp(var->name, "dtmfmode")) { + ast_set_flag(&mask[0], SIP_DTMF); + ast_clear_flag(&flags[0], SIP_DTMF); + + if (!strcasecmp(var->value, "inband")) { + ast_set_flag(&flags[0], SIP_DTMF_INBAND); + } else if (!strcasecmp(var->value, "rfc2833")) { + ast_set_flag(&flags[0], SIP_DTMF_RFC2833); + } else if (!strcasecmp(var->value, "auto")) { + ast_set_flag(&flags[0], SIP_DTMF_AUTO); + } else { + ast_log(LOG_WARNING, "Unknown dtmf mode '%s' on line %d, using rfc2833.\n", var->value, var->lineno); + ast_set_flag(&flags[0], SIP_DTMF_RFC2833); + } + } else if (!strcasecmp(var->name, "nat")) { + char *nat = ast_strdupa(var->value); + char *option; + + ast_set_flag(&mask[0], SIP_NAT_FORCE_RPORT); + ast_set_flag(&mask[1], SIP_SYMMETRICRTP); + ast_set_flag(&mask[2], SIP_NAT_AUTO_RPORT); + ast_set_flag(&mask[2], SIP_NAT_AUTO_COMEDIA); + + while ((option = strsep(&nat, ","))) { + if (ast_false(option)) { + ast_clear_flag(&flags[0], SIP_NAT_FORCE_RPORT); + ast_clear_flag(&flags[1], SIP_SYMMETRICRTP); + ast_clear_flag(&flags[2], SIP_NAT_AUTO_RPORT); + ast_clear_flag(&flags[2], SIP_NAT_AUTO_COMEDIA); + + break; /* It doesn't make sense to have no + something else */ + } else if (!strcasecmp(option, "yes")) { + ast_log(LOG_WARNING, "nat=yes is deprecated, use nat=force_rport,comedia instead.\n"); + + ast_set_flag(&flags[0], SIP_NAT_FORCE_RPORT); + ast_set_flag(&flags[1], SIP_SYMMETRICRTP); + ast_clear_flag(&flags[2], SIP_NAT_AUTO_RPORT); + ast_clear_flag(&flags[2], SIP_NAT_AUTO_COMEDIA); + + break; /* It doesn't make sense to have yes + something else */ + } else if (!strcasecmp(option, "force_rport") && !ast_test_flag(&flags[2], SIP_NAT_AUTO_RPORT)) { + ast_set_flag(&flags[0], SIP_NAT_FORCE_RPORT); + } else if (!strcasecmp(option, "comedia") && !ast_test_flag(&flags[2], SIP_NAT_AUTO_COMEDIA)) { + ast_set_flag(&flags[1], SIP_SYMMETRICRTP); + } else if (!strcasecmp(option, "auto_force_rport")) { + ast_set_flag(&flags[2], SIP_NAT_AUTO_RPORT); + /* In case someone did something dumb like nat=force_rport,auto_force_rport */ + ast_clear_flag(&flags[0], SIP_NAT_FORCE_RPORT); + } else if (!strcasecmp(option, "auto_comedia")) { + ast_set_flag(&flags[2], SIP_NAT_AUTO_COMEDIA); + /* In case someone did something dumb like nat=comedia,auto_comedia*/ + ast_clear_flag(&flags[1], SIP_SYMMETRICRTP); + } + } + } else if (!strcasecmp(var->name, "directmedia") || !strcasecmp(var->name, "canreinvite")) { + ast_set_flag(&mask[0], SIP_REINVITE); + ast_clear_flag(&flags[0], SIP_REINVITE); + + if (ast_true(var->value)) { + ast_set_flag(&flags[0], SIP_DIRECT_MEDIA | SIP_DIRECT_MEDIA_NAT); + } else if (!ast_false(var->value)) { + char *canreinvite = ast_strdupa(var->value); + char *option; + + while ((option = strsep(&canreinvite, ","))) { + if (!strcasecmp(option, "update")) { + ast_set_flag(&flags[0], SIP_REINVITE_UPDATE | SIP_DIRECT_MEDIA); + } else if (!strcasecmp(option, "nonat")) { + ast_set_flag(&flags[0], SIP_DIRECT_MEDIA); + ast_clear_flag(&flags[0], SIP_DIRECT_MEDIA_NAT); + } else if (!strcasecmp(option, "outgoing")) { + ast_set_flag(&flags[0], SIP_DIRECT_MEDIA); + ast_set_flag(&mask[1], SIP_DIRECT_MEDIA_OUTGOING); + ast_set_flag(&flags[1], SIP_DIRECT_MEDIA_OUTGOING); + } else { + ast_log(LOG_WARNING, "Unknown directmedia mode '%s' on line %d.\n", option, var->lineno); + } + } + } + } else if (!strcasecmp(var->name, "insecure")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d (always disabled).\n", var->name, var->lineno); + } else if (!strcasecmp(var->name, "progressinband")) { + ast_set_flag(&mask[0], SIP_PROG_INBAND); + ast_clear_flag(&flags[0], SIP_PROG_INBAND); + + if (ast_true(var->value)) { + ast_set_flag(&flags[0], SIP_PROG_INBAND_YES); + } else if (!strcasecmp(var->value, "never")) { + ast_set_flag(&flags[0], SIP_PROG_INBAND_NEVER); + } + } else if (!strcasecmp(var->name, "promiscredir")) { + ast_set_flag(&mask[0], SIP_PROMISCREDIR); + ast_set2_flag(&flags[0], ast_true(var->value), SIP_PROMISCREDIR); + } else if (!strcasecmp(var->name, "videosupport")) { + if (!strcasecmp(var->value, "always")) { + ast_set_flag(&mask[1], SIP_VIDEOSUPPORT_ALWAYS); + ast_set_flag(&flags[1], SIP_VIDEOSUPPORT_ALWAYS); + } else { + ast_set_flag(&mask[1], SIP_VIDEOSUPPORT); + ast_set2_flag(&flags[1], ast_true(var->value), SIP_VIDEOSUPPORT); + } + } else if (!strcasecmp(var->name, "textsupport")) { + ast_set_flag(&mask[1], SIP_TEXTSUPPORT); + ast_set2_flag(&flags[1], ast_true(var->value), SIP_TEXTSUPPORT); + } else if (!strcasecmp(var->name, "allowoverlap")) { + ast_set_flag(&mask[1], SIP_ALLOWOVERLAP); + ast_clear_flag(&flags[1], SIP_ALLOWOVERLAP); + + if (ast_true(var->value)) { + ast_set_flag(&flags[1], SIP_ALLOWOVERLAP_YES); + } else if (!strcasecmp(var->value, "dtmf")){ + ast_set_flag(&flags[1], SIP_ALLOWOVERLAP_DTMF); + } + } else if (!strcasecmp(var->name, "allowsubscribe")) { + ast_set_flag(&mask[1], SIP_ALLOWSUBSCRIBE); + ast_set2_flag(&flags[1], ast_true(var->value), SIP_ALLOWSUBSCRIBE); + } else if (!strcasecmp(var->name, "ignoresdpversion")) { + ast_set_flag(&mask[1], SIP_IGNORESDPVERSION); + ast_set2_flag(&flags[1], ast_true(var->value), SIP_IGNORESDPVERSION); + } else if (!strcasecmp(var->name, "faxdetect")) { + ast_set_flag(&mask[1], SIP_FAX_DETECT); + + if (ast_true(var->value)) { + ast_set_flag(&flags[1], SIP_FAX_DETECT_CNG | SIP_FAX_DETECT_T38); + } else if (ast_false(var->value)) { + ast_clear_flag(&flags[1], SIP_FAX_DETECT_CNG | SIP_FAX_DETECT_T38); + } else { + char *faxdetect = ast_strdupa(var->value); + char *option; + + while ((option = strsep(&faxdetect, ","))) { + if (!strcasecmp(option, "cng")) { + ast_set_flag(&flags[1], SIP_FAX_DETECT_CNG); + } else if (!strcasecmp(option, "t38")) { + ast_set_flag(&flags[1], SIP_FAX_DETECT_T38); + } else { + ast_log(LOG_WARNING, "Unknown faxdetect mode '%s' on line %d.\n", option, var->lineno); + } + } + } + } else if (!strcasecmp(var->name, "rfc2833compensate")) { + ast_set_flag(&mask[0], SIP_RFC2833_COMPENSATE); + ast_set2_flag(&flags[0], ast_true(var->value), SIP_RFC2833_COMPENSATE); + } else if (!strcasecmp(var->name, "dndbusy")) { + ast_set_flag(&mask[2], SIP_DND_BUSY); + ast_set2_flag(&flags[2], ast_true(var->value), SIP_DND_BUSY); + } else if (!strcasecmp(var->name, "huntgroup_default")) { + ast_set_flag(&mask[2], SIP_HUNTGROUP_DEFAULT); + ast_set2_flag(&flags[2], ast_true(var->value), SIP_HUNTGROUP_DEFAULT); + } else if (!strcasecmp(var->name, "cisco_usecallmanager")) { + ast_set_flag(&mask[1], SIP_CISCO_USECALLMANAGER); + ast_set2_flag(&flags[1], ast_true(var->value), SIP_CISCO_USECALLMANAGER); + } else if (!strcasecmp(var->name, "cisco_keep_conference")) { + ast_set_flag(&mask[2], SIP_CISCO_KEEP_CONFERENCE); + ast_set2_flag(&flags[2], ast_true(var->value), SIP_CISCO_KEEP_CONFERENCE); + } else if (!strcasecmp(var->name, "cisco_multiadmin_conference")) { + ast_set_flag(&mask[2], SIP_CISCO_MULTIADMIN_CONFERENCE); + ast_set2_flag(&flags[2], ast_true(var->value), SIP_CISCO_MULTIADMIN_CONFERENCE); + } else if (!strcasecmp(var->name, "rtcp_mux")) { + ast_set_flag(&mask[2], SIP_RTCP_MUX); + ast_set2_flag(&flags[2], ast_true(var->value), SIP_RTCP_MUX); + } else { + res = 0; + } + + return res; +} + +/* 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 *var; + struct sip_peer *peer; + char *category, *context, *oldregcontext; + char newcontexts[AST_MAX_CONTEXT], oldcontexts[AST_MAX_CONTEXT]; + struct ast_flags mask[3] = {{0}}; + struct ast_flags setflags[3] = {{0}}; + struct ast_flags loadflags = {0}; + int auto_sip_domains = FALSE; + struct ast_sockaddr old_bindaddr = sip_config.bindaddr; + int peer_count = 0, timerb_set = FALSE, timert1_set = FALSE; + int subscribe_network_change = TRUE; + time_t reload_start, reload_end; + int bindport = 0; + int acl_change_sub_needed = FALSE; + int min_subexpiry_set = 0, max_subexpiry_set = 0; + struct ast_jb_conf default_jb_config = {.flags = 0, .max_size = 200, .resync_threshold = 1000, .impl = "fixed", .target_extra = 40}; + + reload_start = time(0); + + ast_unload_realtime("sipregs"); + ast_unload_realtime("sippeers"); + + if (reason != CHANNEL_MODULE_LOAD && reason != CHANNEL_ACL_RELOAD && !ast_test_flag(&sip_config.flags[1], SIP_RTCACHEFRIENDS)) { + ast_set_flag(&loadflags, CONFIG_FLAG_FILEUNCHANGED); + } + + config = ast_config_load("sip.conf", loadflags); + + /* 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) { + 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 */ + + if (reason != CHANNEL_MODULE_LOAD) { + ast_debug(4, "SIP reload started\n"); + + sip_domain_destroy_all(); + sip_auth_destroy_all(); + + sip_registry_destroy_all(); + sip_mwi_subscription_destroy_all(); + + ao2_t_callback(sip_peers, OBJ_NODATA, sip_peer_set_removed, NULL, "callback to mark all peers"); + } + + /* 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(""); + + /* Initialize copy of current sip_config.regcontext for later use in removing stale contexts */ + ast_copy_string(oldcontexts, sip_config.regcontext, sizeof(oldcontexts)); + oldregcontext = oldcontexts; + + /* 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 IP addresses */ + ast_sockaddr_parse(&sip_config.bindaddr, "0.0.0.0:0", 0); + memset(&sip_internip, 0, sizeof(sip_internip)); + + /* Free memory for local network address mask */ + ast_free_ha(sip_config.localaddr); + + memset(&sip_config.localaddr, 0, sizeof(sip_config.localaddr)); + memset(&sip_config.externaddr, 0, sizeof(sip_config.externaddr)); + memset(&sip_config.mediaaddr, 0, sizeof(sip_config.mediaaddr)); + memset(&sip_config.rtpbindaddr, 0, sizeof(sip_config.rtpbindaddr)); + + memset(&sip_config.outboundproxy, 0, sizeof(struct sip_proxy)); + sip_config.outboundproxy.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.srvlookup = SIP_DEFAULT_SRVLOOKUP; + + 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.externhost[0] = '\0'; /* External host name (for behind NAT DynDNS support) */ + sip_config.externexpire = 0; /* Expiration for DNS re-issuing */ + sip_config.externrefresh = 10; + sip_config.externtcpport = 0; + sip_config.externtlsport = 0; + + /* Reset channel settings to default before re-configuring */ + sip_config.allow_external_domains = SIP_DEFAULT_ALLOW_EXTERNAL_DOMAINS; /* Allow external invites */ + sip_config.regcontext[0] = '\0'; + sip_config.regextenonqualify = SIP_DEFAULT_REGEXTENONQUALIFY; + + ast_format_cap_remove_by_type(sip_config.caps, AST_MEDIA_TYPE_UNKNOWN); + + ast_format_cap_append(sip_config.caps, ast_format_ulaw, 0); + ast_format_cap_append(sip_config.caps, ast_format_alaw, 0); + ast_format_cap_append(sip_config.caps, ast_format_gsm, 0); + ast_format_cap_append(sip_config.caps, ast_format_h263, 0); + + sip_config.send_diversion = SIP_DEFAULT_SEND_DIVERSION; + sip_config.notifyringing = SIP_DEFAULT_NOTIFYRINGING; + sip_config.notifycid = SIP_DEFAULT_NOTIFYCID; + sip_config.notifyhold = FALSE; /* Keep track of hold status for a peer */ + + sip_config.directrtpsetup = FALSE; /* Experimental feature, disabled by default */ + sip_config.alwaysauthreject = SIP_DEFAULT_ALWAYSAUTHREJECT; + sip_config.auth_options_requests = SIP_DEFAULT_AUTH_OPTIONS; + sip_config.auth_message_requests = SIP_DEFAULT_AUTH_MESSAGE; + + sip_config.messagecontext[0] = '\0'; + sip_config.accept_outofcall_message = SIP_DEFAULT_ACCEPT_OUTOFCALL_MESSAGE; + + sip_config.allowsubscribe = SIP_DEFAULT_ALLOWSUBSCRIBE; + sip_config.allowtransfer = SIP_DEFAULT_ALLOWTRANSFER; /* Merrily accept all transfers by default */ + + sip_config.disallowed_methods = SIP_UNKNOWN; + 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.sdpsession, sizeof(sip_config.sdpsession), "%s %s", SIP_DEFAULT_SDPSESSION, ast_get_version()); + snprintf(sip_config.sdpowner, sizeof(sip_config.sdpowner), "%s", SIP_DEFAULT_SDPOWNER); + + sip_config.prematuremediafilter = TRUE; + + ast_copy_string(sip_config.realm, S_OR(ast_config_AST_SYSTEM_NAME, SIP_DEFAULT_REALM), sizeof(sip_config.realm)); + sip_config.domainsasrealm = SIP_DEFAULT_DOMAINSASREALM; + + 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.reg_timeout = SIP_DEFAULT_REGISTRATION_TIMEOUT; + sip_config.regattempts_max = 0; + sip_config.reg_retry_403 = 0; + + sip_config.pedanticsipchecking = SIP_DEFAULT_PEDANTIC_CHECKING; + + sip_config.autoframing = 0; + sip_config.callcounter = SIP_DEFAULT_CALLCOUNTER; + sip_config.match_auth_username = FALSE; /* Match auth username if available instead of From: Default off. */ + + sip_config.rtptimeout = 0; + sip_config.rtpholdtimeout = 0; + sip_config.rtpkeepalive = SIP_DEFAULT_RTPKEEPALIVE; + + ast_set_flag(&sip_config.flags[1], SIP_ALLOWSUBSCRIBE); /* Default for all devices: TRUE */ + ast_set_flag(&sip_config.flags[1], SIP_ALLOWOVERLAP_YES); /* Default for all devices: Yes */ + + sip_config.rtautoclear = 120; + sip_config.peer_rtupdate = TRUE; + sip_config.dynamic_exclude_static = FALSE; /* Exclude static peers */ + + sip_config.tcp_enabled = FALSE; + + /* Session-Timers */ + sip_config.stimer_mode = SIP_STIMER_MODE_ACCEPT; + sip_config.stimer_refresher = SIP_STIMER_REFRESHER_THEM; + sip_config.min_se = SIP_DEFAULT_MIN_SE; + sip_config.max_se = SIP_DEFAULT_MAX_SE; + + /* Peer qualification settings */ + sip_config.qualify_peers = SIP_DEFAULT_QUALIFY_PEERS; + sip_config.qualify_maxms = SIP_DEFAULT_QUALIFY_MAXMS; + 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.subscribecontext[0] = '\0'; + sip_config.max_forwards = SIP_DEFAULT_MAX_FORWARDS; + + sip_config.language[0] = '\0'; + sip_config.fromdomain[0] = '\0'; + sip_config.fromdomainport = 0; + + sip_config.keepalive = SIP_DEFAULT_KEEPALIVE; + sip_config.zone[0] = '\0'; + sip_config.maxcallbitrate = SIP_DEFAULT_MAXCALLBITRATE; + + ast_copy_string(sip_config.mohinterpret, SIP_DEFAULT_MOHINTERPRET, sizeof(sip_config.mohinterpret)); + ast_copy_string(sip_config.mohsuggest, SIP_DEFAULT_MOHSUGGEST, sizeof(sip_config.mohsuggest)); + ast_copy_string(sip_config.vmexten, SIP_DEFAULT_VMEXTEN, sizeof(sip_config.vmexten)); + + 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 */ + + ast_copy_string(sip_config.engine, SIP_DEFAULT_ENGINE, sizeof(sip_config.engine)); + ast_copy_string(sip_config.parkinglot, DEFAULT_PARKINGLOT, sizeof(sip_config.parkinglot)); + + /* Debugging settings, always default to off */ + sip_config.dumphistory = FALSE; + sip_config.recordhistory = FALSE; + + /* Misc settings for the channel */ + sip_config.relaxdtmf = FALSE; + sip_config.authfailureevents = FALSE; + + sip_config.timer_t1 = SIP_DEFAULT_TIMER_T1; + sip_config.timer_b = SIP_DEFAULT_TIMER_T1 * 64; + sip_config.t1min = SIP_DEFAULT_T1MIN; + sip_config.t38_maxdatagram = -1; + + sip_config.shrinkcallerid = TRUE; + sip_config.refer_addheaders = TRUE; + + sip_config.tcpauthlimit = SIP_DEFAULT_TCPAUTHLIMIT; + sip_config.tcpauthtimeout = SIP_DEFAULT_TCPAUTHTIMEOUT; + + 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_subexpiry = SIP_DEFAULT_MIN_EXPIRY; + sip_config.max_subexpiry = SIP_DEFAULT_MAX_EXPIRY; + sip_config.mwi_expiry = SIP_DEFAULT_MWI_EXPIRY; + + sip_config.matchexternaddrlocally = SIP_DEFAULT_MATCHEXTERNADDRLOCALLY; + + /* Copy the default jb config over sip_jb_config */ + memcpy(&sip_jb_config, &default_jb_config, sizeof(sip_jb_config)); + + ast_clear_flag(&sip_config.flags[1], SIP_FAX_DETECT); + ast_clear_flag(&sip_config.flags[1], SIP_VIDEOSUPPORT | SIP_VIDEOSUPPORT_ALWAYS); + ast_clear_flag(&sip_config.flags[1], SIP_TEXTSUPPORT); + ast_clear_flag(&sip_config.flags[1], SIP_IGNORESDPVERSION); + + sip_debug &= ~SIP_DEBUG_CONFIG; + + /* Read the [general] config section of sip.conf (or from realtime config) */ + for (var = ast_variable_browse(config, "general"); var; var = var->next) { + if (sip_config_parse_common(&setflags[0], &mask[0], var)) { + continue; + } + + if (sip_config_parse_t38(&setflags[0], &mask[0], var, &sip_config.t38_maxdatagram)) { + continue; + } + + /* handle jb conf */ + if (!ast_jb_read_conf(&sip_jb_config, var->name, var->value)) { + continue; + } + + /* handle tls conf, don't allow setting of tlsverifyclient as it isn't supported by chan_sip */ + if (!strcasecmp(var->name, "tlsverifyclient")) { + ast_log(LOG_WARNING, "Ignoring unsupported option '%s' at line %d\n", var->name, var->lineno); + continue; + } else if (!ast_tls_read_conf(&sip_tls_config, &sip_tls_session, var->name, var->value)) { + continue; + } + + if (!strcasecmp(var->name, "context")) { + ast_copy_string(sip_config.context, var->value, sizeof(sip_config.context)); + } else if (!strcasecmp(var->name, "subscribecontext")) { + ast_copy_string(sip_config.subscribecontext, var->value, sizeof(sip_config.subscribecontext)); + } else if (!strcasecmp(var->name, "callcounter")) { + sip_config.callcounter = ast_true(var->value); + } else if (!strcasecmp(var->name, "allowguest")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d (always disabled)\n", var->name, var->lineno); + } else if (!strcasecmp(var->name, "realm")) { + ast_copy_string(sip_config.realm, var->value, sizeof(sip_config.realm)); + } else if (!strcasecmp(var->name, "domainsasrealm")) { + sip_config.domainsasrealm = ast_true(var->value); + } else if (!strcasecmp(var->name, "useragent")) { + ast_copy_string(sip_config.useragent, var->value, sizeof(sip_config.useragent)); + ast_debug(1, "Setting SIP channel User-Agent Name to %s\n", sip_config.useragent); + } else if (!strcasecmp(var->name, "sdpsession")) { + ast_copy_string(sip_config.sdpsession, var->value, sizeof(sip_config.sdpsession)); + } else if (!strcasecmp(var->name, "sdpowner")) { + /* Field cannot contain spaces */ + if (!strchr(var->value, ' ')) { + ast_copy_string(sip_config.sdpowner, var->value, sizeof(sip_config.sdpowner)); + } else { + ast_log(LOG_WARNING, "'%s' must not contain spaces at line %d. Using default.\n", var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "allowtransfer")) { + sip_config.allowtransfer = ast_true(var->value); + } else if (!strcasecmp(var->name, "rtcachefriends")) { + ast_set2_flag(&sip_config.flags[1], ast_true(var->value), SIP_RTCACHEFRIENDS); + } else if (!strcasecmp(var->name, "rtsavesysname")) { + sip_config.rtsave_sysname = ast_true(var->value); + } else if (!strcasecmp(var->name, "rtsavepath")) { + sip_config.rtsave_path = ast_true(var->value); + } else if (!strcasecmp(var->name, "rtupdate")) { + sip_config.peer_rtupdate = ast_true(var->value); + } else if (!strcasecmp(var->name, "ignoreregexpire")) { + sip_config.ignore_regexpire = ast_true(var->value); + } else if (!strcasecmp(var->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(var->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", var->name, var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "timerb")) { + if (ast_parse_arg(var->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 value for timerb '%s'. Setting to default '%d'.\n", var->value, sip_config.timer_b); + } else { + timerb_set = TRUE; + } + } else if (!strcasecmp(var->name, "t1min")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.t1min, SIP_DEFAULT_T1MIN, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", var->name, var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "transport")) { + char *value = ast_strdupa(var->value); + char *option; + + sip_config.transports = sip_config.primary_transport = 0; + + while ((option = strsep(&value, ","))) { + 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, "'%s' is not a valid transport type. if no other is specified, udp will be used.\n", option); + } + + if (sip_config.primary_transport == 0) { + sip_config.primary_transport = sip_config.transports; + } + } + } else if (!strcasecmp(var->name, "tcpenable")) { + if (!ast_false(var->value)) { + ast_debug(2, "Enabling TCP socket for listening\n"); + sip_config.tcp_enabled = TRUE; + } + } else if (!strcasecmp(var->name, "tcpbindaddr")) { + if (ast_parse_arg(var->value, PARSE_ADDR, &sip_tcp_session.local_address)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", var->name, var->value, var->lineno); + } + + ast_debug(2, "Setting TCP socket address to %s\n", ast_sockaddr_stringify(&sip_tcp_session.local_address)); + } else if (!strcasecmp(var->name, "dynamic_exclude_static") || !strcasecmp(var->name, "dynamic_excludes_static")) { + sip_config.dynamic_exclude_static = ast_true(var->value); + } else if (!strcasecmp(var->name, "contactpermit") || !strcasecmp(var->name, "contactdeny") || !strcasecmp(var->name, "contactacl")) { + int ha_error = 0; + + ast_append_acl(var->name + 7, var->value, &sip_config.contact_acl, &ha_error, &acl_change_sub_needed); + + if (ha_error) { + ast_log(LOG_ERROR, "Bad ACL entry %s at line %d. Failing to load chan_sip.so\n", var->value, var->lineno); + return -1; + } + } else if (!strcasecmp(var->name, "rtautoclear")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.rtautoclear, 0, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", var->name, var->value, var->lineno); + } + + ast_set2_flag(&sip_config.flags[1], sip_config.rtautoclear || ast_true(var->value), SIP_RTAUTOCLEAR); + } else if (!strcasecmp(var->name, "usereqphone")) { + ast_set2_flag(&sip_config.flags[0], ast_true(var->value), SIP_USEREQPHONE); + } else if (!strcasecmp(var->name, "prematuremedia")) { + sip_config.prematuremediafilter = ast_true(var->value); + } else if (!strcasecmp(var->name, "relaxdtmf")) { + sip_config.relaxdtmf = ast_true(var->value); + } else if (!strcasecmp(var->name, "vmexten")) { + ast_copy_string(sip_config.vmexten, var->value, sizeof(sip_config.vmexten)); + } else if (!strcasecmp(var->name, "rtptimeout")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.rtptimeout, 0, 0, INT_MAX)) { + ast_log(LOG_WARNING, "'%s' is not a valid RTP hold time at line %d. Using default.\n", var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "rtpholdtimeout")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.rtpholdtimeout, 0, 0, INT_MAX)) { + ast_log(LOG_WARNING, "'%s' is not a valid RTP hold time at line %d. Using default.\n", var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "rtpkeepalive")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.rtpkeepalive, SIP_DEFAULT_RTPKEEPALIVE, 0, INT_MAX)) { + ast_log(LOG_WARNING, "'%s' is not a valid RTP keepalive time at line %d. Using default.\n", var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "compactheaders")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d (always disabled).\n", var->name, var->lineno); + } else if (!strcasecmp(var->name, "notifymimetype")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d.\n", var->name, var->lineno); + } else if (!strcasecmp(var->name, "directrtpsetup")) { + sip_config.directrtpsetup = ast_true(var->value); + } else if (!strcasecmp(var->name, "notifyringing")) { + sip_config.notifyringing = ast_true(var->value); + } else if (!strcasecmp(var->name, "notifyhold")) { + sip_config.notifyhold = ast_true(var->value); + } else if (!strcasecmp(var->name, "notifycid")) { + if (!strcasecmp(var->value, "ignore-context")) { + sip_config.notifycid = SIP_NOTIFYCID_IGNORE_CONTEXT; + } else { + sip_config.notifycid = ast_true(var->value) ? SIP_NOTIFYCID_ENABLED : SIP_NOTIFYCID_DISABLED; + } + } else if (!strcasecmp(var->name, "alwaysauthreject")) { + sip_config.alwaysauthreject = ast_true(var->value); + } else if (!strcasecmp(var->name, "auth_options_requests")) { + sip_config.auth_options_requests = ast_true(var->value); + } else if (!strcasecmp(var->name, "auth_message_requests")) { + sip_config.auth_message_requests = ast_true(var->value); + } else if (!strcasecmp(var->name, "accept_outofcall_message")) { + sip_config.accept_outofcall_message = ast_true(var->value); + } else if (!strcasecmp(var->name, "outofcall_message_context")) { + ast_copy_string(sip_config.messagecontext, var->value, sizeof(sip_config.messagecontext)); + } else if (!strcasecmp(var->name, "mohinterpret")) { + ast_copy_string(sip_config.mohinterpret, var->value, sizeof(sip_config.mohinterpret)); + } else if (!strcasecmp(var->name, "mohsuggest")) { + ast_copy_string(sip_config.mohsuggest, var->value, sizeof(sip_config.mohsuggest)); + } else if (!strcasecmp(var->name, "tonezone")) { + struct ast_tone_zone *zone; + + if (!(zone = ast_get_indication_zone(var->value))) { + ast_log(LOG_ERROR, "Unknown country code '%s' for tonezone in [general] at line %d. Check indications.conf for available country codes.\n", var->value, var->lineno); + } else { + ast_tone_zone_unref(zone); + ast_copy_string(sip_config.zone, var->value, sizeof(sip_config.zone)); + } + } else if (!strcasecmp(var->name, "language")) { + ast_copy_string(sip_config.language, var->value, sizeof(sip_config.language)); + } else if (!strcasecmp(var->name, "regcontext")) { + char *value = newcontexts; + + ast_copy_string(newcontexts, var->value, sizeof(newcontexts)); + /* Let's remove any contexts that are no longer defined in regcontext */ + sip_config_remove_stale_contexts(newcontexts, oldregcontext); + + /* Create contexts if they don't exist already */ + while ((context = strsep(&value, "&"))) { + ast_copy_string(sip_used_context, context, sizeof(sip_used_context)); + ast_context_find_or_create(NULL, NULL, context, "SIP"); + } + + ast_copy_string(sip_config.regcontext, var->value, sizeof(sip_config.regcontext)); + } else if (!strcasecmp(var->name, "regextenonqualify")) { + sip_config.regextenonqualify = ast_true(var->value); + } else if (!strcasecmp(var->name, "legacy_useroption_parsing")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d (always disabled).\n", var->name, var->lineno); + } else if (!strcasecmp(var->name, "send_diversion")) { + sip_config.send_diversion = ast_true(var->value); + } else if (!strcasecmp(var->name, "callerid")) { + ast_copy_string(sip_config.callerid, var->value, sizeof(sip_config.callerid)); + } else if (!strcasecmp(var->name, "mwi_from")) { + ast_copy_string(sip_config.mwi_from, var->value, sizeof(sip_config.mwi_from)); + } else if (!strcasecmp(var->name, "fromdomain")) { + char *fromdomainport; + + ast_copy_string(sip_config.fromdomain, var->value, sizeof(sip_config.fromdomain)); + + if ((fromdomainport = strchr(sip_config.fromdomain, ':'))) { + *fromdomainport++ = '\0'; + + if (!(sip_config.fromdomainport = sip_str2port(fromdomainport, 0))) { + ast_log(LOG_NOTICE, "'%s' is not a valid port number for fromdomain.\n", fromdomainport); + } + } else { + sip_config.fromdomainport = SIP_STANDARD_PORT; + } + } else if (!strcasecmp(var->name, "outboundproxy")) { + struct sip_proxy *proxy; + + if (ast_strlen_zero(var->value)) { + ast_log(LOG_WARNING, "no value given for outbound proxy on line %d of sip.conf\n", var->lineno); + continue; + } + + if (!(proxy = sip_proxy_build(var->value, var->lineno, &sip_config.outboundproxy))) { + ast_log(LOG_WARNING, "failure parsing the outbound proxy on line %d of sip.conf.\n", var->lineno); + continue; + } + } else if (!strcasecmp(var->name, "autocreatepeer")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d (always disabled).\n", var->name, var->lineno); + } else if (!strcasecmp(var->name, "match_auth_username")) { + sip_config.match_auth_username = ast_true(var->value); + } else if (!strcasecmp(var->name, "srvlookup")) { + sip_config.srvlookup = ast_true(var->value); + } else if (!strcasecmp(var->name, "pedantic")) { + sip_config.pedanticsipchecking = ast_true(var->value); + } else if (!strcasecmp(var->name, "maxexpiry")) { + if (ast_parse_arg(var->value, 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", var->name, var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "minexpiry")) { + if (ast_parse_arg(var->value, 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", var->name, var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "defaultexpiry")) { + if (ast_parse_arg(var->value, 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", var->name, var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "submaxexpiry")) { + if (ast_parse_arg(var->value, PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.max_subexpiry, SIP_DEFAULT_MAX_EXPIRY, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", var->name, var->value, var->lineno); + } else { + max_subexpiry_set = TRUE; + } + } else if (!strcasecmp(var->name, "subminexpiry")) { + if (ast_parse_arg(var->value, PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.min_subexpiry, SIP_DEFAULT_MIN_EXPIRY, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", var->name, var->value, var->lineno); + } else { + min_subexpiry_set = TRUE; + } + } else if (!strcasecmp(var->name, "mwiexpiry")) { + if (ast_parse_arg(var->value, 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", var->name, var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "tcpauthtimeout")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.tcpauthtimeout, SIP_DEFAULT_TCPAUTHTIMEOUT, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", var->name, var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "tcpauthlimit")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.tcpauthlimit, SIP_DEFAULT_TCPAUTHLIMIT, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", var->name, var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "sip_debug")) { + if (ast_true(var->value)) { + sip_debug |= SIP_DEBUG_CONFIG; + } + } else if (!strcasecmp(var->name, "dumphistory")) { + sip_config.dumphistory = ast_true(var->value); + } else if (!strcasecmp(var->name, "recordhistory")) { + sip_config.recordhistory = ast_true(var->value); + } else if (!strcasecmp(var->name, "registertimeout")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.reg_timeout, SIP_DEFAULT_REGISTRATION_TIMEOUT, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", var->name, var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "registerattempts")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.regattempts_max, 1, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", var->name, var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "register_retry_403")) { + sip_config.reg_retry_403 = ast_true(var->value); + } else if (!strcasecmp(var->name, "bindaddr") || !strcasecmp(var->name, "udpbindaddr")) { + if (ast_parse_arg(var->value, PARSE_ADDR, &sip_config.bindaddr)) { + ast_log(LOG_WARNING, "Invalid address: %s\n", var->value); + } + } else if (!strcasecmp(var->name, "localnet")) { + struct ast_ha *localaddr; + int ha_error = 0; + + if (!(localaddr = ast_append_ha("d", var->value, sip_config.localaddr, &ha_error))) { + ast_log(LOG_WARNING, "Invalid localnet value: %s\n", var->value); + } else { + sip_config.localaddr = localaddr; + } + + if (ha_error) { + ast_log(LOG_ERROR, "Bad localnet configuration value line %d : %s\n", var->lineno, var->value); + } + } else if (!strcasecmp(var->name, "mediaaddr")) { + if (ast_parse_arg(var->value, PARSE_ADDR, &sip_config.mediaaddr)) + ast_log(LOG_WARNING, "Invalid address for media_address keyword: %s\n", var->value); + } else if (!strcasecmp(var->name, "rtpbindaddr")) { + if (ast_parse_arg(var->value, PARSE_ADDR, &sip_config.rtpbindaddr)) { + ast_log(LOG_WARNING, "Invalid address for rtpbindaddr keyword: %s\n", var->value); + } + } else if (!strcasecmp(var->name, "externaddr") || !strcasecmp(var->name, "externip")) { + if (ast_parse_arg(var->value, PARSE_ADDR, &sip_config.externaddr)) { + ast_log(LOG_WARNING, "Invalid address for externaddr keyword: %s\n", var->value); + } + + sip_config.externexpire = 0; + } else if (!strcasecmp(var->name, "externhost")) { + ast_copy_string(sip_config.externhost, var->value, sizeof(sip_config.externhost)); + + if (ast_sockaddr_resolve_first_af(&sip_config.externaddr, sip_config.externhost, 0, AST_AF_INET)) { + ast_log(LOG_WARNING, "Invalid address for externhost keyword: %s\n", sip_config.externhost); + } + + sip_config.externexpire = time(NULL); + } else if (!strcasecmp(var->name, "externrefresh")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.externrefresh, 10, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid externrefresh value '%s', must be an integer >0 at line %d\n", var->value, var->lineno); + sip_config.externrefresh = 10; + } + } else if (!strcasecmp(var->name, "externtcpport")) { + if (!(sip_config.externtcpport = sip_str2port(var->value, 0))) { + ast_log(LOG_WARNING, "Invalid externtcpport value, must be a positive integer between 1 and 65535 at line %d\n", var->lineno); + } + } else if (!strcasecmp(var->name, "externtlsport")) { + if (!(sip_config.externtlsport = sip_str2port(var->value, 0))) { + ast_log(LOG_WARNING, "Invalid externtlsport value, must be a positive integer between 1 and 65535 at line %d\n", var->lineno); + } + } else if (!strcasecmp(var->name, "allow")) { + if (ast_format_cap_update_by_allow_disallow(sip_config.caps, var->value, TRUE)) { + ast_log(LOG_WARNING, "Codec configuration errors found at line %d: %s=%s\n", var->lineno, var->name, var->value); + } + } else if (!strcasecmp(var->name, "disallow")) { + if (ast_format_cap_update_by_allow_disallow(sip_config.caps, var->value, FALSE)) { + ast_log(LOG_WARNING, "Codec configuration errors found at line %d: %s=%s\n", var->lineno, var->name, var->value); + } + } else if (!strcasecmp(var->name, "preferred_codec_only")) { + ast_set2_flag(&sip_config.flags[1], ast_true(var->value), SIP_PREFERRED_CODEC); + } else if (!strcasecmp(var->name, "autoframing")) { + sip_config.autoframing = ast_true(var->value); + } else if (!strcasecmp(var->name, "allowexternaldomains")) { + sip_config.allow_external_domains = ast_true(var->value); + } else if (!strcasecmp(var->name, "autodomain")) { + auto_sip_domains = ast_true(var->value); + } else if (!strcasecmp(var->name, "domain")) { + char *domain = ast_strdupa(var->value); + char *context; + + if ((context = strchr(domain, ','))) { + *context++ = '\0'; + } + + if (ast_strlen_zero(context)) { + ast_debug(1, "No context specified at line %d for domain '%s'\n", var->lineno, var->value); + } + + if (ast_strlen_zero(domain)) { + ast_log(LOG_WARNING, "Empty domain specified at line %d\n", var->lineno); + } else { + sip_domain_build(domain, SIP_DOMAIN_CONFIG, context ? ast_strip(context) : ""); + } + } else if (!strcasecmp(var->name, "register")) { + sip_registry_build(var->value, var->lineno); + } else if (!strcasecmp(var->name, "mwi")) { + sip_mwi_subscription_build(var->value, var->lineno); + } else if (!strcasecmp(var->name, "tos_sip")) { + if (ast_str2tos(var->value, &sip_config.tos_sip)) { + ast_log(LOG_WARNING, "Invalid tos_sip value at line %d, refer to QoS documentation\n", var->lineno); + } + } else if (!strcasecmp(var->name, "tos_audio")) { + if (ast_str2tos(var->value, &sip_config.tos_audio)) { + ast_log(LOG_WARNING, "Invalid tos_audio value at line %d, refer to QoS documentation\n", var->lineno); + } + } else if (!strcasecmp(var->name, "tos_video")) { + if (ast_str2tos(var->value, &sip_config.tos_video)) { + ast_log(LOG_WARNING, "Invalid tos_video value at line %d, refer to QoS documentation\n", var->lineno); + } + } else if (!strcasecmp(var->name, "tos_text")) { + if (ast_str2tos(var->value, &sip_config.tos_text)) { + ast_log(LOG_WARNING, "Invalid tos_text value at line %d, refer to QoS documentation\n", var->lineno); + } + } else if (!strcasecmp(var->name, "cos_sip")) { + if (ast_str2cos(var->value, &sip_config.cos_sip)) { + ast_log(LOG_WARNING, "Invalid cos_sip value at line %d, refer to QoS documentation\n", var->lineno); + } + } else if (!strcasecmp(var->name, "cos_audio")) { + if (ast_str2cos(var->value, &sip_config.cos_audio)) { + ast_log(LOG_WARNING, "Invalid cos_audio value at line %d, refer to QoS documentation\n", var->lineno); + } + } else if (!strcasecmp(var->name, "cos_video")) { + if (ast_str2cos(var->value, &sip_config.cos_video)) { + ast_log(LOG_WARNING, "Invalid cos_video value at line %d, refer to QoS documentation\n", var->lineno); + } + } else if (!strcasecmp(var->name, "cos_text")) { + if (ast_str2cos(var->value, &sip_config.cos_text)) { + ast_log(LOG_WARNING, "Invalid cos_text value at line %d, refer to QoS documentation\n", var->lineno); + } + } else if (!strcasecmp(var->name, "bindport")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &bindport, SIP_STANDARD_PORT, 1, 65535)) { + ast_log(LOG_WARNING, "Invalid port number '%s' at line %d\n", var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "qualify")) { + if (!strcasecmp(var->value, "no")) { + sip_config.qualify_maxms = 0; + } else if (!strcasecmp(var->value, "yes")) { + sip_config.qualify_maxms = SIP_DEFAULT_QUALIFY_MAXMS; + } else if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.qualify_maxms, 0, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Qualification default should be 'yes', 'no', or a number of milliseconds at line %d of sip.conf\n", var->lineno); + } + } else if (!strcasecmp(var->name, "keepalive")) { + if (!strcasecmp(var->value, "no")) { + sip_config.keepalive = 0; + } else if (!strcasecmp(var->value, "yes")) { + sip_config.keepalive = SIP_DEFAULT_KEEPALIVE_INTERVAL; + } else if (ast_parse_arg(var->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 of sip.conf\n", var->lineno); + } + } else if (!strcasecmp(var->name, "qualifyfreq")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.qualify_freq, SIP_DEFAULT_QUALIFY_FREQ, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid qualifyfreq number '%s' at line %d\n", var->value, var->lineno); + } else { + sip_config.qualify_freq *= 1000; /* ms */ + } + } else if (!strcasecmp(var->name, "authfailureevents")) { + sip_config.authfailureevents = ast_true(var->value); + } else if (!strcasecmp(var->name, "maxcallbitrate")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.maxcallbitrate, SIP_DEFAULT_MAXCALLBITRATE, 1, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid %s '%s' at line %d\n", var->name, var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "matchexternaddrlocally") || !strcasecmp(var->name, "matchexterniplocally")) { + sip_config.matchexternaddrlocally = ast_true(var->value); + } else if (!strcasecmp(var->name, "session-timers")) { + if (!strcasecmp(var->value, "accept")) { + sip_config.stimer_mode = SIP_STIMER_MODE_ACCEPT; + } else if (!strcasecmp(var->value, "refuse")) { + sip_config.stimer_mode = SIP_STIMER_MODE_REFUSE; + } else if (!strcasecmp(var->value, "originate")) { + sip_config.stimer_mode = SIP_STIMER_MODE_ORIGINATE; + } else { + ast_log(LOG_WARNING, "Invalid session-timers '%s' at line %d\n", var->value, var->lineno); + sip_config.stimer_mode = SIP_STIMER_MODE_ACCEPT; + } + } else if (!strcasecmp(var->name, "session-expires")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.max_se, SIP_DEFAULT_MAX_SE, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid session-expires '%s' at line %d\n", var->value, var->lineno); + sip_config.max_se = SIP_DEFAULT_MAX_SE; + } + } else if (!strcasecmp(var->name, "session-minse")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.min_se, SIP_DEFAULT_MIN_SE, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid session-minse '%s' at line %d\n", var->value, var->lineno); + sip_config.min_se = SIP_DEFAULT_MIN_SE; + } + } else if (!strcasecmp(var->name, "session-refresher")) { + if (!strcasecmp(var->value, "uac")) { + sip_config.stimer_refresher = SIP_STIMER_REFRESHER_US; + } else if (!strcasecmp(var->value, "uas")) { + sip_config.stimer_refresher = SIP_STIMER_REFRESHER_THEM; + } else { + ast_log(LOG_WARNING, "Invalid session-refresher '%s' at line %d\n", var->value, var->lineno); + sip_config.stimer_refresher = SIP_STIMER_REFRESHER_THEM; + } + } else if (!strcasecmp(var->name, "storesipcause")) { + ast_log(LOG_WARNING, "Usage of SIP_CAUSE has been removed. Use HANGUPCAUSE instead.\n"); + } else if (!strcasecmp(var->name, "qualifygap")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.qualify_gap, SIP_DEFAULT_QUALIFY_GAP, 0, INT_MAX)) { + ast_log(LOG_WARNING, "Invalid qualifygap '%s' at line %d\n", var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "qualifypeers")) { + if (ast_parse_arg(var->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", var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "disallowed_methods")) { + sip_method_parse(&sip_config.disallowed_methods, var->value); + } else if (!strcasecmp(var->name, "shrinkcallerid")) { + if (ast_true(var->value)) { + sip_config.shrinkcallerid = TRUE; + } else if (ast_false(var->value)) { + sip_config.shrinkcallerid = FALSE; + } else { + ast_log(LOG_WARNING, "shrinkcallerid value %s is not valid at line %d.\n", var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "use_q850_reason")) { + ast_set2_flag(&sip_config.flags[1], ast_true(var->value), SIP_Q850_REASON); + } else if (!strcasecmp(var->name, "maxforwards")) { + if (ast_parse_arg(var->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE, &sip_config.max_forwards, SIP_DEFAULT_MAX_FORWARDS, 1, 255)) { + ast_log(LOG_WARNING, "'%s' is not a valid maxforwards value at line %d. Using default.\n", var->value, var->lineno); + sip_config.max_forwards = SIP_DEFAULT_MAX_FORWARDS; + } + } else if (!strcasecmp(var->name, "subscribe_network_change_event")) { + if (ast_true(var->value)) { + subscribe_network_change = TRUE; + } else if (ast_false(var->value)) { + subscribe_network_change = FALSE; + } else { + ast_log(LOG_WARNING, "subscribe_network_change_event value %s is not valid at line %d.\n", var->value, var->lineno); + } + } else if (!strcasecmp(var->name, "snom_aoc_enabled")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d (always disabled).\n", var->name, var->lineno); + } else if (!strcasecmp(var->name, "icesupport")) { + ast_set2_flag(&sip_config.flags[2], ast_true(var->value), SIP_ICE_SUPPORT); + } else if (!strcasecmp(var->name, "discard_remote_hold_retrieval")) { + ast_set2_flag(&sip_config.flags[2], ast_true(var->value), SIP_DISCARD_REMOTE_HOLD_RETRIEVAL); + } else if (!strcasecmp(var->name, "parkinglot")) { + ast_copy_string(sip_config.parkinglot, var->value, sizeof(sip_config.parkinglot)); + } else if (!strcasecmp(var->name, "refer_addheaders")) { + sip_config.refer_addheaders = ast_true(var->value); + } else if (!strcasecmp(var->name, "websocket_write_timeout")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d.\n", var->name, var->lineno); + } else if (!strcasecmp(var->name, "websocket_enabled")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d (always disabled).\n", var->name, var->lineno); + } else if (!strcasecmp(var->name, "recordonfeature") || !strcasecmp(var->name, "recordofffeature")) { + ast_log(LOG_WARNING, "'%s' is no longer supported at line %d (always disabled).\n", var->name, var->lineno); + } + } + + /* Override global defaults if setting found in general section */ + ast_copy_flags(&sip_config.flags[0], &setflags[0], mask[0].flags); + ast_copy_flags(&sip_config.flags[1], &setflags[1], mask[1].flags); + ast_copy_flags(&sip_config.flags[2], &setflags[2], mask[2].flags); + + /* 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_subexpiry = sip_config.min_expiry; + } + + if (!max_subexpiry_set) { + sip_config.max_subexpiry = sip_config.max_expiry; + } + + if (subscribe_network_change) { + sip_network_change_subscribe(); + } else { + sip_network_change_unsubscribe(); + } + + if (sip_config.timer_t1 < sip_config.t1min) { + ast_log(LOG_WARNING, "'t1min' (%d) cannot be greater than 't1timer' (%d). Resetting 't1timer' to the value of 't1min'\n", sip_config.t1min, sip_config.timer_t1); + sip_config.timer_t1 = sip_config.t1min; + } + + 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). (RFC 3261, 17.1.1.2)\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.t1min) { + ast_log(LOG_WARNING, "Timer B has been set lower than recommended (%d < 64 * timert1=%d). (RFC 3261, 17.1.1.2)\n", sip_config.timer_b, sip_config.timer_t1); + sip_config.timer_t1 = sip_config.t1min; + 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 (var = ast_variable_browse(config, "authentication"); var; var = var->next) { + /* Format for authentication is auth = username:password@realm */ + if (!strcasecmp(var->name, "auth")) { + sip_auth_build(&sip_auths, var->value, var->lineno); + } + } + + if (bindport) { + if (ast_sockaddr_port(&sip_config.bindaddr)) { + ast_log(LOG_WARNING, "bindport is also specified in bindaddr. Using %d.\n", bindport); + } + + ast_sockaddr_set_port(&sip_config.bindaddr, bindport); + } + + if (!ast_sockaddr_port(&sip_config.bindaddr)) { + ast_sockaddr_set_port(&sip_config.bindaddr, SIP_STANDARD_PORT); + } + + /* Set UDP address and open socket */ + ast_sockaddr_copy(&sip_internip, &sip_config.bindaddr); + + if (ast_find_ourip(&sip_internip, &sip_config.bindaddr, 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_bindaddr, &sip_config.bindaddr)) { + close(sip_socket_fd); + sip_socket_fd = -1; + } + + if (sip_socket_fd < 0) { + sip_socket_fd = socket(ast_sockaddr_is_ipv6(&sip_config.bindaddr) ? 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: */ + const 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.bindaddr) < 0) { + ast_log(LOG_WARNING, "Failed to bind to %s: %s\n", ast_sockaddr_stringify(&sip_config.bindaddr), strerror(errno)); + + close(sip_socket_fd); + sip_socket_fd = -1; + } else { + ast_verb(2, "SIP Listening on %s\n", ast_sockaddr_stringify(&sip_config.bindaddr)); + 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.bindaddr); + } + + 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) { + const 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.bindaddr); + 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) { + const 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, "friend")) { + if ((peer = sip_peer_build(category, ast_variable_browse(config, category), NULL, 0, 0))) { + 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, "PLEASE NOTE: Setting 'nat' for a peer that differs from the global setting can make\n"); + ast_log(LOG_WARNING, "the name of that peer discoverable by an attacker. Replies for non-existent peers\n"); + ast_log(LOG_WARNING, "will be sent to a different port than replies for an existing peer. If at all possible,\n"); + ast_log(LOG_WARNING, "use the global 'nat' setting and do not set 'nat' per peer.\n"); + ast_log(LOG_WARNING, "(config category '%s' global force_rport '%s' peer/user force_rport '%s')\n", + category, AST_CLI_YESNO(global_nat), AST_CLI_YESNO(peer_nat)); + } + + ao2_t_link(sip_peers, peer, "link peer into peers table"); + + if (!ast_sockaddr_isnull(&peer->addr)) { + ao2_t_link(sip_peers_by_addr, peer, "link peer into sip_peers_by_addr table"); + } + + ao2_t_cleanup(peer, "unref the result of the sip_peer_build call. Now, the links from the tables are the only ones left."); + peer_count++; + } + } else { + ast_log(LOG_WARNING, "Unknown type '%s' for '%s' in %s\n", type, category, "sip.conf"); + } + } + + /* 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.bindaddr) && !ast_sockaddr_is_any(&sip_config.bindaddr)) { + sip_domain_build(ast_sockaddr_stringify_addr(&sip_config.bindaddr), SIP_DOMAIN_AUTO, NULL); + } else if (!ast_sockaddr_isnull(&sip_internip) && !ast_sockaddr_is_any(&sip_internip)) { + /* Our internal IP address, if configured */ + sip_domain_build(ast_sockaddr_stringify_addr(&sip_internip), SIP_DOMAIN_AUTO, NULL); + } else { + ast_log(LOG_NOTICE, "Can't 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.bindaddr, &sip_tcp_session.local_address)) { + sip_domain_build(ast_sockaddr_stringify_addr(&sip_tcp_session.local_address), SIP_DOMAIN_AUTO, NULL); + } + + /* 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.bindaddr, &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), SIP_DOMAIN_AUTO, NULL); + } + + /* Our extern IP address, if configured */ + if (!ast_sockaddr_isnull(&sip_config.externaddr)) { + sip_domain_build(ast_sockaddr_stringify_addr(&sip_config.externaddr), SIP_DOMAIN_AUTO, NULL); + } + + /* Extern host name (NAT traversal support) */ + if (!ast_strlen_zero(sip_config.externhost)) { + sip_domain_build(sip_config.externhost, SIP_DOMAIN_AUTO, NULL); + } + + /* Our host name */ + if (!gethostname(host, sizeof(host))) { + sip_domain_build(host, SIP_DOMAIN_AUTO, NULL); + } + } + + /* Release configuration from memory */ + ast_config_destroy(config); + + /* Load the list of manual NOTIFY types to support */ + if (sip_notify_types) { + ast_config_destroy(sip_notify_types); + } + + ast_clear_flag(&loadflags, CONFIG_FLAG_FILEUNCHANGED); + + if ((sip_notify_types = ast_config_load("sip_notify.conf", loadflags)) == CONFIG_STATUS_FILEINVALID) { + ast_log(LOG_ERROR, "Contents of sip_notify.conf are invalid and cannot be parsed.\n"); + sip_notify_types = NULL; + } + + reload_end = time(0); + ast_debug(4, "SIP reload config done (%d secs)\n", (int) (reload_end - reload_start)); + + /* If an ACL change subscription is needed and doesn't exist, we need one. */ + if (acl_change_sub_needed) { + sip_acl_change_subscribe(); + } + + return 0; +} + +/* Destroy disused contexts between reloads Only used in sip_config_parse so the code for regcontext doesn't get ugly */ +static void sip_config_remove_stale_contexts(char *newcontexts, char *oldcontexts) +{ + char *oldcontext, *newcontext, *stalecontext, *sep, contexts[AST_MAX_CONTEXT]; + + while ((oldcontext = strsep(&oldcontexts, "&"))) { + stalecontext = NULL; + + ast_copy_string(contexts, newcontexts, sizeof(contexts)); + sep = contexts; + + while ((newcontext = strsep(&sep, "&"))) { + if (!strcmp(newcontext, oldcontext)) { + /* This is not the context you're looking for */ + stalecontext = NULL; + break; + } else if (strcmp(newcontext, oldcontext)) { + stalecontext = oldcontext; + } + + } + + ast_context_destroy_by_name(stalecontext, "SIP"); + } +} + +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(); +} + +/* Event callback which indicates we're fully booted */ +void sip_startup_event(void *data, struct stasis_subscription *sub, struct stasis_message *msg) +{ + struct ast_json_payload *payload; + const char *type; + + if (stasis_message_type(msg) != ast_manager_get_generic_type()) { + return; + } + + payload = stasis_message_data(msg); + type = ast_json_string_get(ast_json_object_get(payload->json, "type")); + + if (strcmp(type, "FullyBooted")) { + return; + } + + sip_module_notice(); + stasis_unsubscribe(sub); +} + diff -durN asterisk-22.2.0.orig/channels/sip/dialplan_apps.c asterisk-22.2.0/channels/sip/dialplan_apps.c --- asterisk-22.2.0.orig/channels/sip/dialplan_apps.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/dialplan_apps.c 2025-02-18 17:14:46.790903124 +1300 @@ -0,0 +1,757 @@ +/* + * 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 "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/pvt.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 *chan, const char *data) +{ + struct sip_pvt *pvt; + const char *mode = data; + + if (!data) { + ast_log(LOG_WARNING, "This application requires the argument: inband, rfc2833\n"); + return 0; + } + + ast_channel_lock(chan); + + if (ast_channel_tech(chan) != &sip_tech) { + ast_log(LOG_WARNING, "Call this application only on SIP incoming calls\n"); + ast_channel_unlock(chan); + + return 0; + } + + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_channel_unlock(chan); + return 0; + } + + ao2_lock(pvt); + + if (!strcasecmp(mode, "rfc2833")) { + ast_clear_flag(&pvt->flags[0], SIP_DTMF); + ast_set_flag(&pvt->flags[0], SIP_DTMF_RFC2833); + + pvt->jointnoncodeccapability |= AST_RTP_DTMF; + } else if (!strcasecmp(mode, "inband")) { + ast_clear_flag(&pvt->flags[0], SIP_DTMF); + ast_set_flag(&pvt->flags[0], SIP_DTMF_INBAND); + + pvt->jointnoncodeccapability &= ~AST_RTP_DTMF; + } else { + ast_log(LOG_WARNING, "I don't know about this dtmf mode: %s\n", mode); + } + + if (pvt->rtp) { + ast_rtp_instance_set_prop(pvt->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833); + } + + if (ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_INBAND || ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + sip_pvt_set_dsp_detect(pvt, TRUE); + } else { + sip_pvt_set_dsp_detect(pvt, FALSE); + } + + ao2_unlock(pvt); + ast_channel_unlock(chan); + + return 0; +} + +/* Add a SIP header to an outbound INVITE */ +int sip_app_addheader(struct ast_channel *chan, const char *data) +{ + int count = 0; + int ok = FALSE; + char name[30]; + char *value; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "This application requires the argument: Header\n"); + return 0; + } + + ast_channel_lock(chan); + + /* Check for headers */ + while (!ok && count <= 50) { + count++; + snprintf(name, sizeof(name), "__SIPADDHEADER%.2d", count); + + /* Compare without the leading underscores */ + if ((pbx_builtin_getvar_helper(chan, (const char *) name + 2) == (const char *) NULL)) { + ok = TRUE; + } + } + + if (ok) { + size_t len = strlen(data); + + value = ast_alloca(len + 1); + ast_get_encoded_str(data, value, len + 1); + pbx_builtin_setvar_helper(chan, name, value); + + if (sip_debug) { + 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(chan); + + return 0; +} + +/* Remove SIP headers added previously with SipAddHeader application */ +int sip_app_removeheader(struct ast_channel *chan, const char *data) +{ + struct ast_var_t *var; + struct varshead *head; + int removeall = FALSE; + char *value = (char *) data; + int len; + + len = strlen(value); + + if (!len) { + removeall = TRUE; + } + + ast_channel_lock(chan); + head = ast_channel_varshead(chan); + + AST_LIST_TRAVERSE_SAFE_BEGIN(head, var, entries) { + if (strncmp(ast_var_name(var), "SIPADDHEADER", 12) == 0) { + if (removeall || !strncasecmp(ast_var_value(var), value, len)) { + if (sip_debug) { + 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(chan); + + 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_INCLUDEBUSY = 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_INCLUDEBUSY), + 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 *chan, const char *data) +{ + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(peernames); + AST_APP_ARG(options); + ); + struct ast_flags options = {0}; + char *option_args[APP_CISCOPAGE_ARG_ARRAY_SIZE]; + char *parse, *peername, *codec, display[64]; + int volume, port, multicast, includebusy, offhook, beep; + struct ast_format *format; + AST_LIST_HEAD_NOLOCK(, sip_page_target) targets; + struct sip_page_target *target; + struct ast_str *content = NULL; + struct ast_rtp_instance *mrtp = NULL; + struct ast_sockaddr ouraddr; + int res = -1; + + 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.peernames)) { + 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); + } + + if (ast_test_flag(&options, APP_CISCOPAGE_MULTICAST) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_MULTICAST])) { + if (!ast_sockaddr_parse(&ouraddr, 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(&ouraddr)) { + 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); + includebusy = ast_test_flag(&options, APP_CISCOPAGE_INCLUDEBUSY); + offhook = ast_test_flag(&options, APP_CISCOPAGE_OFFHOOK); + + format = ast_channel_readformat(chan); + + 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; + } + + content = ast_str_create(8192); + AST_LIST_HEAD_INIT_NOLOCK(&targets); + + while ((peername = strsep(&args.peernames, "&"))) { + struct sip_peer *peer; + struct sip_pvt *pvt; + struct ast_rtp_instance *rtp = NULL; + struct ast_sockaddr theiraddr; + + if (!(peer = sip_peer_find(peername, NULL, TRUE, FALSE, 0))) { + ast_log(LOG_ERROR, "No such peer '%s'\n", peername); + 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, "unref peer"); + + continue; + } + + if (ast_sockaddr_isnull(&peer->addr)) { + ast_log(LOG_ERROR, "Peer '%s' is not registered\n", peer->name); + ao2_t_cleanup(peer, "unref peer"); + + continue; + } + + if ((peer->offhook || peer->ringing || peer->inuse || peer->donotdisturb) && !includebusy) { + ao2_t_cleanup(peer, "unref peer"); + continue; + } + + if (!multicast) { + /* We only need the socket type for sip_pvt_set_ouraddrfor */ + struct sip_pvt temp_pvt = {.socket.type = peer->socket.type}; + + sip_pvt_set_ouraddrfor(&temp_pvt, &peer->addr, &ouraddr); + ast_sockaddr_copy(&theiraddr, &peer->addr); + ast_sockaddr_set_port(&theiraddr, port); + + if (!(rtp = ast_rtp_instance_new(peer->engine, sip_sched_context, &ouraddr, NULL))) { + ao2_t_cleanup(peer, "unref peer"); + goto cleanup; + } + + ast_rtp_instance_set_write_format(rtp, ast_channel_readformat(chan)); + ast_rtp_instance_set_remote_address(rtp, &theiraddr); + + ast_rtp_instance_set_qos(rtp, sip_config.tos_audio, sip_config.cos_audio, "SIP RTP"); + ast_rtp_instance_activate(rtp); + } + + if (!(pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + ast_log(LOG_ERROR, "Unable to build sip pvt data for refer (memory/socket error)\n"); + ao2_t_cleanup(peer, "unref peer"); + + if (rtp) { + ast_rtp_instance_destroy(rtp); + } + continue; + } + + sip_socket_set_transport(&pvt->socket, 0); + + if (sip_pvt_build_from_peer(pvt, peer)) { + sip_pvt_unlink(pvt); + + ao2_t_cleanup(pvt, "sip_pvt_build_from_peer failed. unref dialog"); + ao2_t_cleanup(peer, "unref 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(&ouraddr, 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(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(pvt, "drop pvt"); + + if (!(target = ast_calloc(1, sizeof(*target)))) { + ao2_t_cleanup(peer, "unref 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)) { + ast_free(content); + return -1; + } + + if (multicast) { + ast_sockaddr_set_port(&ouraddr, port); + + if (!(mrtp = ast_rtp_instance_new("multicast", sip_sched_context, &sip_config.bindaddr, "basic"))) { + goto cleanup; + } + + ast_rtp_instance_set_write_format(mrtp, ast_channel_readformat(chan)); + ast_rtp_instance_set_remote_address(mrtp, &ouraddr); + + ast_rtp_instance_set_qos(mrtp, sip_config.tos_audio, sip_config.cos_audio, "SIP RTP"); + ast_rtp_instance_activate(mrtp); + } + + if (ast_channel_state(chan) != AST_STATE_UP) { + if ((res = ast_answer(chan))) { + goto cleanup; + } + } + + /* Wait 500ms for phones to accept the media stream request */ + if ((res = ast_safe_sleep(chan, 500))) { + goto cleanup; + } + + for (;;) { + struct ast_frame *frame; + + if (ast_waitfor(chan, 10000) < 1) { + break; + } + frame = ast_read(chan); + + 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 (mrtp) { + ast_rtp_instance_write(mrtp, 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_pvt *pvt; + + if (!(pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + ast_log(LOG_ERROR, "Unable to build sip pvt data for refer (memory/socket error)\n"); + } else { + sip_socket_set_transport(&pvt->socket, 0); + + if (sip_pvt_build_from_peer(pvt, target->peer)) { + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "sip_pvt_build_from_peer failed. unref dialog"); + pvt = NULL; + } + } + + if (pvt) { + 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(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(pvt, "drop pvt"); + } + + 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, "unref peer"); + + if (target->rtp) { + ast_rtp_instance_destroy(target->rtp); + } + + ast_free(target); + } + + if (mrtp) { + ast_rtp_instance_destroy(mrtp); + } + + ast_free(content); + + return res; +} diff -durN asterisk-22.2.0.orig/channels/sip/dialplan_funcs.c asterisk-22.2.0/channels/sip/dialplan_funcs.c --- asterisk-22.2.0.orig/channels/sip/dialplan_funcs.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/dialplan_funcs.c 2025-02-18 17:14:46.792903071 +1300 @@ -0,0 +1,897 @@ +/* + * 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). + + + Extension activated at registration. + + + 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 "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/pvt.h" +#include "include/config.h" +#include "include/chan_tech.h" +#include "include/dialplan_funcs.h" + +int sip_func_channel_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t buflen) +{ + struct sip_pvt *pvt; + char *parse = ast_strdupa(data); + int res = 0; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(param); + AST_APP_ARG(type); + AST_APP_ARG(field); + ); + + /* Check for zero arguments */ + if (ast_strlen_zero(parse)) { + ast_log(LOG_ERROR, "Cannot call %s without arguments\n", function); + return -1; + } + + AST_STANDARD_APP_ARGS(args, parse); + + /* Sanity check */ + if (ast_channel_tech(chan) != &sip_tech) { + ast_log(LOG_ERROR, "Cannot call %s on a non-SIP channel\n", function); + return 0; + } + + if (!(pvt = ast_channel_tech_pvt(chan))) { + return -1; + } + + memset(buf, 0, buflen); + + if (!strcasecmp(args.param, "peerip")) { + ast_copy_string(buf, ast_sockaddr_isnull(&pvt->sa) ? "" : ast_sockaddr_stringify_addr(&pvt->sa), buflen); + } else if (!strcasecmp(args.param, "recvip")) { + ast_copy_string(buf, ast_sockaddr_isnull(&pvt->recv) ? "" : ast_sockaddr_stringify_addr(&pvt->recv), buflen); + } else if (!strcasecmp(args.param, "recvport")) { + ast_copy_string(buf, ast_sockaddr_isnull(&pvt->recv) ? "" : ast_sockaddr_stringify_port(&pvt->recv), buflen); + } else if (!strcasecmp(args.param, "from")) { + ast_copy_string(buf, pvt->from, buflen); + } else if (!strcasecmp(args.param, "uri")) { + ast_copy_string(buf, pvt->uri, buflen); + } else if (!strcasecmp(args.param, "ruri")) { + if (pvt->initreq.data) { + char *tmpruri = SIP_REQUEST_PART(&pvt->initreq, rlpart2); + + ast_copy_string(buf, tmpruri, buflen); + } else { + return -1; + } + } else if (!strcasecmp(args.param, "useragent")) { + ast_copy_string(buf, pvt->useragent, buflen); + } else if (!strcasecmp(args.param, "peername")) { + ast_copy_string(buf, pvt->peername, buflen); + } else if (!strcasecmp(args.param, "t38passthrough")) { + ast_copy_string(buf, (pvt->t38.state == SIP_T38_DISABLED) ? "0" : "1", buflen); + } else if (!strcasecmp(args.param, "rtpdest")) { + struct ast_sockaddr addr; + struct ast_rtp_instance *stream; + + if (ast_strlen_zero(args.type)) { + args.type = "audio"; + } + + if (!strcasecmp(args.type, "audio")) { + stream = pvt->rtp; + } else if (!strcasecmp(args.type, "video")) { + stream = pvt->vrtp; + } else if (!strcasecmp(args.type, "text")) { + stream = pvt->trtp; + } else { + return -1; + } + + /* Return 0 to suppress a console warning message */ + if (!stream) { + return 0; + } + + ast_rtp_instance_get_remote_address(stream, &addr); + snprintf(buf, buflen, "%s", ast_sockaddr_stringify(&addr)); + } else if (!strcasecmp(args.param, "rtpsource")) { + struct ast_sockaddr sa; + struct ast_rtp_instance *stream; + + if (ast_strlen_zero(args.type)) + args.type = "audio"; + + if (!strcasecmp(args.type, "audio")) { + stream = pvt->rtp; + } else if (!strcasecmp(args.type, "video")) { + stream = pvt->vrtp; + } else if (!strcasecmp(args.type, "text")) { + stream = pvt->trtp; + } else { + return -1; + } + + /* Return 0 to suppress a console warning message */ + if (!stream) { + return 0; + } + + ast_rtp_instance_get_local_address(stream, &sa); + + if (ast_sockaddr_isnull(&sa)) { + struct ast_sockaddr dest_sa; + + ast_rtp_instance_get_remote_address(stream, &dest_sa); + ast_ouraddrfor(&dest_sa, &sa); + } + + snprintf(buf, buflen, "%s", ast_sockaddr_stringify(&sa)); + } else if (!strcasecmp(args.param, "rtpqos")) { + struct ast_rtp_instance *rtp = NULL; + + if (ast_strlen_zero(args.type)) { + args.type = "audio"; + } + + if (!strcasecmp(args.type, "audio")) { + rtp = pvt->rtp; + } else if (!strcasecmp(args.type, "video")) { + rtp = pvt->vrtp; + } else if (!strcasecmp(args.type, "text")) { + rtp = pvt->trtp; + } else { + return -1; + } + + if (ast_strlen_zero(args.field) || !strcasecmp(args.field, "all")) { + char quality_buf[AST_MAX_USER_FIELD]; + + if (!ast_rtp_instance_get_quality(rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf))) { + return -1; + } + + ast_copy_string(buf, quality_buf, buflen); + + return res; + } else { + struct ast_rtp_instance_stats stats; + int i; + struct { + const char *name; + enum {INT, DBL} type; + union { + unsigned int *i4; + double *d8; + }; + } fields[] = { + {"txcount", INT, {.i4 = &stats.txcount}}, + {"rxcount", INT, {.i4 = &stats.rxcount}}, + {"txjitter", DBL, {.d8 = &stats.txjitter}}, + {"rxjitter", DBL, {.d8 = &stats.rxjitter}}, + {"remote_maxjitter", DBL, {.d8 = &stats.remote_maxjitter}}, + {"remote_minjitter", DBL, {.d8 = &stats.remote_minjitter}}, + {"remote_normdevjitter", DBL, {.d8 = &stats.remote_normdevjitter}}, + {"remote_stdevjitter", DBL, {.d8 = &stats.remote_stdevjitter}}, + {"local_maxjitter", DBL, {.d8 = &stats.local_maxjitter}}, + {"local_minjitter", DBL, {.d8 = &stats.local_minjitter}}, + {"local_normdevjitter", DBL, {.d8 = &stats.local_normdevjitter}}, + {"local_stdevjitter", DBL, {.d8 = &stats.local_stdevjitter}}, + {"txploss", INT, {.i4 = &stats.txploss}}, + {"rxploss", INT, {.i4 = &stats.rxploss}}, + {"remote_maxrxploss", DBL, {.d8 = &stats.remote_maxrxploss,}}, + {"remote_minrxploss", DBL, {.d8 = &stats.remote_minrxploss}}, + {"remote_normdevrxploss", DBL, {.d8 = &stats.remote_normdevrxploss}}, + {"remote_stdevrxploss", DBL, {.d8 = &stats.remote_stdevrxploss}}, + {"local_maxrxploss", DBL, {.d8 = &stats.local_maxrxploss}}, + {"local_minrxploss", DBL, {.d8 = &stats.local_minrxploss}}, + {"local_normdevrxploss", DBL, {.d8 = &stats.local_normdevrxploss}}, + {"local_stdevrxploss", DBL, {.d8 = &stats.local_stdevrxploss}}, + {"rtt", DBL, {.d8 = &stats.rtt}}, + {"maxrtt", DBL, {.d8 = &stats.maxrtt}}, + {"minrtt", DBL, {.d8 = &stats.minrtt}}, + {"normdevrtt", DBL, {.d8 = &stats.normdevrtt}}, + {"stdevrtt", DBL, {.d8 = &stats.stdevrtt}}, + {"local_ssrc", INT, {.i4 = &stats.local_ssrc}}, + {"remote_ssrc", INT, {.i4 = &stats.remote_ssrc}}, + {NULL}, + }; + + if (ast_rtp_instance_get_stats(rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) { + return -1; + } + + for (i = 0; !ast_strlen_zero(fields[i].name); i++) { + if (!strcasecmp(args.field, fields[i].name)) { + if (fields[i].type == INT) { + snprintf(buf, buflen, "%u", *fields[i].i4); + } else { + snprintf(buf, buflen, "%f", *fields[i].d8); + } + + return 0; + } + } + + ast_log(LOG_WARNING, "Unrecognized argument '%s' to %s\n", data, function); + return -1; + } + } else if (!strcasecmp(args.param, "secure_signaling")) { + snprintf(buf, buflen, "%s", pvt->socket.type == AST_TRANSPORT_TLS ? "1" : "0"); + } else if (!strcasecmp(args.param, "secure_media")) { + snprintf(buf, buflen, "%s", pvt->srtp ? "1" : "0"); + } else { + res = -1; + } + + return res; +} + +struct ast_custom_function sip_func_checkdomain = { + .name = "CHECKSIPDOMAIN", + .read = sip_func_checkdomain_read, +}; + +/* Dial plan function to check if domain is local */ +int sip_func_checkdomain_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t buflen) +{ + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "CHECKSIPDOMAIN requires an argument - A domain name\n"); + return -1; + } + + if (sip_domain_check(data, NULL, 0)) { + ast_copy_string(buf, data, buflen); + } else { + buf[0] = '\0'; + } + + return 0; +} + +struct ast_custom_function sip_func_header = { + .name = "SIP_HEADER", + .read = sip_func_header_read, +}; + +/* Read SIP header (dialplan function) */ +int sip_func_header_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t buflen) +{ + struct sip_pvt *pvt; + const char *content = NULL; + char *parse = ast_strdupa(data); + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(header); + AST_APP_ARG(number); + ); + int i, number, start = 0; + + if (!chan) { + 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(chan); + + if (ast_channel_tech(chan) != &sip_tech) { + ast_log(LOG_WARNING, "This function can only be used on SIP channels.\n"); + ast_channel_unlock(chan); + + return -1; + } + + AST_STANDARD_APP_ARGS(args, parse); + + if (!args.number) { + number = 1; + } else { + sscanf(args.number, "%30d", &number); + + if (number < 1) { + number = 1; + } + } + + /* If there is no private structure, this channel is no longer alive */ + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_channel_unlock(chan); + return -1; + } + + for (i = 0; i < number; i++) { + content = sip_request_get_header_full(&pvt->initreq, args.header, &start); + } + + if (ast_strlen_zero(content)) { + ast_channel_unlock(chan); + return -1; + } + + ast_copy_string(buf, content, buflen); + ast_channel_unlock(chan); + + 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 *chan, const char *function, char *data, struct ast_str **buf, ssize_t maxlen) +{ + int i; + struct sip_pvt *pvt; + char *parse = ast_strdupa(data); + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(pattern); + ); + + if (!chan) { + return -1; + } + + ast_channel_lock(chan); + + if (ast_channel_tech(chan) != &sip_tech) { + ast_log(LOG_WARNING, "This function can only be used on SIP channels.\n"); + ast_channel_unlock(chan); + return -1; + } + + if (!(pvt = ast_channel_tech_pvt(chan))) { + ast_channel_unlock(chan); + return -1; + } + + AST_STANDARD_APP_ARGS(args, parse); + + if (!args.pattern || strcmp(args.pattern, "*") == 0) { + args.pattern = ""; + } + + for (i = 0; i < pvt->initreq.headers; i++) { + const char *header = SIP_REQUEST_PART(&pvt->initreq, header[i]); + + if (ast_begins_with(header, args.pattern)) { + int headerlen = strcspn(header, " \t:,"); /* Comma will break our logic, and illegal per RFC. */ + const char *sep = ast_skip_blanks(header + headerlen); + + if (headerlen > 0 && *sep == ':') { /* Header is malformed otherwise! */ + const char *headers; + + /* Has the same header been already added? */ + headers = ast_str_buffer(*buf); + + while ((headers = strstr(headers, header)) != NULL) { + /* Found suffix, but is it the full token? */ + if ((headers == ast_str_buffer(*buf) || headers[-1] == ',') && headers[headerlen] == ',') { + break; + } + + /* Only suffix matched, go on with the search after the comma. */ + headers += headerlen + 1; + } + + /* s is null iff not broken from the loop, hence header not yet added. */ + if (headers == NULL) { + ast_str_append(buf, maxlen, "%s,", header); + } + } + } + } + + ast_str_truncate(*buf, -1); /* Trim the last comma. Safe if empty. */ + ast_channel_unlock(chan); + + return 0; +} + +struct ast_custom_function sip_func_peer = { + .name = "SIPPEER", + .read = sip_func_peer_read, + .write = sip_func_peer_write +}; + +/* ${SIPPEER()} Dialplan function - reads peer data */ +int sip_func_peer_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t buflen) +{ + struct sip_peer *peer; + char *colname; + + if ((colname = strchr(data, ','))) { + *colname++ = '\0'; + } else { + colname = "ip"; + } + + if (!(peer = sip_peer_find(data, NULL, TRUE, FALSE, 0))) { + return -1; + } + + if (!strcasecmp(colname, "ip")) { + ast_copy_string(buf, ast_sockaddr_stringify_addr(&peer->addr), buflen); + } else if (!strcasecmp(colname, "port")) { + snprintf(buf, buflen, "%d", ast_sockaddr_port(&peer->addr)); + } else if (!strcasecmp(colname, "status")) { + sip_peer_get_status(peer, buf, buflen); + } else if (!strcasecmp(colname, "language")) { + ast_copy_string(buf, peer->language, buflen); + } else if (!strcasecmp(colname, "regexten")) { + ast_copy_string(buf, peer->regexten, buflen); + } else if (!strcasecmp(colname, "limit")) { + snprintf(buf, buflen, "%d", peer->call_limit); + } else if (!strcasecmp(colname, "busylevel")) { + snprintf(buf, buflen, "%d", peer->busy_level); + } else if (!strcasecmp(colname, "curcalls")) { + snprintf(buf, buflen, "%d", peer->inuse); + } else if (!strcasecmp(colname, "maxforwards")) { + snprintf(buf, buflen, "%d", peer->maxforwards); + } else if (!strcasecmp(colname, "accountcode")) { + ast_copy_string(buf, peer->accountcode, buflen); + } else if (!strcasecmp(colname, "callgroup")) { + ast_print_group(buf, buflen, peer->callgroup); + } else if (!strcasecmp(colname, "pickupgroup")) { + ast_print_group(buf, buflen, peer->pickupgroup); + } else if (!strcasecmp(colname, "namedcallgroup")) { + struct ast_str *groups = ast_str_create(1024); + + if (groups) { + ast_copy_string(buf, ast_print_namedgroups(&groups, peer->named_callgroups), buflen); + ast_free(groups); + } + } else if (!strcasecmp(colname, "namedpickupgroup")) { + struct ast_str *groups = ast_str_create(1024); + + if (groups) { + ast_copy_string(buf, ast_print_namedgroups(&groups, peer->named_pickupgroups), buflen); + ast_free(groups); + } + } else if (!strcasecmp(colname, "useragent")) { + ast_copy_string(buf, peer->useragent, buflen); + } else if (!strcasecmp(colname, "mailbox")) { + struct ast_str *mailboxes = ast_str_alloca(512); + + sip_peer_get_mailboxes(peer, &mailboxes); + ast_copy_string(buf, ast_str_buffer(mailboxes), buflen); + } else if (!strcasecmp(colname, "context")) { + ast_copy_string(buf, peer->context, buflen); + } else if (!strcasecmp(colname, "expire")) { + snprintf(buf, buflen, "%d", peer->expire); + } else if (!strcasecmp(colname, "dynamic")) { + ast_copy_string(buf, peer->host_dynamic ? "yes" : "no", buflen); + } else if (!strcasecmp(colname, "callerid_name")) { + ast_copy_string(buf, peer->cid_name, buflen); + } else if (!strcasecmp(colname, "callerid_num")) { + ast_copy_string(buf, peer->cid_num, buflen); + } else if (!strcasecmp(colname, "codecs")) { + struct ast_str *codecs = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + + ast_format_cap_get_names(peer->caps, &codecs); + ast_copy_string(buf, ast_str_buffer(codecs), buflen); + } else if (!strcasecmp(colname, "encryption")) { + snprintf(buf, buflen, "%u", ast_test_flag(&peer->flags[0], SIP_USE_SRTP)); + } else if (!strncasecmp(colname, "chanvar[", 8)) { + char *name; + const char *value; + + name = colname + 8; + name = strsep(&name, "]"); + + if ((value = ast_variable_find_in_list(peer->chanvars, name))) { + ast_copy_string(buf, value, buflen); + } + } else if (!strncasecmp(colname, "codec[", 6)) { + char *codecnum; + struct ast_format *format; + + codecnum = colname + 6; /* move past the '[' */ + codecnum = strsep(&codecnum, "]"); /* trim trailing ']' if any */ + + format = ast_format_cap_get_format(peer->caps, atoi(codecnum)); + + if (format) { + ast_copy_string(buf, ast_format_get_name(format), buflen); + ao2_ref(format, -1); + } else { + buf[0] = '\0'; + } + } else if (!strncasecmp(colname, "vmexten", 7)) { + ast_copy_string(buf, peer->vmexten, buflen); + } else if (!strncasecmp(colname, "donotdisturb", 12)) { + ast_copy_string(buf, peer->donotdisturb ? "yes" : "no", buflen); + } else if (!strncasecmp(colname, "callforward", 11)) { + ast_copy_string(buf, peer->callforward, buflen); + } else if (!strncasecmp(colname, "huntgroup", 9)) { + ast_copy_string(buf, peer->huntgroup ? "yes" : "no", buflen); + } else if (!strncasecmp(colname, "regcallid", 9)) { + ast_copy_string(buf, peer->regcallid, buflen); + } else if (!strncasecmp(colname, "ciscodevicename", 15)) { + ast_copy_string(buf, peer->cisco_devicename, buflen); + } else if (!strncasecmp(colname, "ciscolineindex", 14)) { + snprintf(buf, buflen, "%d", peer->cisco_lineindex); + } else { + buf[0] = '\0'; + } + + ao2_t_cleanup(peer, "ao2_t_cleanup from sip_func_peer_read just before return"); + + return 0; +} + +int sip_func_peer_write(struct ast_channel *chan, const char *function, char *data, const char *value) +{ + struct sip_peer *peer; + struct sip_alias *alias; + char *colname; + + if ((colname = strchr(data, ','))) { + *colname++ = '\0'; + } else { + colname = ""; + } + + if (!(peer = sip_peer_find(data, NULL, TRUE, FALSE, 0))) { + return -1; + } + + if (!strncasecmp(colname, "donotdisturb", 12)) { + peer->donotdisturb = 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->donotdisturb = peer->donotdisturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", alias->peer->name); + } + } + + if (!peer->is_realtime) { + ast_db_put("SIP/DoNotDisturb", peer->name, peer->donotdisturb ? "yes" : "no"); + } else if (sip_config.peer_rtupdate && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name, "donotdisturb", peer->donotdisturb ? "yes" : "no", SENTINEL); + + } + + sip_peer_send_donotdisturb(peer); + } else if (!strncasecmp(colname, "huntgroup", 9)) { + peer->huntgroup = ast_true(value); + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (alias->peer) { + alias->peer->huntgroup = peer->huntgroup; + } + } + + if (!peer->is_realtime) { + ast_db_put("SIP/HuntGroup", peer->name, peer->huntgroup ? "yes" : "no"); + } else if (sip_config.peer_rtupdate && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name, "huntgroup", peer->huntgroup ? "yes" : "no", SENTINEL); + } + + sip_peer_send_huntgroup(peer); + } else if (!strncasecmp(colname, "callforward", 11)) { + ast_string_field_set(peer, callforward, value); + + if (!peer->is_realtime) { + if (ast_strlen_zero(peer->callforward)) { + ast_db_del("SIP/CallForward", peer->name); + } else { + ast_db_put("SIP/CallForward", peer->name, peer->callforward); + } + } else if (sip_config.peer_rtupdate && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name, "callforward", peer->callforward, SENTINEL); + } + + sip_peer_send_callforward(peer); + } + + ao2_t_cleanup(peer, "ao2_t_cleanup from sip_func_peer_write just before return"); + + return 0; +} diff -durN asterisk-22.2.0.orig/channels/sip/domain.c asterisk-22.2.0/channels/sip/domain.c --- asterisk-22.2.0.orig/channels/sip/domain.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/domain.c 2025-02-18 17:14:46.793903044 +1300 @@ -0,0 +1,123 @@ +/* + * 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 *name, int mode, const char *context) +{ + struct sip_domain *domain; + + if (ast_strlen_zero(name)) { + ast_log(LOG_WARNING, "Zero length domain.\n"); + return -1; + } + + if (!(domain = ast_calloc(1, sizeof(*domain)))) { + return 0; + } + + 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); + + if (sip_debug) { + ast_debug(1, "Added local SIP domain '%s'\n", name); + } + + return -1; +} + +/* 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 len) +{ + struct sip_domain *domain; + int res = 0; + + AST_LIST_LOCK(&sip_domains); + + AST_LIST_TRAVERSE(&sip_domains, domain, next) { + if (strcasecmp(domain->name, name)) { + continue; + } + + if (len && !ast_strlen_zero(domain->context)) { + ast_copy_string(context, domain->context, len); + } + + res = 1; + break; + } + + AST_LIST_UNLOCK(&sip_domains); + + return res; +} diff -durN asterisk-22.2.0.orig/channels/sip/events.c asterisk-22.2.0/channels/sip/events.c --- asterisk-22.2.0.orig/channels/sip/events.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/events.c 2025-02-18 17:14:46.794903018 +1300 @@ -0,0 +1,129 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +/*** MODULEINFO + extended + ***/ + +#include "asterisk.h" +#include "asterisk/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 *sub, struct stasis_message *msg); +static void sip_acl_change_event(void *data, struct stasis_subscription *sub, struct stasis_message *msg); + +static int sip_network_change_sched_id = -1; +static struct stasis_subscription *sip_network_change_sub; /* subscription id for network change events */ +static struct stasis_subscription *sip_acl_change_sub; /* subscription id for named ACL system change events */ + +/* 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 *sub, struct stasis_message *msg) +{ + /* This callback is only concerned with network change messages from the system topic. */ + if (stasis_message_type(msg) != 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_sub) { + sip_network_change_sub = stasis_subscribe(ast_system_topic(), sip_network_change_event, NULL); + + stasis_subscription_accept_message_type(sip_network_change_sub, ast_network_change_type()); + stasis_subscription_set_filter(sip_network_change_sub, STASIS_SUBSCRIPTION_FILTER_SELECTIVE); + } +} + +void sip_network_change_unsubscribe(void) +{ + sip_network_change_sub = stasis_unsubscribe_and_join(sip_network_change_sub); +} + +static void sip_acl_change_event(void *data, struct stasis_subscription *sub, struct stasis_message *msg) +{ + if (stasis_message_type(msg) != 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_sub) { + sip_acl_change_sub = stasis_subscribe(ast_security_topic(), sip_acl_change_event, NULL); + + stasis_subscription_accept_message_type(sip_acl_change_sub, ast_named_acl_change_type()); + stasis_subscription_set_filter(sip_acl_change_sub, STASIS_SUBSCRIPTION_FILTER_SELECTIVE); + } +} + +void sip_acl_change_unsubscribe(void) +{ + sip_acl_change_sub = stasis_unsubscribe_and_join(sip_acl_change_sub); +} diff -durN asterisk-22.2.0.orig/channels/sip/handlers.c asterisk-22.2.0/channels/sip/handlers.c --- asterisk-22.2.0.orig/channels/sip/handlers.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/handlers.c 2025-02-18 17:14:46.799902885 +1300 @@ -0,0 +1,5966 @@ +/* + * 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/pvt.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/message.h" +#include "include/pickup.h" +#include "include/parking.h" +#include "include/conference.h" +#include "include/recording.h" +#include "include/callback.h" + +/* The results from handling an invite request */ +enum { + SIP_INVITE_SUCCESS = 0, /* Success code */ + SIP_INVITE_FAILED, /* Failure code */ + SIP_INVITE_ERROR, /* Error code */ +}; + +enum { + SIP_FEATURE_BULKUPDATE, + SIP_FEATURE_DONOTDISTURB, + SIP_FEATURE_CALLFORWARD +}; + +/* 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 */ + struct ast_party_redirecting redirecting; /* Redirecting information to set on the channel */ + struct ast_set_party_redirecting update_redirecting; /* Parts of the redirecting structure that are to be updated */ +}; + +/* Request handling functions */ +static int sip_handle_request(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, int *recount, int *nounlock); +static int sip_handle_request_invite(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, int *recount, int *nounlock); +static int sip_handle_invite_replaces(struct sip_pvt *pvt, struct sip_request *req, int *nounlock, struct sip_pvt *replaces_pvt, + struct ast_channel *replaces_chan); +static int sip_handle_request_update(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno); +static int sip_handle_request_cancel(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno); +static int sip_handle_request_bye(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno); +static int sip_handle_request_subscribe(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno); +static int sip_handle_request_notify(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno); +static int sip_handle_request_refer(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, int *nounlock); +static int sip_handle_request_register(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno); +static int sip_handle_request_options(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno); +static int sip_handle_request_publish(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno); +static int sip_handle_request_info(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno); +static int sip_handle_request_message(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno); + +static void sip_transfer_blind(struct ast_channel *chan, struct transfer_channel_data *chan_data, enum ast_transfer_type transfer_type); +static int sip_transfer_attended(struct sip_pvt *pvt, struct ast_channel *chan, uint32_t seqno, int *nounlock); + +static int sip_handle_subscribe_featureevent(struct sip_peer *peer, struct sip_request *req, int *feature); +static int sip_handle_notify_dialog(struct sip_pvt *pvt, struct sip_request *req); +static int sip_handle_publish_presence(struct sip_pvt *pvt, struct sip_request *req); +static int sip_handle_refer_remotecc(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer); + +/* Remotecc requests */ +static int sip_remotecc_idivert(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data); +static int sip_remotecc_hlog(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data); +static int sip_remotecc_startrecording(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data); +static int sip_remotecc_stoprecording(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data); +static int sip_remotecc_qrt(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data); +static int sip_remotecc_mcid(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data); + +/* Response handling functions */ +static void sip_handle_response(struct sip_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno); +static void sip_handle_response_invite(struct sip_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno); +static void sip_handle_response_update(struct sip_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno); +static void sip_handle_response_subscribe(struct sip_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno); +static void sip_handle_response_notify(struct sip_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno); +static void sip_handle_response_refer(struct sip_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno); +static void sip_handle_response_register(struct sip_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno); +static void sip_handle_response_options(struct sip_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno); +static void sip_handle_response_info(struct sip_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno); +static void sip_handle_response_message(struct sip_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno); + +/* Handle incoming SIP message - request or response. This is used for all transports (udp, tcp and tcp/tls) */ +int sip_handle_incoming(struct sip_request *req, struct ast_sockaddr *addr) +{ + struct sip_pvt *pvt; + struct ast_channel *chan = NULL; + int recount = FALSE; + int nounlock = FALSE; + + if (sip_debug_test_addr(addr)) { /* Set the debug flag early on packet level */ + req->debug = TRUE; + } + + if (sip_config.pedanticsipchecking) { + sip_compress_whitespace(req->data); /* Fix multiline headers */ + } + + if (req->debug) { + ast_verbose("\n<--- SIP read from %s:%s --->\n%s\n<------------->\n", + ast_transport2str(req->socket.type), ast_sockaddr_stringify(addr), ast_str_buffer(req->data)); + } + + if (sip_request_parse(req) == -1) { /* Bad packet, can't parse */ + ast_str_reset(req->data); /* nulling this out is NOT a good idea here. */ + return -1; + } + + req->method = sip_method_find(SIP_REQUEST_PART(req, rlpart1)); + + if (req->debug) { + ast_verb(3, "--- (%d headers %d lines)%s ---\n", req->headers, req->lines, (req->headers + req->lines == 0) ? " Nat keepalive" : ""); + } + + if (req->headers < 2) { /* Must have at least two headers */ + ast_str_reset(req->data); /* nulling this out is NOT a good idea here. */ + return -1; + } + + ast_mutex_lock(&sip_netsock_lock); + + /* Find the active SIP dialog or create a new one */ + if (!(pvt = sip_request_find_pvt(req, addr, req->method))) { /* returns pvt with a reference only. _NOT_ locked*/ + ast_debug(1, "Invalid SIP message - rejected, no callid, len %zu\n", ast_str_strlen(req->data)); + ast_mutex_unlock(&sip_netsock_lock); + + return -1; + } + + if (pvt->logger_callid) { + ast_callid_threadassoc_add(pvt->logger_callid); + } + + /* Lock both the pvt and the owner if owner is present. This will not fail. */ + chan = sip_pvt_lock_full(pvt); + + sip_socket_copy_data(&pvt->socket, &req->socket); + ast_sockaddr_copy(&pvt->recv, addr); + + /* if we have an owner, then this request has been authenticated */ + if (pvt->owner) { + req->authenticated = TRUE; + } + + if (pvt->recordhistory) { /* This is a request or response, note what it was for */ + sip_history_append(pvt, "Rx", "%s / %s / %s", ast_str_buffer(req->data), sip_request_get_header(req, "CSeq"), SIP_REQUEST_PART(req, rlpart2)); + } + + if (sip_handle_request(pvt, req, addr, &recount, &nounlock) == -1) { + /* Request failed */ + ast_debug(1, "SIP message could not be handled, bad request: %-70.70s\n", pvt->callid[0] ? pvt->callid : ""); + } + + if (recount) { + ast_update_use_count(); + } + + if (pvt->owner && !nounlock) { + ast_channel_unlock(pvt->owner); + } + + if (chan) { + ast_channel_unref(chan); + } + + ao2_unlock(pvt); + ast_mutex_unlock(&sip_netsock_lock); + + if (pvt->logger_callid) { + ast_callid_threadassoc_remove(); + } + + ao2_t_ref(pvt, -1, "throw away dialog ptr from sip_request_find_pvt at end of routine"); /* pvt is gone after the return */ + + return 0; +} + +/* Handle incoming SIP requests (methods) called with pvt and pvt->owner locked. */ +static int sip_handle_request(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, int *recount, int *nounlock) +{ + const char *cmd, *cseq, *useragent, *via, *callid; + uint32_t seqno; + int len, resp, res = 0, via_pos = 0; + int error = FALSE; + int oldmethod = pvt->method; + int acked = FALSE; + + /* Get the command */ + cmd = SIP_REQUEST_PART(req, rlpart1); + + /* 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 */ + cseq = sip_request_get_header(req, "Cseq"); + + /* Save the via_pos so we can check later that responses only have 1 Via header */ + via = sip_request_get_header_full(req, "Via", &via_pos); + + /* This must exist already because we've called sip_request_find_pvt by now */ + callid = sip_request_get_header(req, "Call-ID"); + + /* Must have Cseq */ + if (ast_strlen_zero(cseq) || ast_strlen_zero(via)) { + ast_log(LOG_ERROR, "Dropping this SIP message with Call-ID '%s', it's incomplete.\n", callid); + error = TRUE; + } + + if (!error && sscanf(cseq, "%30u%n", &seqno, &len) != 1) { + ast_log(LOG_ERROR, "No seqno in '%s'. Dropping incomplete message.\n", cmd); + error = TRUE; + } + + if (error) { + if (!pvt->initreq.headers) { /* New call */ + sip_pvt_set_need_destroy(pvt, "no headers"); + } + + return -1; + } + + /* Save useragent of the client */ + useragent = sip_request_get_header(req, "User-Agent"); + + if (!ast_strlen_zero(useragent)) { + ast_string_field_set(pvt, useragent, useragent); + } + + /* Find out SIP method for incoming request */ + if (req->method == SIP_RESPONSE) { /* Response to our request */ + /* Ignore means "don't do anything with it" but still have to respond appropriately. But in this case this is a + * response already, so we really have nothing to do with this message, and even setting the ignore flag is pointless. */ + if (sscanf(SIP_REQUEST_PART(req, rlpart2), "%30d %n", &resp, &len) != 1) { + ast_log(LOG_WARNING, "Invalid response: '%s'\n", SIP_REQUEST_PART(req, rlpart2)); + return 0; + } + + if (resp <= 0) { + ast_log(LOG_WARNING, "Invalid SIP response code: '%d'\n", resp); + return 0; + } + + /* 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 */ + if (!ast_strlen_zero(sip_request_get_header_full(req, "via", &via_pos))) { + ast_log(LOG_WARNING, "Misrouted SIP response '%s' with Call-ID '%s', too many vias\n", + SIP_REQUEST_PART(req, rlpart2), callid); + + return 0; + } + + if (pvt->ocseq && (pvt->ocseq < seqno)) { + ast_debug(1, "Ignoring out of order response %u (expecting %u)\n", seqno, pvt->ocseq); + return -1; + } else { + if (resp == 200 || (resp >= 300 && resp <= 399)) { + sip_pvt_extract_uri(pvt, req); + } + + if (pvt->owner) { + struct ast_control_pvt_cause_code *cause; + int causelen; + + /* size of the string making up the cause code is "SIP " + cause length */ + causelen = sizeof(*cause) + 4 + strlen(SIP_REQUEST_PART(req, rlpart2)); + cause = ast_alloca(causelen); + + memset(cause, 0, causelen); + ast_copy_string(cause->chan_name, ast_channel_name(pvt->owner), AST_CHANNEL_NAME); + + snprintf(cause->code, causelen - sizeof(*cause) + 1, "SIP %s", SIP_REQUEST_PART(req, rlpart2)); + cause->ast_cause = sip_hangup2cause(resp); + + ast_queue_control_data(pvt->owner, AST_CONTROL_PVT_CAUSE_CODE, cause, causelen); + ast_channel_hangupcause_hash_set(pvt->owner, cause, causelen); + } + + sip_handle_response(pvt, resp, req, seqno); + } + + return 0; + } + + /* New SIP request coming in */ + pvt->method = req->method; /* Find out which SIP method they are using */ + ast_debug(4, "Received %s (%u) - Command in SIP %s\n", sip_methods[pvt->method].name, sip_methods[pvt->method].id, cmd); + + if (pvt->icseq && (pvt->icseq > seqno)) { + if (pvt->pendinginvite && seqno == pvt->pendinginvite && (req->method == SIP_ACK || req->method == SIP_CANCEL)) { + ast_debug(2, "Got CANCEL or ACK on INVITE with transactions in between.\n"); + } else { + ast_debug(1, "Ignoring too old SIP packet packet %u (expecting >= %u)\n", seqno, pvt->icseq); + + if (req->method == SIP_INVITE) { + /* respond according to RFC 3261 14.2 with Retry-After betwewn 0 and 10 */ + sip_send_response_with_retry_after(pvt, "500 Internal Server Error", req, (unsigned int) (ast_random() % 10) + 1); + } else if (req->method != SIP_ACK) { + sip_send_response(pvt, "500 Internal Server Error", req); /* We must respond according to RFC 3261 sec 12.2 */ + } + + return -1; + } + } else if (pvt->icseq && pvt->icseq == seqno && req->method != SIP_ACK && (pvt->method != SIP_CANCEL || pvt->alreadygone)) { + /* 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 */ + req->ignore = TRUE; + + ast_debug(3, "Ignoring SIP message because of retransmit (%s Seqno %u, ours %u)\n", + sip_methods[pvt->method].name, pvt->icseq, seqno); + } + + /* RFC 3261 section 9. "CANCEL has no effect on a request to which a UAS has already given a final response." */ + if (!pvt->pendinginvite && (req->method == SIP_CANCEL)) { + sip_send_response(pvt, "481 Call/Transaction Does Not Exist", req); + return res; + } + + if (seqno >= pvt->icseq) { + /* Next should follow monotonically (but not necessarily incrementally) */ + pvt->icseq = seqno; + } + + /* Find their tag if we haven't got it */ + if (ast_strlen_zero(pvt->theirtag)) { + char tag[128]; + + sip_request_get_tag(req, "From", tag, sizeof(tag)); + ast_string_field_set(pvt, theirtag, tag); + } + + ast_string_field_build(pvt, lastmsg, "Rx: %s", cmd); + + if (sip_config.pedanticsipchecking) { + /* 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 totag already attached to it, RFC 3261 - section 12.2 - and we + * don't want to mess with recovery */ + if (!pvt->initreq.headers && req->has_to_tag) { + /* If this is a first request and it got a to-tag, it is not for us */ + if (!req->ignore && req->method == SIP_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. */ + char totag[128]; + + sip_request_get_tag(req, "To", totag, sizeof(totag)); + ast_string_field_set(pvt, tag, totag); + + pvt->pendinginvite = pvt->icseq; + sip_send_response_reliable(pvt, "481 Call/Transaction Does Not Exist", req); + + /* Will cease to exist after ACK */ + return res; + } else if (req->method != SIP_ACK) { + sip_send_response(pvt, "481 Call/Transaction Does Not Exist", req); + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + return res; + } + /* Otherwise, this is an ACK. It will always have a to-tag */ + } + } + + /* Handle various incoming SIP methods in requests */ + switch (pvt->method) { + case SIP_OPTIONS: + res = sip_handle_request_options(pvt, req, addr, seqno); + break; + case SIP_INVITE: + res = sip_handle_request_invite(pvt, req, addr, seqno, recount, nounlock); + + if (res < 9) { + sip_report_security_event(NULL, &pvt->recv, pvt, req, res); + } + + switch (res) { + case SIP_INVITE_SUCCESS: + res = 1; + break; + case SIP_INVITE_FAILED: + res = 0; + break; + case SIP_INVITE_ERROR: + res = -1; + break; + default: + res = 0; + break; + } + + break; + case SIP_REFER: + res = sip_handle_request_refer(pvt, req, addr, seqno, nounlock); + break; + case SIP_CANCEL: + res = sip_handle_request_cancel(pvt, req, addr, seqno); + break; + case SIP_BYE: + res = sip_handle_request_bye(pvt, req, addr, seqno); + break; + case SIP_MESSAGE: + res = sip_handle_request_message(pvt, req, addr, seqno); + break; + case SIP_PUBLISH: + res = sip_handle_request_publish(pvt, req, addr, seqno); + break; + case SIP_SUBSCRIBE: + res = sip_handle_request_subscribe(pvt, req, addr, seqno); + break; + case SIP_REGISTER: + res = sip_handle_request_register(pvt, req, addr, seqno); + sip_report_security_event(pvt->exten, NULL, pvt, req, res); + break; + case SIP_INFO: + res = sip_handle_request_info(pvt, req, addr, seqno); + break; + case SIP_NOTIFY: + res = sip_handle_request_notify(pvt, req, addr, seqno); + break; + case SIP_UPDATE: + res = sip_handle_request_update(pvt, req, addr, seqno); + break; + case SIP_ACK: + /* Make sure we don't ignore this */ + if (seqno == pvt->pendinginvite) { + pvt->invitestate = SIP_INVITE_TERMINATED; + pvt->pendinginvite = 0; + + acked = sip_packet_ack(pvt, seqno, TRUE /* is a response */, 0); + + if (pvt->owner && sip_request_find_sdp(req)) { + if (sip_sdp_parse(pvt, req, SIP_SDP_T38_NONE, FALSE)) { + return -1; + } + + if (ast_test_flag(&pvt->flags[0], SIP_DIRECT_MEDIA)) { + ast_queue_control(pvt->owner, AST_CONTROL_UPDATE_RTP_PEER); + } + } + + sip_pvt_sched_check_pendings(pvt); + } else if (pvt->glareinvite == seqno) { + /* handle ack for the 491 pending sent for glareinvite */ + pvt->glareinvite = 0; + acked = sip_packet_ack(pvt, seqno, TRUE, 0); + } + + if (!acked) { + /* Got an ACK that did not match anything. Ignore silently and restore previous method */ + pvt->method = oldmethod; + } + + if (!pvt->lastinvite && ast_strlen_zero(pvt->nonce)) { + sip_pvt_set_need_destroy(pvt, "unmatched ACK"); + } + + break; + default: + sip_send_response_with_allow(pvt, "501 Method Not Implemented", req); + ast_log(LOG_NOTICE, "Unknown SIP command '%s' from '%s'\n", cmd, ast_sockaddr_stringify(&pvt->sa)); + + /* If this is some new method, and we don't have a call, destroy it now */ + if (!pvt->initreq.headers) { + sip_pvt_set_need_destroy(pvt, "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_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, int *recount, int *nounlock) +{ + int res = SIP_INVITE_SUCCESS; + int dest_res; + const char *replaces; + char *decoded_exten; + char *callid = NULL; + const char *require; + unsigned int required_profile = 0; + struct ast_channel *chan = NULL; /* New channel */ + struct sip_peer *authpeer = NULL; /* Matching Peer */ + int reinvite = FALSE; + struct ast_party_redirecting redirecting; + struct ast_set_party_redirecting update_redirecting; + int start = 0; + const char *supported = NULL; + char unsupported[SIP_BUFFER_SIZE] = ""; + struct { + char exten[AST_MAX_EXTENSION]; + char context[AST_MAX_CONTEXT]; + } pickup = {.exten = ""}; + RAII_VAR(struct sip_pvt *, replaces_pvt, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, replaces_chan, NULL, ao2_cleanup); + + /* Find out what they support */ + if (!pvt->sipoptions) { + start = 0; + + do { + supported = sip_request_get_header_full(req, "Supported", &start); + + if (!ast_strlen_zero(supported)) { + pvt->sipoptions |= sip_parse_options(supported, NULL, 0); + } + } while (!ast_strlen_zero(supported)); + } + + /* Find out what they require */ + start = 0; + + do { + require = sip_request_get_header_full(req, "Require", &start); + + if (!ast_strlen_zero(require)) { + required_profile |= sip_parse_options(require, unsupported, sizeof(unsupported)); + } + } while (!ast_strlen_zero(require)); + + /* If there are any options required that we do not support, then send a 420 with only those unsupported options listed */ + if (!ast_strlen_zero(unsupported)) { + ast_log(LOG_WARNING, "Received SIP INVITE with unsupported required extension: %s\n", unsupported); + + sip_send_response_with_unsupported(pvt, "420 Bad Extension (unsupported)", req, unsupported); + pvt->invitestate = SIP_INVITE_COMPLETED; + + if (!pvt->lastinvite) { + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + } + + res = -1; + goto cleanup; + } + + /* The option tags may be present in Supported: or Require: headers Include the Require: option tags for further processing + * as well */ + pvt->sipoptions |= required_profile; + pvt->reqsipoptions = required_profile; + + /* Check if this is a loop */ + if (ast_test_flag(&pvt->flags[0], SIP_OUTGOING) && pvt->owner && + (pvt->invitestate != SIP_INVITE_TERMINATED && pvt->invitestate != SIP_INVITE_CONFIRMED) && ast_channel_state(pvt->owner) != 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; + const char *initreq_rlpart2 = SIP_REQUEST_PART(&pvt->initreq, rlpart2); + const char *rlpart2 = SIP_REQUEST_PART(req, rlpart2); + + if (sip_config.pedanticsipchecking) { + match = !sip_uri_cmp(initreq_rlpart2, rlpart2); + } else { + match = !strcmp(initreq_rlpart2, rlpart2); + } + + if (match) { + sip_send_response(pvt, "482 Loop Detected", req); + + pvt->invitestate = SIP_INVITE_COMPLETED; + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + res = SIP_INVITE_FAILED; + goto cleanup; + } 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, *sep; + + ast_debug(2, "Potential spiral detected. Original RURI was %s, new RURI is %s\n", initreq_rlpart2, rlpart2); + sip_send_response(pvt, "100 Trying", req); + + exten = ast_strdup(rlpart2); + + if ((sep = strchr(exten, '@'))) { + *sep = '\0'; + } + + /* Parse out "sip:" */ + if ((sep = strchr(exten, ':'))) { + exten = sep + 1; + } + + ast_string_field_set(pvt, theirtag, NULL); + + /* Treat this as if there were a call forward instead... */ + ast_channel_call_forward_set(pvt->owner, exten); + ast_queue_control(pvt->owner, AST_CONTROL_BUSY); + + res = SIP_INVITE_FAILED; + goto cleanup; + } + } + + if (!req->ignore && pvt->pendinginvite) { + if (!ast_test_flag(&pvt->flags[0], SIP_OUTGOING) && + (pvt->invitestate == SIP_INVITE_COMPLETED || pvt->invitestate == 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 pvt->pendinginvite 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 pvt->pendinginvite and removing the response + * from the previous transaction from the list of outstanding packets. */ + sip_packet_ack(pvt, pvt->pendinginvite, TRUE, 0); + } else { + /* We already have a pending invite. Sorry. You are on hold. */ + pvt->glareinvite = seqno; + sip_send_response_reliable(pvt, "491 Request Pending", req); + + sip_pvt_check_via(pvt, req); + ast_debug(1, "Got INVITE on call where we already have pending INVITE, deferring that - %s\n", pvt->callid); + + /* Don't destroy dialog here */ + res = SIP_INVITE_FAILED; + goto cleanup; + } + } + + replaces = sip_request_get_header(req, "Replaces"); + + if (!ast_strlen_zero(replaces)) { + /* We have a replaces header */ + char *fromtag = NULL, *totag = NULL, *tag, *sep; + int error = FALSE; + + if (pvt->owner) { + ast_debug(3, "INVITE with Replaces on existing call? Refusing action. [%s]\n", pvt->callid); + + sip_send_response_reliable(pvt, "400 Bad Request", req); /* The best way to not accept the transfer */ + sip_pvt_check_via(pvt, req); + sip_request_copy(&pvt->initreq, req); + + /* Do not destroy existing call */ + res = SIP_INVITE_ERROR; + goto cleanup; + } + + if (sip_debug) { + ast_debug(3, "INVITE part of call transfer. Replaces %s\n", replaces); + } + + /* Create a buffer we can manipulate */ + callid = ast_strdupa(replaces); + ast_uri_decode(callid, ast_uri_sip_user); + + if (!sip_refer_alloc(pvt)) { + sip_send_response_reliable(pvt, "500 Internal Server Error", req); + sip_history_append(pvt, "Xfer", "INVITE/Replace Failed. Out of memory."); + + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + pvt->invitestate = SIP_INVITE_COMPLETED; + + sip_pvt_check_via(pvt, req); + sip_request_copy(&pvt->initreq, req); + + res = SIP_INVITE_ERROR; + goto cleanup; + } + + /* Skip leading whitespace */ + callid = ast_skip_blanks(callid); + sep = callid; + + while ((tag = strsep(&sep, ";"))) { + if (!strncmp(tag, "to-tag=", 7)) { + totag = tag + 7; /* skip the keyword */ + } else if (!strncmp(tag, "from-tag=", 9)) { + fromtag = tag + 9; /* skip the keyword */ + fromtag = strsep(&fromtag, "&"); /* trim what? */ + } + } + + if (sip_debug) { + ast_debug(4, "Invite/replaces: Will use call-id: %s, from-tag: %s, to-tag: %s\n", + callid, fromtag ? fromtag : "", totag ? totag : ""); + } + + /* 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... */ + if (!strncmp(callid, "pickup-", 7)) { + RAII_VAR(struct sip_pvt *, subscribed_pvt, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, subscribed_chan, NULL, ao2_cleanup); + + callid += 7; /* Worst case we are looking at \0 */ + + if (sip_pvt_find_full(callid, totag, fromtag, &subscribed_pvt, &subscribed_chan)) { + ast_log(LOG_NOTICE, "Unable to find subscribed with call-id: %s\n", callid); + sip_send_response_reliable(pvt, "481 Call Leg Does Not Exist (replaces)", req); + + error = TRUE; + } else { + SCOPED_LOCK(lock, subscribed_pvt, ao2_lock, ao2_unlock); + + ast_log(LOG_NOTICE, "Trying to pick up %s@%s\n", subscribed_pvt->exten, subscribed_pvt->context); + + ast_copy_string(pickup.exten, subscribed_pvt->exten, sizeof(pickup.exten)); + ast_copy_string(pickup.context, subscribed_pvt->context, sizeof(pickup.context)); + } + } + + if (!error && ast_strlen_zero(pickup.exten) && + sip_pvt_find_full(callid, totag, fromtag, &replaces_pvt, &replaces_chan)) { + ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existent call id (%s)!\n", callid); + + sip_send_response_reliable(pvt, "481 Call Leg Does Not Exist (replaces)", req); + 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 (replaces_pvt == pvt) { + ast_log(LOG_NOTICE, "INVITE with replaces into it's own call id (%s == %s)!\n", callid, pvt->callid); + + sip_send_response_reliable(pvt, "400 Bad Request", req); /* The best way to not accept the transfer */ + error = TRUE; + } + + if (!error && ast_strlen_zero(pickup.exten) && !replaces_chan) { + /* Oops, someting wrong anyway, no owner, no call */ + ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existing call id (%s)!\n", callid); + + /* Check for better return code */ + sip_send_response_reliable(pvt, "481 Call Leg Does Not Exist (replaces)", req); + error = TRUE; + } + + if (!error && ast_strlen_zero(pickup.exten) && + ast_channel_state(replaces_chan) != AST_STATE_RINGING && + ast_channel_state(replaces_chan) != AST_STATE_RING && + ast_channel_state(replaces_chan) != 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(replaces_chan) != AST_STATE_DOWN) { + ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-ringing or active call id (%s)!\n", callid); + + sip_send_response_reliable(pvt, "603 Declined (replaces)", req); + error = TRUE; + } + + if (error) { /* Give up this dialog */ + sip_history_append(pvt, "Xfer", "INVITE/Replace Failed."); + + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + pvt->invitestate = SIP_INVITE_COMPLETED; + + sip_pvt_check_via(pvt, req); + sip_request_copy(&pvt->initreq, req); + + res = SIP_INVITE_ERROR; + goto cleanup; + } + } + + /* Check if this is an INVITE that sets up a new dialog or a re-invite in an existing dialog */ + if (!req->ignore) { + int newcall = pvt->initreq.headers; /* Has at least one header */ + + sip_pvt_cancel_destroy(pvt); + + /* This also counts as a pending invite */ + pvt->pendinginvite = seqno; + sip_pvt_check_via(pvt, req); + + if (sip_debug) { + ast_debug(1, "Initializing initreq for method %s, callid %s\n", sip_methods[req->method].name, pvt->callid); + } + + sip_request_copy(&pvt->initreq, req); /* Save this INVITE as the transaction basis */ + sip_parse_ok_contact(pvt, req); /* Parse new contact both for existing (re-invite) and new calls. */ + + if (!pvt->owner) { /* Not a re-invite */ + if (req->debug) { + ast_verb(3, "Using INVITE request as basis request - %s\n", pvt->callid); + } + + if (newcall) { + sip_history_append(pvt, "Invite", "New call: %s", pvt->callid); + } + } else { /* Re-invite on existing call */ + ast_clear_flag(&pvt->flags[0], SIP_OUTGOING); /* This is now an inbound dialog */ + + if (sip_parse_rpid(pvt, req)) { + sip_pvt_queue_connected_line_update(pvt, AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER); + } + + /* Handle SDP here if we already have an owner */ + if (sip_request_find_sdp(req)) { + if (sip_sdp_parse(pvt, req, SIP_SDP_T38_INITIATE, TRUE)) { + if (!ast_strlen_zero(sip_request_get_header(req, "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(pvt, "415 Unsupported Media Type", req); + } else { + sip_send_response_reliable(pvt, "488 Not Acceptable Here", req); + } + + if (!pvt->lastinvite) { + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + } + + res = SIP_INVITE_ERROR; + goto cleanup; + } + + ast_queue_control(pvt->owner, AST_CONTROL_SRCUPDATE); + } else { + ast_format_cap_remove_by_type(pvt->jointcaps, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(pvt->jointcaps, pvt->caps, AST_MEDIA_TYPE_UNKNOWN); + + ast_debug(1, "Hm.... No sdp for the moment\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(&pvt->flags[1], SIP_CALL_ONHOLD)) { + ast_queue_unhold(pvt->owner); + /* Activate a re-invite */ + ast_queue_frame(pvt->owner, &ast_null_frame); + sip_pvt_change_onhold(pvt, req, FALSE, FALSE); + } + } + + if (pvt->recordhistory) { /* This is a response, note what it was for */ + sip_history_append(pvt, "ReInv", "Re-invite received"); + } + } + } else if (req->debug) { + ast_verb(3, "Ignoring this INVITE request\n"); + } + + if (!pvt->lastinvite && !req->ignore && !pvt->owner) { + /* This is a new invite. Handle authentication if this is our first invite */ + sip_pvt_set_allowed_methods(pvt, req); + res = sip_pvt_check_peer_auth(pvt, req, addr, &authpeer, SIP_SEND_RELIABLE); + + if (res == SIP_AUTH_CHALLENGE_SENT) { + pvt->invitestate = SIP_INVITE_COMPLETED; /* Needs to restart in another INVITE transaction */ + goto cleanup; + } + + if (res != SIP_AUTH_SUCCESSFUL) { /* Something failed in authentication */ + sip_send_response_with_auth_fail(pvt, req, res, SIP_SEND_RELIABLE); + + pvt->invitestate = SIP_INVITE_COMPLETED; + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + goto cleanup; + } + + /* Successful authentication and peer matching so record the peer related to this pvt (for easy access to peer settings) */ + if (pvt->relatedpeer) { + ao2_t_cleanup(pvt->relatedpeer,"unsetting the relatedpeer field in the dialog, before it is set to something else."); + pvt->relatedpeer = NULL; + } + + if (authpeer) { + pvt->relatedpeer = ao2_t_bump(authpeer, "setting dialog's relatedpeer pointer"); + } + + req->authenticated = TRUE; + + /* We have a successful authentication, process the SDP portion if there is one */ + if (sip_request_find_sdp(req)) { + if (sip_sdp_parse(pvt, req, 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(req, "Content-Encoding"))) { + sip_send_response_reliable(pvt, "415 Unsupported Media Type", req); + } else { + /* Unacceptable codecs */ + sip_send_response_reliable(pvt, "488 Not Acceptable Here", req); + } + + ast_debug(1, "No compatible codecs for this SIP call.\n"); + + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + pvt->invitestate = SIP_INVITE_COMPLETED; + + res = SIP_INVITE_ERROR; + goto cleanup; + } + } else { + /* No SDP in invite, call control session */ + ast_format_cap_remove_by_type(pvt->jointcaps, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(pvt->jointcaps, pvt->caps, 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(pvt->context)) { + ast_string_field_set(pvt, context, sip_config.context); + } + + /* Check number of concurrent calls -vs- incoming limit HERE */ + ast_debug(1, "Checking SIP call limits for device %s\n", pvt->username); + + if ((res = sip_pvt_update_call_counter(pvt, SIP_INC_CALL_LIMIT))) { + if (res < 0) { + ast_log(LOG_NOTICE, "Failed to place call for device %s, too many calls\n", pvt->username); + sip_send_response_reliable(pvt, "480 Temporarily Unavailable (call limit) ", req); + + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + pvt->invitestate = SIP_INVITE_COMPLETED; + + res = SIP_AUTH_SESSION_LIMIT; + } + + goto cleanup; + } + + dest_res = sip_pvt_get_destination(pvt, NULL); /* Get destination right away */ + sip_pvt_extract_uri(pvt, req); /* Get the Contact URI */ + sip_pvt_build_contact(pvt, req, TRUE); /* Build our contact header */ + + if (pvt->rtp) { + ast_rtp_instance_set_prop(pvt->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833); + ast_rtp_instance_set_prop(pvt->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&pvt->flags[0], SIP_RFC2833_COMPENSATE)); + } + + if (!callid && (dest_res != SIP_DESTINATION_EXTEN_FOUND)) { /* No matching extension found */ + switch (dest_res) { + case SIP_DESTINATION_INVALID_URI: + sip_send_response_reliable(pvt, "416 Unsupported URI scheme", req); + break; + case SIP_DESTINATION_EXTEN_MATCHMORE: + if (ast_test_flag(&pvt->flags[1], SIP_ALLOWOVERLAP) == SIP_ALLOWOVERLAP_YES) { + sip_send_response_reliable(pvt, "484 Address Incomplete", req); + break; + } + + /* We would have to implement collecting more digits in chan_sip for any other schemes of overlap dialing. + * For SIP_ALLOWOVERLAP_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(pvt, "404 Not Found", req); + + decoded_exten = ast_strdupa(pvt->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(pvt->username, pvt->peername), ast_sockaddr_stringify(&pvt->recv), decoded_exten, pvt->context); + + sip_report_failed_acl(pvt, "no_extension_match"); + break; + case SIP_DESTINATION_REFUSED: + default: + sip_send_response_reliable(pvt, "403 Forbidden", req); + } + + sip_pvt_update_call_counter(pvt, SIP_DEC_CALL_LIMIT); + + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + pvt->invitestate = SIP_INVITE_COMPLETED; + + res = SIP_INVITE_FAILED; + goto cleanup; + } else { + /* If no extension was specified, use the s one Basically for calling to IP/Host name only */ + if (ast_strlen_zero(pvt->exten)) { + ast_string_field_set(pvt, exten, "s"); + } + + /* Initialize our tag */ + sip_pvt_build_our_tag(pvt); + + if (sip_stimer_handle_invite(pvt, req, reinvite)) { + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + pvt->invitestate = SIP_INVITE_COMPLETED; + + res = SIP_INVITE_ERROR; + goto cleanup; + } + + /* First invitation - create the channel. Allocation failures are handled below. */ + chan = sip_pvt_channel_alloc(pvt, AST_STATE_DOWN, S_OR(pvt->peername, NULL), NULL, NULL, pvt->logger_callid); + *recount = TRUE; + + /* Save Record-Route for any later requests we make on this dialogue */ + sip_pvt_build_route(pvt, req, FALSE, 0); + + if (chan) { + ast_party_redirecting_init(&redirecting); + memset(&update_redirecting, 0, sizeof(update_redirecting)); + + /* Will return immediately if no Diversion header is present */ + sip_pvt_set_redirecting(pvt, req, &redirecting, &update_redirecting, FALSE); + + ast_channel_set_redirecting(chan, &redirecting, &update_redirecting); + ast_party_redirecting_free(&redirecting); + } + } + } else { + ast_party_redirecting_init(&redirecting); + memset(&update_redirecting, 0, sizeof(update_redirecting)); + + if (sip_debug) { + if (!req->ignore) { + ast_debug(2, "Got a SIP re-invite for call %s\n", pvt->callid); + } else { + ast_debug(2, "Got a SIP re-transmit of INVITE for call %s\n", pvt->callid); + } + } + + if (!req->ignore) { + reinvite = TRUE; + } + + if (sip_stimer_handle_invite(pvt, req, reinvite)) { + pvt->invitestate = SIP_INVITE_COMPLETED; + + if (!pvt->lastinvite) { + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + } + + res = SIP_INVITE_ERROR; + goto cleanup; + } + + chan = pvt->owner; + /* Will return immediately if no Diversion header is present */ + sip_pvt_set_redirecting(pvt, req, &redirecting, &update_redirecting, FALSE); + + if (chan) { + ast_channel_set_redirecting(chan, &redirecting, &update_redirecting); + } + + ast_party_redirecting_free(&redirecting); + } + + /* Check if OLI/ANI-II is present in From: */ + sip_parse_oli(req, pvt->owner); + + if (reinvite && pvt->stimer) { + sip_stimer_restart(pvt); + } + + if (!req->ignore && pvt) { + pvt->lastinvite = seqno; + } + + if (chan && callid) { /* Attended transfer or call pickup - we're the target */ + if (!ast_strlen_zero(pickup.exten)) { + sip_history_append(pvt, "Xfer", "INVITE/Replace received"); + + /* Let the caller know we're giving it a shot */ + sip_send_response(pvt, "100 Trying", req); + + pvt->invitestate = SIP_INVITE_PROCEEDING; + ast_setstate(chan, AST_STATE_RING); + + /* Do the pickup itself */ + ast_channel_unlock(chan); + *nounlock = TRUE; + + /* since pvt->owner (c) is unlocked, we need to go ahead and unlock pvt for both magic pickup and ast_hangup. Both of + * these functions will attempt to lock pvt->owner again, which can cause a deadlock if we already hold a lock on pvt. + * Locking order is, channel then pvt. Dead lock avoidance must be used if called the other way around. */ + ao2_unlock(pvt); + sip_pickup_exten(chan, pickup.exten, pickup.context); + + /* Now we're either masqueraded or we failed to pickup, in either case we... */ + ast_hangup(chan); + ao2_lock(pvt); /* pvt is expected to remain locked on return, so re-lock it */ + + res = SIP_INVITE_FAILED; + goto cleanup; + } else { + /* Go and take over the target call */ + if (sip_debug) { + ast_debug(4, "Sending this call to the invite/replaces handler %s\n", pvt->callid); + } + + res = sip_handle_invite_replaces(pvt, req, nounlock, replaces_pvt, replaces_chan); + goto cleanup; + } + } + + if (chan) { /* We have a call -either a new call or an old one (RE-INVITE) */ + enum ast_channel_state state = ast_channel_state(chan); + RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, ast_get_chan_features_pickup_config(chan), ao2_cleanup); + const char *pickupexten; + + if (!pickup_cfg) { + ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n"); + pickupexten = ""; + } else { + pickupexten = ast_strdupa(pickup_cfg->pickupexten); + } + + if (state != AST_STATE_UP && reinvite && (pvt->invitestate == SIP_INVITE_TERMINATED || pvt->invitestate == 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, "%s: New call is still down.... Trying... \n", ast_channel_name(chan)); + + sip_send_response_provisional(pvt, "100 Trying", req, FALSE); + + pvt->invitestate = SIP_INVITE_PROCEEDING; + ast_setstate(chan, AST_STATE_RING); + + if (strcmp(pvt->exten, pickupexten)) { /* Call to extension -start pbx on this call */ + enum ast_pbx_result pbx_res; + + pbx_res = ast_pbx_start(chan); + + switch (pbx_res) { + case AST_PBX_FAILED: + ast_log(LOG_WARNING, "Failed to start PBX :(\n"); + + sip_pvt_set_already_gone(pvt); + pvt->invitestate = SIP_INVITE_COMPLETED; + + sip_send_response_reliable(pvt, "503 Unavailable", req); + break; + + case AST_PBX_CALL_LIMIT: + ast_log(LOG_WARNING, "Failed to start PBX (call limit reached) \n"); + + sip_pvt_set_already_gone(pvt); + pvt->invitestate = SIP_INVITE_COMPLETED; + + sip_send_response_reliable(pvt, "480 Temporarily Unavailable", req); + + res = SIP_AUTH_SESSION_LIMIT; + break; + + case AST_PBX_SUCCESS: + /* nothing to do */ + break; + } + + if (pbx_res) { + /* Unlock locks so ast_hangup can do its magic */ + ast_channel_unlock(chan); + *nounlock = TRUE; + + ao2_unlock(pvt); + ast_hangup(chan); + ao2_lock(pvt); + + chan = NULL; + } + } else { /* Pickup call in call group */ + if (sip_pickup_call(chan)) { + ast_log(LOG_WARNING, "Failed to start Group pickup by %s\n", ast_channel_name(chan)); + sip_send_response_reliable(pvt, "480 Temporarily Unavailable", req); + + sip_pvt_set_already_gone(pvt); + ast_channel_hangupcause_set(chan, AST_CAUSE_FAILURE); + + /* Unlock locks so ast_hangup can do its magic */ + ast_channel_unlock(chan); + *nounlock = TRUE; + + pvt->invitestate = SIP_INVITE_COMPLETED; + ao2_unlock(pvt); + ast_hangup(chan); + ao2_lock(pvt); + + chan = NULL; + } + } + + break; + case AST_STATE_RING: + sip_send_response_provisional(pvt, "100 Trying", req, FALSE); + pvt->invitestate = SIP_INVITE_PROCEEDING; + + break; + case AST_STATE_RINGING: + sip_send_response_provisional(pvt, "180 Ringing", req, FALSE); + pvt->invitestate = SIP_INVITE_PROCEEDING; + + break; + case AST_STATE_UP: + ast_debug(2, "%s: This call is UP.... \n", ast_channel_name(chan)); + sip_send_response(pvt, "100 Trying", req); + + if (pvt->t38.state == SIP_T38_PEER_REINVITE) { + sip_pvt_start_t38_abort_timer(pvt); + } else if (pvt->t38.state == SIP_T38_ENABLED) { + ast_set_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED); + + sip_send_response_with_t38_sdp(pvt, "200 OK", req, + (reinvite ? SIP_SEND_RELIABLE : (req->ignore ? SIP_SEND_UNRELIABLE : SIP_SEND_CRITICAL))); + } else if (pvt->t38.state == SIP_T38_DISABLED || pvt->t38.state == SIP_T38_REJECTED) { + /* If this is not a re-invite or something to ignore - it's critical */ + if (pvt->srtp && !ast_test_flag(pvt->srtp, AST_SRTP_CRYPTO_OFFER_OK)) { + ast_log(LOG_WARNING, "Target does not support required crypto\n"); + sip_send_response_reliable(pvt, "488 Not Acceptable Here (crypto)", req); + } else { + ast_set_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED); + + sip_send_response_with_sdp(pvt, "200 OK", req, + (reinvite ? SIP_SEND_RELIABLE : (req->ignore ? SIP_SEND_UNRELIABLE : SIP_SEND_CRITICAL)), + pvt->session_modify ? FALSE : TRUE, FALSE); + + ast_queue_control(pvt->owner, AST_CONTROL_UPDATE_RTP_PEER); + } + } + + pvt->invitestate = SIP_INVITE_TERMINATED; + break; + default: + ast_log(LOG_WARNING, "Don't know how to handle INVITE in state %u\n", ast_channel_state(chan)); + sip_send_response(pvt, "100 Trying", req); + + break; + } + } else { + if (!req->ignore && pvt && pvt->autokillid == -1) { + const char *msg; + + if (!ast_format_cap_count(pvt->jointcaps)) { + msg = "488 Not Acceptable Here (codec error)"; + } else { + ast_log(LOG_NOTICE, "Unable to create/find SIP channel for this INVITE\n"); + msg = "503 Unavailable"; + } + + sip_send_response_reliable(pvt, msg, req); + + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + pvt->invitestate = SIP_INVITE_COMPLETED; + } + } + +cleanup: + if (authpeer) { + ao2_t_cleanup(authpeer, "ao2_t_cleanup, from sip_handle_request_invite authpeer"); + } + + return res; +} + +/* 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). pvt and pvt->owner are locked upon entering this function. If the + * call pickup or attended transfer is successful, then pvt->owner will be unlocked upon exiting this function. This is communicated + * to the caller through the nounlock parameter. */ +static int sip_handle_invite_replaces(struct sip_pvt *pvt, struct sip_request *req, int *nounlock, struct sip_pvt *replaces_pvt, + struct ast_channel *replaces_chan) +{ + struct ast_channel *chan; + struct ast_bridge *bridge; + + if (req->ignore) { + return 0; + } + + if (!pvt->owner) { + /* What to do if no channel ??? */ + ast_log(LOG_ERROR, "Unable to create new channel. Invite/replace failed.\n"); + + sip_send_response_reliable(pvt, "503 Service Unavailable", req); + sip_history_append(pvt, "Xfer", "INVITE/Replace Failed. No new channel."); + + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + return 1; + } + + sip_history_append(pvt, "Xfer", "INVITE/Replace received"); + + /* Get a ref to ensure the channel cannot go away on us. */ + chan = ast_channel_ref(pvt->owner); + + /* Fake call progress */ + sip_send_response(pvt, "100 Trying", req); + ast_setstate(chan, AST_STATE_RING); + + ast_debug(4, "Invite/Replaces: preparing to replace %s with %s\n", ast_channel_name(replaces_chan), ast_channel_name(chan)); + *nounlock = TRUE; + + ast_channel_unlock(chan); + ao2_unlock(pvt); + + ast_raw_answer(chan); + bridge = ast_bridge_transfer_acquire_bridge(replaces_chan); + + if (bridge) { + /* We have two refs of the channel. One is held in c and the other is notionally represented by pvt->owner. The + * impart is "stealing" the pvt->owner ref on success so the bridging system can have control of when the channel + * is hung up. */ + if (ast_bridge_impart(bridge, chan, replaces_chan, NULL, AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) { + ast_hangup(chan); + } + + ao2_ref(bridge, -1); + } else { + int pickedup; + + ast_channel_lock(replaces_chan); + pickedup = ast_can_pickup(replaces_chan) && !ast_do_pickup(chan, replaces_chan); + ast_channel_unlock(replaces_chan); + + if (!pickedup) { + ast_channel_move(replaces_chan, chan); + } + + ast_hangup(chan); + } + + ast_channel_unref(chan); + ao2_lock(pvt); + + return 0; +} + +/* bare-bones support for SIP UPDATE. XXX 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_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno) +{ + if (!pvt->owner) { + sip_send_response(pvt, "481 Call/Transaction Does Not Exist", req); + return 0; + } + + if (sip_parse_rpid(pvt, req)) { + sip_pvt_queue_connected_line_update(pvt, AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER); + } + + sip_send_response(pvt, "200 OK", req); + return 0; +} + +/* Handle incoming CANCEL request */ +static int sip_handle_request_cancel(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno) +{ + sip_pvt_check_via(pvt, req); + sip_pvt_set_already_gone(pvt); + + if (pvt->owner && ast_channel_state(pvt->owner) == AST_STATE_UP) { + /* This call is up, cancel is ignored, we need a bye */ + sip_send_response(pvt, "200 OK", req); + ast_debug(1, "Got CANCEL on an answered call. Ignoring...\n"); + + return 0; + } + + sip_parse_reason(pvt, req); + + /* 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 (pvt->invitestate == SIP_INVITE_TERMINATED || pvt->invitestate == SIP_INVITE_COMPLETED) { + sip_packet_pretend_ack(pvt); + } + + if (pvt->invitestate != SIP_INVITE_TERMINATED) { + pvt->invitestate = SIP_INVITE_CANCELLED; + } + + if (ast_test_flag(&pvt->flags[0], SIP_INC_COUNT) || ast_test_flag(&pvt->flags[1], SIP_CALL_ONHOLD)) { + sip_pvt_update_call_counter(pvt, SIP_DEC_CALL_LIMIT); + } + + sip_pvt_stop_rtp(pvt); /* Immediately stop RTP, VRTP and UDPTL as applicable */ + + if (pvt->owner) { + sip_queue_hangup_cause(pvt, ast_channel_hangupcause(pvt->owner)); + } else { + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + } + + if (pvt->initreq.data && ast_str_strlen(pvt->initreq.data) > 0) { + 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(&pvt->packet_queue, packet, next) { + if (packet->seqno == pvt->lastinvite && packet->response_code == 487) { + /* Unlink and destroy the packet object. */ + AST_LIST_REMOVE_CURRENT(next); + + sip_packet_cancel_resend(packet); + ao2_t_ref(packet, -1, "Packet retransmission list"); + + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + /* Cisco phones fail to include the To tag in the ACK response */ + if (ast_test_flag(&pvt->flags[1], SIP_CISCO_USECALLMANAGER)) { + sip_send_response(pvt, "487 Request Terminated", &pvt->initreq); + } else { + sip_send_response_reliable(pvt, "487 Request Terminated", &pvt->initreq); + } + + sip_send_response(pvt, "200 OK", req); + return 1; + } else { + sip_send_response(pvt, "481 Call Leg Does Not Exist", req); + return 0; + } +} + +/* Handle incoming BYE request */ +static int sip_handle_request_bye(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno) +{ + const char *require; + RAII_VAR(struct ast_channel *, peer_chan, NULL, ast_channel_cleanup); + char field[AST_MAX_USER_FIELD], *quality; + + /* If we have an INCOMING invite that we haven't answered, terminate that transaction */ + if (pvt->pendinginvite && !ast_test_flag(&pvt->flags[0], SIP_OUTGOING) && !req->ignore) { + sip_send_response_reliable(pvt, "487 Request Terminated", &pvt->initreq); + } + + sip_packet_pretend_ack(pvt); + pvt->invitestate = SIP_INVITE_TERMINATED; + + sip_request_copy(&pvt->initreq, req); + + if (sip_debug) { + ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].name, pvt->callid); + } + + sip_pvt_check_via(pvt, req); + sip_pvt_set_already_gone(pvt); + + if (pvt->owner) { + RAII_VAR(struct ast_channel *, owner_relock, NULL, ast_channel_cleanup); + RAII_VAR(struct ast_channel *, owner, NULL, ast_channel_cleanup); + + /* Grab a reference to pvt->owner to prevent it from going away */ + owner = ast_channel_ref(pvt->owner); + + /* Established locking order here is bridge, channel, pvt and the bridge will be locked during ast_channel_bridge_peer */ + ast_channel_unlock(owner); + ao2_unlock(pvt); + + peer_chan = ast_channel_bridge_peer(owner); + owner_relock = sip_pvt_lock_full(pvt); + + if (!owner_relock) { + ast_debug(3, "Unable to reacquire owner channel lock, channel is gone\n"); + return 0; + } + } + + /* Get RTCP quality before end of call */ + if (pvt->rtp) { + if (pvt->recordhistory) { + if ((quality = ast_rtp_instance_get_quality(pvt->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, field, sizeof(field)))) { + sip_history_append(pvt, "RTCPaudio", "Quality:%s", quality); + } + + if ((quality = ast_rtp_instance_get_quality(pvt->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_JITTER, field, sizeof(field)))) { + sip_history_append(pvt, "RTCPaudioJitter", "Quality:%s", quality); + } + + if ((quality = ast_rtp_instance_get_quality(pvt->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS, field, sizeof(field)))) { + sip_history_append(pvt, "RTCPaudioLoss", "Quality:%s", quality); + } + + if ((quality = ast_rtp_instance_get_quality(pvt->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT, field, sizeof(field)))) { + sip_history_append(pvt, "RTCPaudioRTT", "Quality:%s", quality); + } + } + + if (pvt->owner) { + RAII_VAR(struct ast_channel *, owner_relock, NULL, ast_channel_cleanup); + RAII_VAR(struct ast_channel *, owner, NULL, ast_channel_cleanup); + struct ast_rtp_instance *rtp; + + /* Grab a reference to pvt->owner to prevent it from going away */ + owner = ast_channel_ref(pvt->owner); + + rtp = pvt->rtp; + ao2_ref(rtp, +1); + + /* Established locking order here is bridge, channel, pvt and the bridge and channel will be locked during + * ast_rtp_instance_set_stats_vars */ + ast_channel_unlock(owner); + ao2_unlock(pvt); + + ast_rtp_instance_set_stats_vars(owner, rtp); + ao2_ref(rtp, -1); + + if (peer_chan) { + ast_channel_lock(peer_chan); + + if (ast_channel_tech(peer_chan) == &sip_tech) { + struct sip_pvt *peer_pvt = ast_channel_tech_pvt(peer_chan); + + if (peer_pvt) { + ao2_ref(peer_pvt, +1); + ao2_lock(peer_pvt); + + if (peer_pvt->rtp) { + struct ast_rtp_instance *peer_rtp = peer_pvt->rtp; + + ao2_ref(peer_rtp, +1); + ast_channel_unlock(peer_chan); + ao2_unlock(peer_pvt); + + ast_rtp_instance_set_stats_vars(peer_chan, peer_rtp); + + ao2_ref(peer_rtp, -1); + ast_channel_lock(peer_chan); + ao2_lock(peer_pvt); + } + + ao2_unlock(peer_pvt); + ao2_ref(peer_pvt, -1); + } + } + + ast_channel_unlock(peer_chan); + } + + owner_relock = sip_pvt_lock_full(pvt); + + if (!owner_relock) { + ast_debug(3, "Unable to reacquire owner channel lock, channel is gone\n"); + return 0; + } + } + } + + if (pvt->vrtp && + (quality = ast_rtp_instance_get_quality(pvt->vrtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, field, sizeof(field)))) { + + if (pvt->recordhistory) { + sip_history_append(pvt, "RTCPvideo", "Quality:%s", quality); + } + + if (pvt->owner) { + pbx_builtin_setvar_helper(pvt->owner, "RTPVIDEOQOS", quality); + } + } + + if (pvt->trtp && + (quality = ast_rtp_instance_get_quality(pvt->trtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, field, sizeof(field)))) { + if (pvt->recordhistory) { + sip_history_append(pvt, "RTCPtext", "Quality:%s", quality); + } + + if (pvt->owner) { + pbx_builtin_setvar_helper(pvt->owner, "RTPTEXTQOS", quality); + } + } + + sip_pvt_stop_rtp(pvt); /* Immediately stop RTP, VRTP and UDPTL as applicable */ + + if (pvt->stimer) { + sip_stimer_stop(pvt); /* Stop Session-Timer */ + } + + sip_parse_reason(pvt, req); + + if (pvt->owner) { + sip_queue_hangup_cause(pvt, ast_channel_hangupcause(pvt->owner)); + ast_debug(3, "Received bye, issuing owner hangup\n"); + } else { + ast_debug(3, "Received bye, no owner, selfdestruct soon.\n"); + } + + if (!pvt->final_destruction_scheduled) { + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + pvt->final_destruction_scheduled = TRUE; + } + + ast_clear_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED); + /* Find out what they require */ + require = sip_request_get_header(req, "Require"); + + if (!ast_strlen_zero(require)) { + char unsupported[SIP_BUFFER_SIZE] = ""; + + sip_parse_options(require, unsupported, sizeof(unsupported)); + + /* If there are any options required that we do not support, then send a 420 with only those unsupported options listed */ + if (!ast_strlen_zero(unsupported)) { + sip_send_response_with_unsupported(pvt, "420 Bad Extension (unsupported)", req, unsupported); + ast_log(LOG_WARNING, "Received SIP BYE with unsupported required extension: required:%s unsupported:%s\n", require, unsupported); + } else { + sip_send_response(pvt, "200 OK", req); + } + } else { + sip_send_response(pvt, "200 OK", req); + } + + if (ast_test_flag(&pvt->flags[2], SIP_RTP_STATS_ON_BYE)) { + sip_parse_rtp_stats(pvt, req); + } + + /* Destroy any pending invites so we won't try to do another scheduled reINVITE. */ + sip_pvt_stop_reinvite_retry(pvt); + + return 1; +} + +/* Handle incoming SUBSCRIBE request */ +static int sip_handle_request_subscribe(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno) +{ + int res = 0; + struct sip_peer *authpeer = NULL; + char *event = ast_strdupa(sip_request_get_header(req, "Event")); /* Get Event package name */ + int resubscribe = (pvt->subscribed != SIP_SUBSCRIBED_NONE) && !req->ignore; + char *options; + int feature = -1; + + if (pvt->initreq.headers) { + /* We already have a dialog */ + if (pvt->initreq.method != SIP_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(pvt, "403 Forbidden (within dialog)", req); + + /* Do not destroy session, since we will break the call if we do */ + ast_debug(1, "Got a subscription within the context of another call, can't handle that - %s (Method %s)\n", + pvt->callid, sip_methods[pvt->initreq.method].name); + + return 0; + } else if (req->debug) { + if (resubscribe) { + ast_debug(1, "Got a re-subscribe on existing subscription %s\n", pvt->callid); + } else { + ast_debug(1, "Got a new subscription %s (possibly with auth) or retransmission\n", pvt->callid); + } + } + } + + /* Check if we have a global disallow setting on subscriptions if so, we don't have to check peer settings after auth, which saves + * a lot of processing. */ + if (!sip_config.allowsubscribe) { + sip_send_response(pvt, "403 Forbidden (policy)", req); + sip_pvt_set_need_destroy(pvt, "forbidden"); + + return 0; + } + + if (!req->ignore && !resubscribe) { /* Set up dialog, new subscription */ + const char *to = sip_request_get_header(req, "To"); + char totag[128]; + + sip_pvt_set_allowed_methods(pvt, req); + + /* 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) && sip_request_get_tag(req, "To", totag, sizeof(totag))) { + if (req->debug) { + ast_verb(3, "Received resubscription for a dialog we no longer know about. Telling remote side to subscribe again.\n"); + } + + sip_send_response(pvt, "481 Subscription Does Not Exist", req); + sip_pvt_set_need_destroy(pvt, "subscription does not exist"); + + return 0; + } + + /* Use this as the basis */ + if (req->debug) { + ast_verb(3, "Creating new subscription\n"); + } + + sip_request_copy(&pvt->initreq, req); + + if (sip_debug) { + ast_debug(4, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].name, pvt->callid); + } + + sip_pvt_check_via(pvt, req); + sip_pvt_build_route(pvt, req, FALSE, 0); + } else if (req->debug && req->ignore) { + ast_verb(3, "Ignoring this SUBSCRIBE request\n"); + } + + /* Find parameters to Event: header value and remove them for now */ + if (ast_strlen_zero(event)) { + sip_send_response(pvt, "489 Bad Event", req); + + ast_debug(2, "Received SIP subscribe for unknown event package: \n"); + sip_pvt_set_need_destroy(pvt, "unknown event package in subscribe"); + + return 0; + } + + if ((options = strchr(event, ';')) != NULL) { + *options++ = '\0'; + } + + /* Handle authentication if we're new and not a retransmission. We can't just use if !req->ignore, because then we'll end up + * sending a 200 OK if someone retransmits without sending auth */ + if (pvt->subscribed == SIP_SUBSCRIBED_NONE || resubscribe) { + res = sip_pvt_check_peer_auth(pvt, req, addr, &authpeer, SIP_SEND_UNRELIABLE); + + /* if an authentication response was sent, we are done here */ + if (res == SIP_AUTH_CHALLENGE_SENT) {/* authpeer = NULL here */ + return 0; + } + + if (res != SIP_AUTH_SUCCESSFUL) { + sip_send_response_with_auth_fail(pvt, req, res, SIP_SEND_UNRELIABLE); + sip_pvt_set_need_destroy(pvt, "authentication failed"); + + return 0; + } + } + + /* At this point, we hold a reference to authpeer (if not NULL). It must be released when done. */ + /* Check if this device is allowed to subscribe at all */ + if (!ast_test_flag(&pvt->flags[1], SIP_ALLOWSUBSCRIBE)) { + sip_send_response(pvt, "403 Forbidden (policy)", req); + sip_pvt_set_need_destroy(pvt, "subscription not allowed"); + + if (authpeer) { + ao2_t_cleanup(authpeer, "ao2_t_cleanup, from sip_handle_request_subscribe (authpeer 1)"); + } + + return 0; + } + + /* Get full contact header - this needs to be used as a request URI in NOTIFY's */ + sip_parse_ok_contact(pvt, req); + sip_pvt_build_contact(pvt, req, TRUE); + + /* Initialize tag for new subscriptions */ + if (ast_strlen_zero(pvt->tag)) { + sip_pvt_build_our_tag(pvt); + } + + if (!strcmp(event, "presence") || !strcmp(event, "dialog")) { /* Presence, RFC 3842 */ + int dest_res; + int start = 0; + int subscribed = SIP_SUBSCRIBED_NONE; + const char *accept, *unknown_accept = NULL; + + /* Get destination right away */ + dest_res = sip_pvt_get_destination(pvt, NULL); + + if (dest_res != SIP_DESTINATION_EXTEN_FOUND) { + if (dest_res == SIP_DESTINATION_INVALID_URI) { + sip_send_response(pvt, "416 Unsupported URI scheme", req); + } else { + sip_send_response(pvt, "404 Not Found", req); + } + + sip_pvt_set_need_destroy(pvt, "subscription target not found"); + + if (authpeer) { + ao2_t_cleanup(authpeer, "ao2_t_cleanup, from sip_handle_request_subscribe (authpeer 2)"); + } + + return 0; + } + + accept = sip_request_get_header_full(req, "Accept", &start); + + while ((subscribed == SIP_SUBSCRIBED_NONE) && !ast_strlen_zero(accept)) { + if (strstr(accept, "application/dialog-info+xml")) { + subscribed = SIP_SUBSCRIBED_DIALOG_INFO_XML; + /* IETF draft: draft-ietf-sipping-dialog-package-05.txt */ + } else if (strstr(accept, "application/cpim-pidf+xml")) { + if (ast_test_flag(&pvt->flags[1], SIP_CISCO_USECALLMANAGER)) { + subscribed = SIP_SUBSCRIBED_PIDF_XML; /* RFC 3863 format + Cisco USECALLMANAGER */ + } + } else { + unknown_accept = accept; + } + + /* check to see if there is another Accept header present */ + accept = sip_request_get_header_full(req, "Accept", &start); + } + + if (!start) { + if (pvt->subscribed == SIP_SUBSCRIBED_NONE) { /* if the subscribed field is not already set, and there is no accept header... */ + ast_log(LOG_WARNING,"SUBSCRIBE failure: no Accept header: pvt: stateid: %d, laststate: %d, dialogver: %u, subscribecont: '%s', subscribeuri: '%s'\n", + pvt->stateid, pvt->last_exten_state, pvt->dialogver, pvt->subscribecontext, pvt->subscribeuri); + + sip_send_response(pvt, "489 Bad Event", req); + sip_pvt_set_need_destroy(pvt, "no Accept header"); + + if (authpeer) { + ao2_t_cleanup(authpeer, "ao2_t_cleanup, from sip_handle_request_subscribe (authpeer 2)"); + } + + return 0; + } + + /* If pvt->subscribed is non-zero, then accept is not obligatory; according to rfc 3265 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 (subscribed == SIP_SUBSCRIBED_NONE) { + /* Can't find a format for events that we know about */ + char msg[128]; + + if (!ast_strlen_zero(unknown_accept)) { + snprintf(msg, sizeof(msg), "489 Bad Event (accept %s)", unknown_accept); + } else { + snprintf(msg, sizeof(msg), "489 Bad Event"); + } + + sip_send_response(pvt, msg, req); + sip_pvt_set_need_destroy(pvt, "unrecognized format"); + + ast_log(LOG_WARNING,"SUBSCRIBE failure: unrecognized format: '%s' pvt: subscribed: %d, stateid: %d, laststate: %d, dialogver: %u, subscribecont: '%s', subscribeuri: '%s'\n", + unknown_accept, (int) pvt->subscribed, pvt->stateid, pvt->last_exten_state, pvt->dialogver, pvt->subscribecontext, pvt->subscribeuri); + + if (authpeer) { + ao2_t_cleanup(authpeer, "ao2_t_cleanup, from sip_handle_request_subscribe (authpeer 2)"); + } + + return 0; + } else { + pvt->subscribed = subscribed; + } + } else if (!strcmp(event, "message-summary")) { + int start = 0; + int found_supported = 0; + const char *accept; + + accept = sip_request_get_header_full(req, "Accept", &start); + + while (!found_supported && !ast_strlen_zero(accept)) { + if ((found_supported = !strcmp(accept, "application/simple-message-summary"))) { + ast_debug(3, "Received SIP mailbox subscription for unknown format: %s\n", accept); + } + + accept = sip_request_get_header_full(req, "Accept", &start); + } + + /* If !start, there is no Accept header at all */ + if (start && !found_supported) { + /* Format requested that we do not support */ + sip_send_response(pvt, "406 Not Acceptable", req); + + ast_debug(2, "Received SIP mailbox subscription for unknown format\n"); + sip_pvt_set_need_destroy(pvt, "unknown format"); + + if (authpeer) { + ao2_t_cleanup(authpeer, "ao2_t_cleanup, from sip_handle_request_subscribe (authpeer 3)"); + } + + 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 (!authpeer || AST_LIST_EMPTY(&authpeer->mailboxes)) { + if (!authpeer) { + sip_send_response(pvt, "404 Not Found", req); + } else { + ast_log(LOG_NOTICE, "Received SIP subscribe for peer without mailbox: %s\n", S_OR(authpeer->name, "")); + sip_send_response(pvt, "404 Not Found (no mailbox)", req); + } + + sip_pvt_set_need_destroy(pvt, "received 404 response"); + + if (authpeer) { + ao2_t_cleanup(authpeer, "ao2_t_cleanup, from sip_handle_request_subscribe (authpeer 3)"); + } + + return 0; + } + + pvt->subscribed = SIP_SUBSCRIBED_MESSAGE_SUMMARY; + + if (ast_test_flag(&authpeer->flags[1], SIP_SUBSCRIBEMWIONLY)) { + ao2_unlock(pvt); + sip_peer_mailbox_subscriptions(authpeer); + ao2_lock(pvt); + } + + if (authpeer->mwipvt != pvt) { /* Destroy old PVT if this is a new one */ + /* We only allow one subscription per peer */ + if (authpeer->mwipvt) { + sip_pvt_unlink(authpeer->mwipvt); + ao2_t_cleanup(authpeer->mwipvt, "unref dialog authpeer->mwipvt"); + } + + authpeer->mwipvt = ao2_t_bump(pvt, "setting peers' mwipvt to p"); + } + + if (pvt->relatedpeer != authpeer) { + if (pvt->relatedpeer) { + ao2_t_cleanup(pvt->relatedpeer, "Unref previously stored relatedpeer ptr"); + } + + pvt->relatedpeer = ao2_t_bump(authpeer, "setting dialog's relatedpeer pointer"); + } + /* Do not release authpeer here */ + } else if (!strcmp(event, "as-feature-event")) { + if (!authpeer || sip_handle_subscribe_featureevent(authpeer, req, &feature) == -1) { + sip_send_response(pvt, "489 Bad Event", req); + sip_pvt_set_need_destroy(pvt, "unknown format"); + + if (authpeer) { + ao2_t_cleanup(authpeer, "ao2_t_cleanup, from sip_handle_request_subscribe (authpeer 4)"); + } + + return 0; + } + + pvt->subscribed = SIP_SUBSCRIBED_FEATURE_EVENTS; + + if (authpeer->fepvt != pvt) { /* Destroy old PVT if this is a new one */ + /* We only allow one subscription per peer */ + if (authpeer->fepvt) { + sip_pvt_unlink(authpeer->fepvt); + ao2_t_cleanup(authpeer->fepvt, "unref dialog authpeer->fepvt"); + } + + authpeer->fepvt = ao2_t_bump(pvt, "setting peers' fepvt to p"); + } + + if (pvt->relatedpeer != authpeer) { + if (pvt->relatedpeer) { + ao2_t_cleanup(pvt->relatedpeer, "Unref previously stored relatedpeer ptr"); + } + + pvt->relatedpeer = ao2_t_bump(authpeer, "setting dialog's relatedpeer pointer"); + } + /* Do not release authpeer here */ + } 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(pvt, "489 Bad Event", req); + sip_pvt_set_need_destroy(pvt, "unknown event package"); + + if (authpeer) { + ao2_t_cleanup(authpeer, "ao2_t_cleanup, from sip_handle_request_subscribe (authpeer 5)"); + } + + return 0; + } + + if (!req->ignore) { + pvt->lastinvite = seqno; + } + + if (!pvt->needdestroy) { + const char *expires = sip_request_get_header(req, "Expires"); + + if (ast_strlen_zero(expires)) { + pvt->expiry = sip_config.default_expiry; + } else { + pvt->expiry = atoi(expires); + } + + /* check if the requested expiry-time is within the approved limits from sip.conf */ + if (pvt->expiry > sip_config.max_subexpiry) { + pvt->expiry = sip_config.max_subexpiry; + } else if (pvt->expiry < sip_config.min_subexpiry && pvt->expiry > 0) { + ast_log(LOG_WARNING, "Received subscription for extension \"%s\" context \"%s\" with Expire header less than 'subminexpire' limit. Received \"Expire: %d\" min is %d\n", + pvt->exten, pvt->context, pvt->expiry, sip_config.min_subexpiry); + + sip_send_response_with_min_expires(pvt, "423 Interval Too Small", req, sip_config.min_subexpiry); + sip_pvt_set_need_destroy(pvt, "Expires is less that the min expires allowed."); + + if (authpeer) { + ao2_t_cleanup(authpeer, "ao2_t_cleanup, from sip_handle_request_subscribe (authpeer 6)"); + } + + return 0; + } + + if (sip_debug) { + const char *action = pvt->expiry > 0 ? "Adding" : "Removing"; + + if (pvt->subscribed == SIP_SUBSCRIBED_MESSAGE_SUMMARY && pvt->relatedpeer) { + ast_debug(2, "%s subscription for mailbox notification - peer %s\n", action, pvt->relatedpeer->name); + } else if (pvt->subscribed == SIP_SUBSCRIBED_FEATURE_EVENTS) { + ast_debug(2, "%s feature event subscription for peer %s\n", action, pvt->username); + } else { + ast_debug(2, "%s subscription for extension %s context %s for peer %s\n", action, pvt->exten, pvt->context, pvt->username); + } + } + + /* Remove subscription expiry for renewals */ + sip_pvt_cancel_destroy(pvt); + + if (pvt->expiry > 0) { + /* Set timer for destruction of call at expiration */ + sip_pvt_sched_destroy(pvt, (pvt->expiry + 10) * 1000); + } + + if (pvt->subscribed == SIP_SUBSCRIBED_MESSAGE_SUMMARY) { + ast_set_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED); + sip_send_response(pvt, "200 OK", req); + + if (pvt->relatedpeer) { /* Send first notification */ + struct sip_peer *peer = pvt->relatedpeer; + + ao2_t_bump(peer, "ensure a peer ref is held during MWI sending"); + ao2_unlock(pvt); + + sip_peer_send_mwi(peer, FALSE); + ao2_lock(pvt); + + ao2_t_cleanup(peer, "release a peer ref now that MWI is sent"); + } + } else if (pvt->subscribed == SIP_SUBSCRIBED_FEATURE_EVENTS) { + struct sip_request resp; + + ast_set_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED); + sip_response_prepare(&resp, pvt, "200 OK", req); + + if (feature != SIP_FEATURE_BULKUPDATE) { + sip_request_add_header(&resp, "Content-Type", "application/x-as-feature-event+xml"); + sip_request_add_content(&resp, "\n"); + + if (feature == SIP_FEATURE_DONOTDISTURB) { + sip_request_add_content(&resp, "\n"); + } else if (feature == SIP_FEATURE_CALLFORWARD) { + sip_request_add_content(&resp, "\n"); + } + } + + sip_response_send(pvt, &resp, SIP_SEND_UNRELIABLE, 0); + + if (pvt->relatedpeer) { + struct sip_peer *peer = pvt->relatedpeer; + + ao2_t_bump(peer, "ensure a peer ref is held during feature events sending"); + + if (feature == SIP_FEATURE_BULKUPDATE) { + sip_peer_send_bulkupdate(peer); + } else if (feature == SIP_FEATURE_DONOTDISTURB) { + sip_peer_send_donotdisturb(peer); + } else if (feature == SIP_FEATURE_CALLFORWARD) { + sip_peer_send_callforward(peer); + } + + ao2_t_cleanup(peer, "release a peer ref now that feature events is sent"); + } + } else { + struct sip_extension_state_data state_data = {0}; + char *subtype = NULL, *message = NULL; + struct ao2_container *device_state_info = NULL; + + if (pvt->expiry > 0 && !resubscribe) { + /* Add subscription for extension state from the PBX core */ + if (pvt->stateid != -1) { + ast_extension_state_del(pvt->stateid, sip_pvt_extension_state_event); + } + + ao2_t_bump(pvt, "copying dialog ptr into extension state struct"); + pvt->stateid = ast_extension_state_add_destroy_extended(pvt->context, pvt->exten, + sip_pvt_extension_state_event, sip_pvt_extension_state_destroy, pvt); + + if (pvt->stateid == -1) { + ao2_t_cleanup(pvt, "copying dialog ptr into extension state struct failed"); + } + } + + ao2_unlock(pvt); + + state_data.exten_state = ast_extension_state_extended(NULL, pvt->context, pvt->exten, &device_state_info); + state_data.presence_state = ast_hint_presence_state(NULL, pvt->context, pvt->exten, &subtype, &message); + state_data.presence_subtype = subtype; + state_data.presence_message = message; + + ao2_lock(pvt); + + if (state_data.exten_state < 0) { + ao2_cleanup(device_state_info); + + if (pvt->expiry > 0) { + ast_log(LOG_NOTICE, "Got SUBSCRIBE for extension %s@%s from %s, but there is no hint for that extension.\n", + pvt->exten, pvt->context, ast_sockaddr_stringify(&pvt->sa)); + } + + sip_send_response(pvt, "404 Not Found", req); + sip_pvt_set_need_destroy(pvt, "no extension for SUBSCRIBE"); + + if (authpeer) { + ao2_t_cleanup(authpeer, "ao2_t_cleanup, from sip_handle_request_subscribe (authpeer 6)"); + } + + return 0; + } + + ast_set_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED); + sip_send_response(pvt, "200 OK", req); + + /* RFC 3265: A notification must be sent on every subscribe, so force it */ + state_data.device_state_info = device_state_info; + + if (state_data.exten_state & AST_EXTENSION_RINGING) { + struct ast_channel *chan; + + /* Save last_ringing_channel_time if this state really contains a ringing channel because + * sip_pvt_extension_state_update() 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 ((chan = sip_find_ringing_channel(state_data.device_state_info))) { + pvt->last_ringing_channel_time = ast_channel_creationtime(chan); + ao2_ref(chan, -1); + } + } + + sip_pvt_extension_state_update(pvt, &state_data, TRUE); + sip_history_append(pvt, "Subscribestatus", "%s", ast_extension_state2str(state_data.exten_state)); + /* hide the 'complete' exten/context in the refer_to field for later display */ + + ast_string_field_build(pvt, subscribeuri, "%s@%s", pvt->exten, pvt->context); + /* Deleted the slow iteration of all sip dialogs to find old subscribes from this peer for exten@context */ + + ao2_cleanup(device_state_info); + ast_free(subtype); + ast_free(message); + } + + if (!pvt->expiry) { + sip_pvt_set_need_destroy(pvt, "forcing expiration"); + } + } + + if (authpeer) { + ao2_t_cleanup(authpeer, "unref pointer into (*authpeer)"); + } + + 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_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno) +{ + int res = 0; + char *event = ast_strdupa(sip_request_get_header(req, "Event")); + char *sep; + + if ((sep = strchr(event, ';'))) { + *sep++ = '\0'; + } + + if (sip_debug) { + ast_debug(2, "Got NOTIFY Event: %s\n", event); + } + + if (!strcmp(event, "refer")) { + /* Handle REFER notifications */ + char *buf, *cmd, *code; + int respcode; + int success = TRUE; + const char *content_type = sip_request_find_content_type(req); + + /* EventID 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 */ + + /* Check the content type */ + if (strncasecmp(content_type, "message/sipfrag", 15)) { + if (!strcasecmp(content_type, "application/x-cisco-remotecc-response+xml")) { + sip_send_response(pvt, "200 OK", req); + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + return 0; + } + + /* We need a sipfrag */ + sip_send_response(pvt, "400 Bad Request", req); + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + return -1; + } + + /* Get the text of the attachment */ + if (ast_strlen_zero(buf = sip_request_get_content(req, 0, req->lines))) { + ast_log(LOG_WARNING, "Unable to retrieve attachment from NOTIFY %s\n", pvt->callid); + + sip_send_response(pvt, "400 Bad Request", req); + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + return -1; + } + + ast_debug(3, "SIP Transfer NOTIFY Attachment: \n---%s\n---\n", buf); + + cmd = ast_skip_blanks(buf); + code = cmd; + + /* We are at SIP/2.0 */ + while (*code && (*code > 32)) { /* Search white space */ + code++; + } + + *code++ = '\0'; + code = ast_skip_blanks(code); + + sep = code; + sep++; + + while (*sep && (*sep > 32)) { /* Search white space */ + sep++; + } + + *sep++ = '\0'; /* Response string */ + respcode = atoi(code); + + switch (respcode) { + case 200: /* OK: The new call is up, hangup this call */ + /* Hangup the call that we are replacing */ + break; + case 301: /* Moved permanently */ + case 302: /* Moved temporarily */ + /* Do we get the header in the packet in this case? */ + success = FALSE; + break; + case 503: /* Service Unavailable: The new call failed */ + case 603: /* Declined: Not accepted */ + /* Cancel transfer, continue the current call */ + success = FALSE; + break; + case 0: /* Parse error */ + /* Cancel transfer, continue the current call */ + ast_log(LOG_NOTICE, "Error parsing sipfrag in NOTIFY in response to REFER.\n"); + success = FALSE; + break; + default: + if (respcode < 200) { + /* ignore provisional responses */ + success = -1; + } else { + ast_log(LOG_NOTICE, "Got unknown code '%d' in NOTIFY in response to REFER.\n", respcode); + success = FALSE; + } + + break; + } + + if (success == FALSE) { + ast_log(LOG_NOTICE, "Transfer failed. Sorry. Nothing further to do with this call\n"); + } + + if (pvt->owner && success != -1) { + enum ast_control_transfer transfer = success ? AST_TRANSFER_SUCCESS : AST_TRANSFER_FAILED; + + ast_queue_control_data(pvt->owner, AST_CONTROL_TRANSFER, &transfer, sizeof(transfer)); + } + + /* Confirm that we received this packet */ + sip_send_response(pvt, "200 OK", req); + } else if (!strcmp(event, "dialog")) { + /* Cisco USECALLMANAGER on/off-hook notifications */ + if (sip_handle_notify_dialog(pvt, req)) { + sip_send_response(pvt, "489 Bad Event", req); + res = -1; + } + } else if (!strcmp(event, "message-summary")) { + char *mailbox = NULL, *context; + char *msgs = ast_strdupa(sip_request_get_content_line(req, "Voice-Message", ':')); + + if (!pvt->mwi) { + struct sip_peer *peer; + + if ((peer = sip_peer_find(NULL, &pvt->recv, TRUE, FALSE, pvt->socket.type))) { + mailbox = ast_strdupa(peer->unsolicited_mailbox); + + if ((context = strchr(mailbox, '@'))) { + *context++ = '\0'; + } else { + context = "default"; + } + + ao2_t_cleanup(peer, "removing unsolicited mwi ref"); + } + } else { + mailbox = ast_strdupa(pvt->mwi->mailbox); + context = ast_strdupa(pvt->mwi->context); + } + + if (!ast_strlen_zero(mailbox) && !ast_strlen_zero(msgs)) { + char *old = strsep(&msgs, " "); + char *new = strsep(&old, "/"); + + ast_publish_mwi_state(mailbox, context, atoi(new), atoi(old)); + sip_send_response(pvt, "200 OK", req); + } else { + sip_send_response(pvt, "489 Bad Event", req); + 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(pvt, "200 OK", req); + } else { + /* We don't understand this event. */ + sip_send_response(pvt, "489 Bad Event", req); + res = -1; + } + + if (!pvt->lastinvite) { + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + } + + return res; +} + +/* Handle incoming REFER request */ +static int sip_handle_request_refer(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, int *nounlock) +{ + char *refer_to = NULL; + char *refer_to_context = NULL; + int res = 0; + struct sip_transfer_blind_data transfer_data; + enum ast_transfer_result transfer_res; + RAII_VAR(struct ast_channel *, chan, NULL, ast_channel_cleanup); + RAII_VAR(struct ast_str *, replaces, NULL, ast_free_ptr); + const char *content_type = sip_request_find_content_type(req); + + /* 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(pvt, "202 Accepted", req); + + if (!pvt->owner) { + sip_pvt_set_already_gone(pvt); + sip_pvt_set_need_destroy(pvt, "alarm/remotecc device notificaton"); + } + + return 0; + } else if (!strcasecmp(content_type, "application/x-cisco-remotecc-request+xml")) { + struct sip_peer *authpeer = NULL; + + res = sip_pvt_check_peer_auth(pvt, req, addr, &authpeer, SIP_SEND_UNRELIABLE); + + /* if an authentication response was sent, we are done here */ + if (res == SIP_AUTH_CHALLENGE_SENT) { + return 0; + } + + if (res != SIP_AUTH_SUCCESSFUL) { + ast_log(LOG_NOTICE, "Failed to authenticate device %s for REFER\n", sip_request_get_header(req, "From")); + sip_send_response_reliable(pvt, "403 Forbidden", req); + } else if (sip_handle_refer_remotecc(pvt, req, authpeer)) { + sip_send_response(pvt, "603 Declined (remotecc)", req); + + if (authpeer) { + ao2_t_cleanup(authpeer, "ao2_t_cleanup, from sip_handle_request_refer"); + } + } + + if (!pvt->owner) { + sip_pvt_set_already_gone(pvt); + sip_pvt_set_need_destroy(pvt, "remotecc request"); + } + + return 0; + } else if (!strcasecmp(sip_request_get_header(req, "Refer-To"), "")) { + sip_send_response(pvt, "202 Accepted", req); + + sip_pvt_set_already_gone(pvt); + sip_pvt_set_need_destroy(pvt, "token registration"); + + return 0; + } + + if (req->debug) { + ast_verb(3, "Call %s got a SIP call transfer from %s: (REFER)!\n", pvt->callid, ast_test_flag(&pvt->flags[0], SIP_OUTGOING) ? "callee" : "caller"); + } + + if (!pvt->owner) { + /* 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", pvt->callid); + sip_send_response(pvt, "603 Declined (no dialog)", req); + + if (!req->ignore) { + sip_history_append(pvt, "Xfer", "Refer failed. Outside of dialog."); + + sip_pvt_set_already_gone(pvt); + sip_pvt_set_need_destroy(pvt, "outside of dialog"); + + } + return 0; + } + + /* Check if transfer is allowed from this device */ + if (!pvt->allowtransfer) { + /* Transfer not allowed, decline */ + sip_send_response(pvt, "603 Declined (policy)", req); + sip_history_append(pvt, "Xfer", "Refer failed. Allowtransfer == closed."); + + /* Do not destroy SIP session */ + return 0; + } + + if (!req->ignore && ast_test_flag(&pvt->flags[0], SIP_GOTREFER)) { + /* Already have a pending REFER */ + sip_send_response(pvt, "491 Request pending", req); + sip_history_append(pvt, "Xfer", "Refer failed. Request pending."); + + return 0; + } + + /* Allocate memory for call transfer data */ + if (!sip_refer_alloc(pvt)) { + sip_send_response(pvt, "500 Internal Server Error", req); + sip_history_append(pvt, "Xfer", "Refer failed. Memory allocation error."); + + return -3; + } + + res = sip_parse_refer(pvt, req); /* Extract headers */ + pvt->refer->status = SIP_REFER_SENT; + + if (res != 0) { + switch (res) { + case -2: /* Syntax error */ + sip_send_response(pvt, "400 Bad Request (refer-to missing)", req); + sip_history_append(pvt, "Xfer", "Refer failed. Refer-to missing."); + + if (req->debug) { + ast_debug(1, "SIP transfer to black hole can't be handled (no refer-to: )\n"); + } + + break; + case -3: + sip_send_response(pvt, "603 Declined (non sip: uri)", req); + sip_history_append(pvt, "Xfer", "Refer failed. Non SIP uri"); + + if (req->debug) { + ast_debug(1, "SIP transfer to non-SIP uri denied\n"); + } + + break; + default: + /* Refer-to extension not found, fake a failed transfer */ + sip_send_response(pvt, "202 Accepted", req); + sip_history_append(pvt, "Xfer", "Refer failed. Bad extension."); + + sip_send_notify_with_sipfrag(pvt, seqno, "404 Not Found", TRUE); + ast_clear_flag(&pvt->flags[0], SIP_GOTREFER); + + if (req->debug) { + ast_debug(1, "SIP transfer to bad extension: %s\n", pvt->refer->refer_to); + } + + break; + } + + return 0; + } + + if (ast_strlen_zero(pvt->context)) { + ast_string_field_set(pvt, context, sip_config.context); + } + + /* If we do not support SIP domains, all transfers are local */ + if (sip_config.allow_external_domains && sip_domain_check(pvt->refer->refer_to_domain, NULL, 0)) { + pvt->refer->localtransfer = TRUE; + + if (sip_debug) { + ast_debug(3, "This SIP transfer is local: %s\n", pvt->refer->refer_to_domain); + } + } else if (AST_LIST_EMPTY(&sip_domains) || sip_domain_check(pvt->refer->refer_to_domain, NULL, 0)) { + /* This PBX doesn't bother with SIP domains or domain is local, so this transfer is local */ + pvt->refer->localtransfer = TRUE; + } else if (sip_debug) { + ast_debug(3, "This SIP transfer is to a remote SIP extension (remote domain %s)\n", pvt->refer->refer_to_domain); + } + + /* Is this a repeat of a current request? Ignore it */ + if (req->ignore) { + return 0; + } + + /* Get the transferer's channel */ + chan = ast_channel_ref(pvt->owner); + + if (sip_debug) { + ast_debug(3, "SIP %s transfer: Transferer channel %s\n", pvt->refer->attendedtransfer ? "attended" : "blind", ast_channel_name(chan)); + } + + ast_set_flag(&pvt->flags[0], SIP_GOTREFER); + + /* From here on failures will be indicated with NOTIFY requests */ + sip_send_response(pvt, "202 Accepted", req); + + /* Attended transfer: Find all call legs and bridge transferee with target*/ + if (pvt->refer->attendedtransfer) { + /* both pvt and pvt->owner _MUST_ be locked while calling sip_transfer_attended */ + if ((res = sip_transfer_attended(pvt, chan, seqno, nounlock))) { + ast_clear_flag(&pvt->flags[0], SIP_GOTREFER); + return res; + } + + /* Fall through for remote transfers that we did not find locally */ + if (sip_debug) { + ast_debug(4, "SIP attended transfer: Still not our call - generating INVITE with replaces\n"); + } + /* Fallthrough if we can't find the call leg internally */ + } + + /* Copy data we can not safely access after letting the pvt lock go. */ + refer_to = ast_strdupa(pvt->refer->refer_to); + refer_to_context = ast_strdupa(pvt->refer->refer_to_context); + + ast_party_redirecting_init(&transfer_data.redirecting); + memset(&transfer_data.update_redirecting, 0, sizeof(transfer_data.update_redirecting)); + + sip_pvt_set_redirecting(pvt, req, &transfer_data.redirecting, &transfer_data.update_redirecting, 0); + + transfer_data.domain = ast_strdupa(pvt->refer->refer_to_domain); + transfer_data.referred_by = ast_strdupa(pvt->refer->referred_by); + + if (!ast_strlen_zero(pvt->refer->replaces_callid)) { + 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(&pvt->flags[0], SIP_GOTREFER); + ast_party_redirecting_free(&transfer_data.redirecting); + + return -1; + } + + ast_str_append(&replaces, 0, "%s%s%s%s%s", pvt->refer->replaces_callid, + !ast_strlen_zero(pvt->refer->replaces_callid_totag) ? ";to-tag=" : "", + S_OR(pvt->refer->replaces_callid_totag, ""), + !ast_strlen_zero(pvt->refer->replaces_callid_fromtag) ? ";from-tag=" : "", + S_OR(pvt->refer->replaces_callid_fromtag, "")); + + transfer_data.replaces = ast_str_buffer(replaces); + } else { + transfer_data.replaces = NULL; + } + + if (!*nounlock) { + ast_channel_unlock(pvt->owner); + *nounlock = TRUE; + } + + ast_set_flag(&pvt->flags[0], SIP_DEFER_BYE_ON_TRANSFER); + + ao2_unlock(pvt); + transfer_res = ast_bridge_transfer_blind(TRUE, chan, refer_to, refer_to_context, sip_transfer_blind, &transfer_data); + ao2_lock(pvt); + + switch (transfer_res) { + case AST_BRIDGE_TRANSFER_INVALID: + pvt->refer->status = SIP_REFER_FAILED; + + sip_send_notify_with_sipfrag(pvt, seqno, "503 Service Unavailable (can't handle one-legged xfers)", TRUE); + sip_history_append(pvt, "Xfer", "Refer failed (only bridged calls)."); + + ast_clear_flag(&pvt->flags[0], SIP_DEFER_BYE_ON_TRANSFER); + res = -1; + + break; + case AST_BRIDGE_TRANSFER_NOT_PERMITTED: + pvt->refer->status = SIP_REFER_FAILED; + + sip_send_notify_with_sipfrag(pvt, seqno, "403 Forbidden", TRUE); + sip_history_append(pvt, "Xfer", "Refer failed (bridge does not permit transfers)"); + + ast_clear_flag(&pvt->flags[0], SIP_DEFER_BYE_ON_TRANSFER); + res = -1; + + break; + case AST_BRIDGE_TRANSFER_FAIL: + pvt->refer->status = SIP_REFER_FAILED; + + sip_send_notify_with_sipfrag(pvt, seqno, "500 Internal Server Error", TRUE); + sip_history_append(pvt, "Xfer", "Refer failed (internal error)"); + + ast_clear_flag(&pvt->flags[0], SIP_DEFER_BYE_ON_TRANSFER); + res = -1; + + break; + case AST_BRIDGE_TRANSFER_SUCCESS: + pvt->refer->status = SIP_REFER_200OK; + + sip_send_notify_with_sipfrag(pvt, seqno, "200 OK", TRUE); + sip_history_append(pvt, "Xfer", "Refer succeeded."); + + res = 0; + + break; + default: + break; + } + + ast_clear_flag(&pvt->flags[0], SIP_GOTREFER); + ast_party_redirecting_free(&transfer_data.redirecting); + + return res; +} + +/* Handle incoming REGISTER request */ +static int sip_handle_request_register(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno) +{ + int res; + + /* If this is not the intial request, and the initial request isn't a register, something screwy happened, so bail */ + if (pvt->initreq.headers && pvt->initreq.method != SIP_REGISTER) { + ast_log(LOG_WARNING, "Ignoring spurious REGISTER with Call-ID: %s\n", pvt->callid); + return -1; + } + + /* Use this as the basis */ + sip_request_copy(&pvt->initreq, req); + + if (sip_debug) { + ast_debug(4, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].name, pvt->callid); + } + + sip_pvt_check_via(pvt, req); + + if ((res = sip_pvt_check_register_auth(pvt, req, addr)) < 0) { + const char *reason; + + switch (res) { + case SIP_AUTH_SECRET_FAILED: + reason = "Wrong password"; + break; + case SIP_AUTH_USERNAME_MISMATCH: + reason = "Username/auth name mismatch"; + break; + case SIP_AUTH_NOT_FOUND: + reason = "No matching peer found"; + break; + case SIP_AUTH_UNKNOWN_DOMAIN: + reason = "Not a local domain"; + break; + case SIP_AUTH_PEER_NOT_DYNAMIC: + reason = "Peer is not supposed to register"; + break; + case SIP_AUTH_ACL_FAILED: + reason = "Device does not match ACL"; + break; + case SIP_AUTH_BAD_TRANSPORT: + reason = "Device not configured to use this transport type"; + break; + case SIP_AUTH_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(req, "To"), ast_sockaddr_stringify(addr), reason); + + sip_history_append(pvt, "RegRequest", "Failed : Account %s : %s", sip_request_get_header(req, "To"), reason); + } else { + req->authenticated = TRUE; + sip_history_append(pvt, "RegRequest", "Succeeded : Account %s", sip_request_get_header(req, "To")); + } + + if (res != SIP_AUTH_CHALLENGE_SENT) { + /* Destroy the session, but keep us around for just a bit in case they don't + get our 200 OK */ + sip_pvt_sched_destroy(pvt, 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_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno) +{ + const char *msg; + int dest_res; + int res; + + if (pvt->lastinvite) { + /* if this is a request in an active dialog, just confirm that the dialog exists. */ + sip_send_response_with_allow(pvt, "200 OK", req); + return 0; + } + + if (sip_config.auth_options_requests) { + /* Do authentication if this OPTIONS request began the dialog */ + sip_request_copy(&pvt->initreq, req); + sip_pvt_set_allowed_methods(pvt, req); + + res = sip_pvt_check_peer_auth(pvt, req, addr, NULL, SIP_SEND_UNRELIABLE); + + if (res == SIP_AUTH_CHALLENGE_SENT) { + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + return 0; + } + + if (res != SIP_AUTH_SUCCESSFUL) { /* Something failed in authentication */ + sip_send_response_with_auth_fail(pvt, req, res, SIP_SEND_UNRELIABLE); + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + return 0; + } + } + + /* must go through authentication before getting here */ + dest_res = sip_pvt_get_destination(pvt, req); + sip_pvt_build_contact(pvt, req, TRUE); + + if (ast_strlen_zero(pvt->context)) + ast_string_field_set(pvt, context, sip_config.context); + if (ast_shutting_down()) { + /* Not taking any new calls at this time. Likely a server availability OPTIONS poll. */ + msg = "503 Unavailable"; + } else { + msg = "404 Not Found"; + + switch (dest_res) { + case SIP_DESTINATION_INVALID_URI: + msg = "416 Unsupported URI scheme"; + break; + case SIP_DESTINATION_EXTEN_MATCHMORE: + case SIP_DESTINATION_REFUSED: + case SIP_DESTINATION_EXTEN_NOT_FOUND: + break; + case SIP_DESTINATION_EXTEN_FOUND: + msg = "200 OK"; + break; + } + } + + sip_send_response_with_allow(pvt, msg, req); + + /* Destroy if this OPTIONS was the opening request, but not if it's in the middle of a normal call flow. */ + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + return 0; +} + +static int sip_handle_request_publish(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno) +{ + static int etag_counter = 0; + const char *event = sip_request_get_header(req, "Event"); + const char *etag = sip_request_get_header(req, "SIP-If-Match"); + const char *expires = sip_request_get_header(req, "Expires"); + struct sip_request resp; + int res; + int expiry; + + if (ast_strlen_zero(event)) { + sip_send_response(pvt, "489 Bad Event", req); + sip_pvt_set_need_destroy(pvt, "missing Event: header"); + + return -1; + } + + res = sip_pvt_check_peer_auth(pvt, req, addr, NULL, SIP_SEND_UNRELIABLE); + + if (res == SIP_AUTH_CHALLENGE_SENT) { + pvt->lastinvite = seqno; + return 0; + } else if (res != SIP_AUTH_SUCCESSFUL) { + sip_send_response_with_auth_fail(pvt, req, res, SIP_SEND_UNRELIABLE); + + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + ast_string_field_set(pvt, theirtag, NULL); + + return 0; + } else if (res == SIP_AUTH_SUCCESSFUL && pvt->lastinvite) { + /* We need to stop retransmitting the 401 */ + sip_packet_ack(pvt, pvt->lastinvite, TRUE, 0); + } + + 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(pvt, "423 Interval too small", req, sip_config.min_expiry); + sip_pvt_set_need_destroy(pvt, "Expires is less that the min expires allowed."); + return 0; + } + } + + pvt->expiry = expiry; + + if (!strcmp(event, "presence")) { + res = sip_handle_publish_presence(pvt, req); + } else { + sip_send_response(pvt, "400 Unknown Event", req); + res = -1; + } + + if (!res) { + sip_response_prepare(&resp, pvt, "200 OK", req); + + if (!ast_strlen_zero(etag)) { + sip_request_add_header(&resp, "SIP-ETag", etag); + } else { + ast_atomic_fetchadd_int(&etag_counter, +1); + sip_request_build_header(&resp, "SIP-ETag", "%d", (unsigned int) etag_counter); + } + + res = sip_response_send(pvt, &resp, 0, 0); + } + + sip_pvt_set_need_destroy(pvt, "forcing expiration"); + + return res; +} + +/* Receive SIP INFO Message */ +static int sip_handle_request_info(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno) +{ + const char *buf = ""; + unsigned int event; + const char *content_type = sip_request_get_header(req, "Content-Type"); + const char *len; + + if (req->ignore) { + sip_send_response(pvt, "200 OK", req); + return 0; + } + + /* Need to check the media/type */ + + if (!strcasecmp(content_type, "application/hook-flash")) { + /* send a FLASH event, for ATAs that send flash as hook-flash not dtmf */ + struct ast_frame frame = {AST_FRAME_CONTROL, {AST_CONTROL_FLASH}}; + + ast_queue_frame(pvt->owner, &frame); + + if (sip_debug) { + ast_verb(3, "* DTMF-relay event received: FLASH\n"); + } + + sip_send_response(pvt, "200 OK", req); + return 0; + } + + if (!strcasecmp(content_type, "application/dtmf-relay") || !strcasecmp(content_type, "application/dtmf")) { + unsigned int duration = 0; + + if (!pvt->owner) { /* not a PBX call */ + sip_send_response(pvt, "481 Call leg/transaction does not exist", req); + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + return 0; + } + + /* If dtmf-relay parse the signal and duration; otherwise use the body as the signal */ + if (strcasecmp(content_type, "application/dtmf")) { + const char *tmp; + + if (ast_strlen_zero(buf = sip_request_get_content_line(req, "Signal", '=')) + && ast_strlen_zero(buf = sip_request_get_content_line(req, "d", '='))) { + ast_log(LOG_WARNING, "Unable to retrieve DTMF signal for INFO message on call %s\n", pvt->callid); + sip_send_response(pvt, "200 OK", req); + + return 0; + } + + if (!ast_strlen_zero((tmp = sip_request_get_content_line(req, "Duration", '=')))) { + sscanf(tmp, "%30u", &duration); + } + } else { + /* Type is application/dtmf, simply use what's in the message body */ + buf = sip_request_get_content(req, 0, req->lines); + } + + /* An empty message body requires us to send a 200 OK */ + if (ast_strlen_zero(buf)) { + sip_send_response(pvt, "200 OK", req); + return 0; + } + + if (!duration) { + duration = 100; /* 100 ms */ + } + + if (buf[0] == '*') { + event = 10; + } else if (buf[0] == '#') { + event = 11; + } else if (buf[0] == '!') { + event = 16; + } else if ('A' <= buf[0] && buf[0] <= 'D') { + event = 12 + buf[0] - 'A'; + } else if ('a' <= buf[0] && buf[0] <= 'd') { + event = 12 + buf[0] - 'a'; + } else if ((sscanf(buf, "%30u", &event) != 1) || event > 16) { + ast_log(AST_LOG_WARNING, "Unable to convert DTMF event signal code to a valid value for INFO message on call %s\n", pvt->callid); + sip_send_response(pvt, "200 OK", req); + + return 0; + } + + if (event == 16) { + /* send a FLASH event */ + struct ast_frame frame = {AST_FRAME_CONTROL, {AST_CONTROL_FLASH}}; + + ast_queue_frame(pvt->owner, &frame); + + if (sip_debug) { + ast_verb(3, "* DTMF-relay event received: FLASH\n"); + } + } else { + /* send a DTMF event */ + struct ast_frame frame = {AST_FRAME_DTMF}; + + if (event < 10) { + frame.subclass.integer = '0' + event; + } else if (event == 10) { + frame.subclass.integer = '*'; + } else if (event == 11) { + frame.subclass.integer = '#'; + } else { + frame.subclass.integer = 'A' + (event - 12); + } + + frame.len = duration; + ast_queue_frame(pvt->owner, &frame); + + if (sip_debug) { + ast_verb(3, "* DTMF-relay event received: %c\n", (int) frame.subclass.integer); + } + } + + sip_send_response(pvt, "200 OK", req); + return 0; + } else if (!strcasecmp(content_type, "application/media_control+xml")) { + /* Eh, we'll just assume it's a fast picture update for now */ + if (pvt->owner) { + ast_queue_control(pvt->owner, AST_CONTROL_VIDUPDATE); + } + + sip_send_response(pvt, "200 OK", req); + return 0; + } else if (ast_strlen_zero(len = sip_request_get_header(req, "Content-Length")) || !strcmp(len, "0")) { + /* This is probably just a packet making sure the signalling is still up, just send back a 200 OK */ + sip_send_response(pvt, "200 OK", req); + return 0; + } + + /* Other type of INFO message, not really understood by Asterisk */ + ast_log(LOG_WARNING, "Unable to parse INFO message from %s. Content %s\n", pvt->callid, buf); + sip_send_response(pvt, "415 Unsupported media type", req); + + return 0; +} + +/* Handle incoming MESSAGE request. We only handle messages within current calls currently */ +static int sip_handle_request_message(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno) +{ + char *buf, *from, *to, *contact, *sep; + size_t len; + const char *content_type = sip_request_get_header(req, "Content-Type"); + struct ast_msg *msg; + int i; + char name[1024], value[1024], from_name[50]; + int dest_res, res = 0; + + if (req->ignore) { + sip_send_response(pvt, "202 Accepted", req); + return 0; + } + + if (strncmp(content_type, "text/plain", 10)) { /* No text/plain attachment */ + sip_send_response(pvt, "415 Unsupported Media Type", req); /* Good enough, or? */ + + if (!pvt->owner) { + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + } + + return 0; + } + + if (!(buf = sip_request_get_content(req, 0, req->lines))) { + ast_log(LOG_WARNING, "Unable to retrieve text from %s\n", pvt->callid); + sip_send_response(pvt, "500 Internal Server Error", req); + + if (!pvt->owner) { + sip_pvt_sched_destroy(pvt, 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) */ + len = strlen(buf); + + while (len > 0) { + if (buf[--len] != '\n') { + len++; + break; + } + } + + buf[len] = '\0'; + + if (pvt->owner) { + struct ast_frame frame; + + if (sip_debug_test_pvt(pvt)) { + ast_verb(3, "SIP Text message received: '%s'\n", buf); + } + + memset(&frame, 0, sizeof(frame)); + + frame.frametype = AST_FRAME_TEXT; + frame.subclass.integer = 0; + frame.offset = 0; + frame.data.ptr = buf; + frame.datalen = strlen(buf) + 1; + + ast_queue_frame(pvt->owner, &frame); + sip_send_response(pvt, "202 Accepted", req); /* 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(pvt, "405 Method Not Allowed", req); + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + return 0; + } + + sip_request_copy(&pvt->initreq, req); + + if (sip_config.auth_message_requests) { + sip_pvt_set_allowed_methods(pvt, req); + res = sip_pvt_check_peer_auth(pvt, req, addr, NULL, SIP_SEND_UNRELIABLE); + + if (res == SIP_AUTH_CHALLENGE_SENT) { + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + return 0; + } + + if (res != SIP_AUTH_SUCCESSFUL) { /* Something failed in authentication */ + sip_send_response_with_auth_fail(pvt, req, res, SIP_SEND_UNRELIABLE); + sip_pvt_sched_destroy(pvt, 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(NULL, &pvt->recv, TRUE, FALSE, pvt->socket.type))) { + /* Only if no auth is required. */ + if (ast_strlen_zero(peer->secret) && ast_strlen_zero(peer->md5secret)) { + ast_string_field_set(pvt, context, peer->context); + } + + if (!ast_strlen_zero(peer->messagecontext)) { + ast_string_field_set(pvt, messagecontext, peer->messagecontext); + } + + ast_string_field_set(pvt, peername, peer->name); + ao2_t_cleanup(peer, "from sip_peer_find() in receive_message"); + } + } + + /* 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(pvt->messagecontext)) { + ast_string_field_set(pvt, context, pvt->messagecontext); + } else if (!ast_strlen_zero(sip_config.messagecontext)) { + ast_string_field_set(pvt, context, sip_config.messagecontext); + } + + dest_res = sip_pvt_get_destination(pvt, NULL); + + switch (dest_res) { + case SIP_DESTINATION_REFUSED: + /* Okay to send 403 since this is after auth processing */ + sip_send_response(pvt, "403 Forbidden", req); + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + return 0; + case SIP_DESTINATION_INVALID_URI: + sip_send_response(pvt, "416 Unsupported URI Scheme", req); + sip_pvt_sched_destroy(pvt, 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(pvt, "500 Internal Server Error", req); + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + return -1; + } + + to = ast_strdupa(SIP_REQUEST_PART(req, rlpart2)); + from = ast_strdupa(sip_request_get_header(req, "From")); + + res = ast_msg_set_to(msg, "%s", to); + + /* Build "display" for from string. */ + from = (char *) sip_get_name(from, from_name, sizeof(from_name)); + from = sip_get_in_brackets(from); + + if (!ast_strlen_zero(from_name)) { + char quoted_name[128]; + + ast_escape_quoted(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); + } + + res = 0; + + res |= ast_msg_set_body(msg, "%s", buf); + res |= ast_msg_set_context(msg, "%s", pvt->context); + + res |= ast_msg_set_var(msg, "SIP_RECVADDR", ast_sockaddr_stringify(&pvt->recv)); + res |= ast_msg_set_tech(msg, "%s", "SIP"); + + if (!ast_strlen_zero(pvt->peername)) { + res |= ast_msg_set_endpoint(msg, "%s", pvt->peername); + res |= ast_msg_set_var(msg, "SIP_PEERNAME", pvt->peername); + } + + contact = ast_strdupa(sip_request_get_header(req, "Contact")); + res |= ast_msg_set_var(msg, "SIP_FULLCONTACT", sip_get_in_brackets(contact)); + + res |= ast_msg_set_exten(msg, "%s", pvt->exten); + + for (i = 0; i < req->headers; i++) { + const char *header = SIP_REQUEST_PART(req, header[i]); + + if ((sep = strchr(header, ':'))) { + ast_copy_string(name, header, MIN((sep - header + 1), sizeof(name))); + ast_copy_string(value, ast_skip_blanks(sep + 1), sizeof(value)); + + ast_trim_blanks(name); + res |= ast_msg_set_var(msg, name, value); + + if (res) { + break; + } + } + } + + if (res) { + ast_msg_destroy(msg); + + sip_send_response(pvt, "500 Internal Server Error", req); + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + return -1; + } + + if (ast_msg_has_destination(msg)) { + ast_msg_queue(msg); + + sip_send_response(pvt, "202 Accepted", req); + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + return 0; + } + + /* Find a specific error cause to send */ + switch (dest_res) { + case SIP_DESTINATION_EXTEN_NOT_FOUND: + case SIP_DESTINATION_EXTEN_MATCHMORE: + sip_send_response(pvt, "404 Not Found", req); + break; + case SIP_DESTINATION_EXTEN_FOUND: + default: + /* We should have sent the message already! */ + sip_send_response(pvt, "500 Internal Server Error", req); + break; + } + + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + ast_msg_destroy(msg); + + 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 *chan, struct transfer_channel_data *chan_data, enum ast_transfer_type transfer_type) +{ + struct sip_transfer_blind_data *transfer_data = chan_data->data; + + pbx_builtin_setvar_helper(chan, "SIPTRANSFER", "yes"); + pbx_builtin_setvar_helper(chan, "SIPTRANSFER_REPLACES", transfer_data->replaces); + pbx_builtin_setvar_helper(chan, "SIPDOMAIN", transfer_data->domain); + + ast_channel_update_redirecting(chan, &transfer_data->redirecting, &transfer_data->update_redirecting); +} + +/* Find all call legs and bridge transferee with target. + * This function assumes two locks to begin with, sip_pvt transferer and current.chan1 (the pvt's owner)... + * additional locks are held at the beginning of the function, target_pvt, and target_pvt'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 pvt'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 pvt lock will remain on return. Setting nounlock indicates to + * sip_handle_incoming() that the pvt's owner it locked does not require an unlock. */ +static int sip_transfer_attended(struct sip_pvt *pvt, struct ast_channel *chan, uint32_t seqno, int *nounlock) +{ + RAII_VAR(struct sip_pvt *, target_pvt, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, target_chan, NULL, ao2_cleanup); + enum ast_transfer_result transfer_res; + + /* Check if the call ID of the replaces header does exist locally */ + if (sip_pvt_find_full(pvt->refer->replaces_callid, pvt->refer->replaces_callid_totag, pvt->refer->replaces_callid_fromtag, + &target_pvt, &target_chan)) { + if (pvt->refer->localtransfer) { + /* 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(pvt, seqno, "481 Call leg/transaction does not exist", TRUE); + sip_history_append(pvt, "Xfer", "Refer failed"); + + ast_clear_flag(&pvt->flags[0], SIP_GOTREFER); + pvt->refer->status = SIP_REFER_FAILED; + + return -1; + } + + /* Fall through for remote transfers that we did not find locally */ + ast_debug(3, "SIP attended transfer: Not our call - generating INVITE with replaces\n"); + return 0; + } + + if (!target_chan) { /* No active channel */ + ast_debug(4, "SIP attended transfer: Error: No owner of target call\n"); + + /* Cancel transfer */ + sip_send_notify_with_sipfrag(pvt, seqno, "503 Service Unavailable", TRUE); + sip_history_append(pvt, "Xfer", "Refer failed"); + + ast_clear_flag(&pvt->flags[0], SIP_GOTREFER); + pvt->refer->status = SIP_REFER_FAILED; + + return -1; + } + + ast_set_flag(&pvt->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Delay hangup */ + + /* Cisco phones need a different response code */ + if (ast_test_flag(&target_pvt->flags[1], SIP_CISCO_USECALLMANAGER)) { + ast_set_flag(&target_pvt->flags[1], SIP_TRANSFER_RESPONSE); + } + + ao2_unlock(pvt); + ast_channel_unlock(chan); + *nounlock = TRUE; + + transfer_res = ast_bridge_transfer_attended(chan, target_chan); + ao2_lock(pvt); + + switch (transfer_res) { + case AST_BRIDGE_TRANSFER_SUCCESS: + pvt->refer->status = SIP_REFER_200OK; + + sip_send_notify_with_sipfrag(pvt, seqno, "200 OK", TRUE); + sip_history_append(pvt, "Xfer", "Refer succeeded"); + + return 1; + case AST_BRIDGE_TRANSFER_FAIL: + pvt->refer->status = SIP_REFER_FAILED; + sip_send_notify_with_sipfrag(pvt, seqno, "500 Internal Server Error", TRUE); + + sip_history_append(pvt, "Xfer", "Refer failed (internal error)"); + ast_clear_flag(&pvt->flags[0], SIP_DEFER_BYE_ON_TRANSFER); + + return -1; + case AST_BRIDGE_TRANSFER_INVALID: + pvt->refer->status = SIP_REFER_FAILED; + sip_send_notify_with_sipfrag(pvt, seqno, "503 Service Unavailable", TRUE); + + sip_history_append(pvt, "Xfer", "Refer failed (invalid bridge state)"); + ast_clear_flag(&pvt->flags[0], SIP_DEFER_BYE_ON_TRANSFER); + + return -1; + case AST_BRIDGE_TRANSFER_NOT_PERMITTED: + pvt->refer->status = SIP_REFER_FAILED; + sip_send_notify_with_sipfrag(pvt, seqno, "403 Forbidden", TRUE); + + sip_history_append(pvt, "Xfer", "Refer failed (operation not permitted)"); + ast_clear_flag(&pvt->flags[0], SIP_DEFER_BYE_ON_TRANSFER); + + return -1; + default: + break; + } + + return 1; +} + +/* Handle incoming feature event SUBSCRIBE body */ +static int sip_handle_subscribe_featureevent(struct sip_peer *peer, struct sip_request *req, int *feature) +{ +#ifdef HAVE_LIBXML2 + const char *content_type = sip_request_get_header(req, "Content-Type"); + char *featureevent_body; + RAII_VAR(struct ast_xml_doc *, featureevent_doc, NULL, ast_xml_close); + struct ast_xml_node *root_node; + + if (!atoi(sip_request_get_header(req, "Content-Length"))) { + /* Peer is subscribing to the current DoNotDisturb and CallForward state */ + *feature = SIP_FEATURE_BULKUPDATE; + return 0; + } + + 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 (!(featureevent_body = sip_request_get_content(req, 0, req->lines - 1))) { + ast_log(LOG_WARNING, "Unable to get feature event body\n"); + return -1; + } + + if (!(featureevent_doc = ast_xml_read_memory(featureevent_body, strlen(featureevent_body)))) { + ast_log(LOG_WARNING, "Unable to open XML as-feature-event document. Is it malformed?\n"); + return -1; + } + + if (!(root_node = ast_xml_get_root(featureevent_doc))) { + ast_log(LOG_WARNING, "Unable to get root node\n"); + return -1; + } + + if (!strcmp(ast_xml_node_get_name(root_node), "SetDoNotDisturb")) { + int donotdisturb; + struct ast_xml_node *set_donotdisturb_node, *set_donotdisturb_children; + struct ast_xml_node *donotdisturb_on_node; + RAII_VAR(const char *, donotdisturb_on_text, NULL, ast_xml_free_text); + + set_donotdisturb_node = root_node; + + if (!(set_donotdisturb_children = ast_xml_node_get_children(set_donotdisturb_node))) { + ast_log(LOG_WARNING, "No tuples within SetDoNotDisturb node"); + return -1; + } + + if (!(donotdisturb_on_node = ast_xml_find_element(set_donotdisturb_children, "doNotDisturbOn", NULL, NULL))) { + ast_log(LOG_WARNING, "Couldn't find doNotDisturbOn node"); + return -1; + } + + donotdisturb_on_text = ast_xml_get_text(donotdisturb_on_node); + + if (!strcmp(donotdisturb_on_text, "true")) { + donotdisturb = TRUE; + } else if (!strcmp(donotdisturb_on_text, "false")) { + donotdisturb = FALSE; + } else { + ast_log(LOG_WARNING, "Invalid content in doNotDisturbOn node %s\n", donotdisturb_on_text); + return -1; + } + + if (peer->donotdisturb != donotdisturb) { + peer->donotdisturb = donotdisturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", peer->name); + + if (!peer->is_realtime) { + ast_db_put("SIP/DoNotDisturb", peer->name, peer->donotdisturb ? "yes" : "no"); + } else if (sip_config.peer_rtupdate && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name, "donotdisturb", donotdisturb ? "yes" : "no", SENTINEL); + } + } + + *feature = SIP_FEATURE_DONOTDISTURB; + } else if (!strcmp(ast_xml_node_get_name(root_node), "SetForwarding")) { + char callforward[AST_MAX_EXTENSION]; + struct ast_xml_node *set_forwarding_node, *set_forwarding_children; + struct ast_xml_node *forwarding_type_node, *activate_forward_node, *forward_dn_node; + RAII_VAR(const char *, forwarding_type_text, NULL, ast_xml_free_text); + RAII_VAR(const char *, activate_forward_text, NULL, ast_xml_free_text); + RAII_VAR(const char *, forward_dn_text, NULL, ast_xml_free_text); + + set_forwarding_node = root_node; + + if (!(set_forwarding_children = ast_xml_node_get_children(set_forwarding_node))) { + ast_log(LOG_WARNING, "No tuples within SetForwarding node"); + return -1; + } + + if (!(forwarding_type_node = ast_xml_find_element(set_forwarding_children, "forwardingType", NULL, NULL))) { + ast_log(LOG_WARNING, "Couldn't find forwardingType node\n"); + return -1; + } + + forwarding_type_text = ast_xml_get_text(forwarding_type_node); + + if (strcmp(forwarding_type_text, "forwardImmediate")) { + ast_log(LOG_WARNING, "forwardingType not supported: %s\n", forwarding_type_text); + ast_xml_free_text(forwarding_type_text); + return -1; + } + + if (!(activate_forward_node = ast_xml_find_element(set_forwarding_children, "activateForward", NULL, NULL))) { + ast_log(LOG_WARNING, "Couldn't find activateForward node"); + return -1; + } + + activate_forward_text = ast_xml_get_text(activate_forward_node); + + if (!strcmp(activate_forward_text, "true")) { + if (!(forward_dn_node = ast_xml_find_element(set_forwarding_children, "forwardDN", NULL, NULL))) { + ast_log(LOG_WARNING, "Couldn't find forwardDN node\n"); + ast_xml_free_text(activate_forward_text); + return -1; + } + + forward_dn_text = ast_xml_get_text(forward_dn_node); + ast_copy_string(callforward, S_OR(forward_dn_text, ""), sizeof(callforward)); + } else if (!strcmp(activate_forward_text, "false")) { + callforward[0] = '\0'; + } else { + ast_log(LOG_WARNING, "Invalid content in activateForward node: %s\n", activate_forward_text); + return -1; + } + + if (strcmp(peer->callforward, callforward)) { + ast_string_field_set(peer, callforward, callforward); + if (!peer->is_realtime) { + if (ast_strlen_zero(peer->callforward)) { + ast_db_del("SIP/CallForward", peer->name); + } else { + ast_db_put("SIP/CallForward", peer->name, peer->callforward); + } + } else if (sip_config.peer_rtupdate && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name, "callforward", peer->callforward, SENTINEL); + } + } + + *feature = SIP_FEATURE_CALLFORWARD; + } else { + ast_log(LOG_WARNING, "Couldn't find SetDoNotDisturb or SetForwarding node: %s\n", ast_xml_node_get_name(root_node)); + return -1; + } + + return 0; +#else + return -1 +#endif +} + +/* Handle dialog notifications */ +static int sip_handle_notify_dialog(struct sip_pvt *pvt, struct sip_request *req) +{ +#ifdef HAVE_LIBXML2 + struct sip_peer *peer; + const char *content_type = sip_request_get_header(req, "Content-Type"); + char *dialog_body, *uri, *name, *domain; + RAII_VAR(struct ast_xml_doc *, dialog_doc, NULL, ast_xml_close); + struct ast_xml_node *dialog_info_node, *dialog_info_children; + struct ast_xml_node *dialog_node, *dialog_children; + struct ast_xml_node *state_node; + RAII_VAR(const char *, entity_attr, NULL, ast_xml_free_attr); + RAII_VAR(const char *, state_text, NULL, ast_xml_free_text); + int offhook = 0; + + if (strcasecmp(content_type, "application/dialog-info+xml")) { + ast_log(LOG_WARNING, "Content type is not application/dialog-info+xml\n"); + return -1; + } + + if (!(dialog_body = sip_request_get_content(req, 0, req->lines - 1))) { + ast_log(LOG_WARNING, "Unable to get dialog body\n"); + return -1; + } + + if (!(dialog_doc = ast_xml_read_memory(dialog_body, strlen(dialog_body)))) { + ast_log(LOG_WARNING, "Unable to open XML dialog document. Is it malformed?\n"); + return -1; + } + + if (!(dialog_info_node = ast_xml_get_root(dialog_doc))) { + ast_log(LOG_WARNING, "Unable to get root node\n"); + ast_xml_close(dialog_doc); + return -1; + } + + if (strcasecmp(ast_xml_node_get_name(dialog_info_node), "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_attr = ast_xml_get_attribute(dialog_info_node, "entity"))) { + ast_log(LOG_WARNING, "Missing entity attribute"); + return -1; + } + + uri = ast_strdupa(entity_attr); + + if (!(dialog_info_children = ast_xml_node_get_children(dialog_info_node))) { + ast_log(LOG_WARNING, "No tuples in dialog-info node\n"); + return -1; + } + + if (!(dialog_node = ast_xml_find_element(dialog_info_children, "dialog", NULL, NULL))) { + ast_log(LOG_WARNING, "Missing dialog node\n"); + return -1; + } + + if (!(dialog_children = ast_xml_node_get_children(dialog_node))) { + ast_log(LOG_WARNING, "No tuples in dialog node\n"); + return -1; + } + + if (!(state_node = ast_xml_find_element(dialog_children, "state", NULL, NULL))) { + ast_log(LOG_WARNING, "Missing state node\n"); + return -1; + } + + state_text = ast_xml_get_text(state_node); + + if (!strcasecmp(state_text, "trying")) { + offhook = 1; + } else if (!strcasecmp(state_text, "terminated")) { + offhook = -1; + } else { + ast_log(LOG_WARNING, "Invalid content in state node %s\n", state_text); + return -1; + } + + if (sip_uri_parse(uri, "sip:,sips", &name, NULL, &domain, NULL)) { + return -1; + } + + sip_pedantic_decode(name); + + if (!(peer = sip_peer_find(name, NULL, TRUE, TRUE, 0))) { + ast_log(LOG_WARNING, "Unknown peer '%s'\n", name); + return -1; + } + + if (peer->socket.type == pvt->socket.type && !ast_sockaddr_cmp(&peer->addr, &pvt->recv)) { + 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, "sip_handle_notify_dialog: unref peer from sip_peer_find lookup"); + sip_send_response(pvt, "200 OK", req); + + return 0; +#else + return -1; +#endif +} + +static int sip_handle_publish_presence(struct sip_pvt *pvt, struct sip_request *req) +{ +#ifdef HAVE_LIBXML2 + const char *content_type = sip_request_get_header(req, "Content-Type"); + const char *etag = sip_request_get_header(req, "SIP-If-Match"); + char *pidf_body; + RAII_VAR(struct ast_xml_doc *, pidf_doc, NULL, ast_xml_close); + struct ast_xml_node *presence_node; + struct ast_xml_node *presence_children; + struct ast_xml_node *person_node; + struct ast_xml_node *person_children; + struct ast_xml_node *activities_node; + struct ast_xml_node *activities_children; + struct ast_xml_node *dnd_node; + struct ast_xml_node *available_node; + struct sip_peer *peer; + struct sip_alias *alias; + int res = -1; + int donotdisturb = FALSE; + + if (ast_strlen_zero(content_type) || strcmp(content_type, "application/pidf+xml")) { + if (!ast_strlen_zero(etag)) { + return 0; + } + + ast_log(LOG_WARNING, "Content type is not PIDF\n"); + goto cleanup; + } + + if (!(pidf_body = sip_request_get_content(req, 0, req->lines))) { + ast_log(LOG_WARNING, "Unable to get PIDF body\n"); + goto cleanup; + } + + if (!(pidf_doc = ast_xml_read_memory(pidf_body, strlen(pidf_body)))) { + ast_log(LOG_WARNING, "Unable to open XML PIDF document. Is it malformed?\n"); + goto cleanup; + } + + presence_node = ast_xml_get_root(pidf_doc); + + if (strcmp(ast_xml_node_get_name(presence_node), "presence")) { + ast_log(LOG_WARNING, "Root node of PIDF document is not 'presence'. Invalid\n"); + goto cleanup; + } + + if (!(presence_children = ast_xml_node_get_children(presence_node))) { + ast_log(LOG_WARNING, "No tuples within presence element.\n"); + goto cleanup; + } + + if (!(person_node = ast_xml_find_element(presence_children, "person", NULL, NULL))) { + ast_log(LOG_NOTICE, "Couldn't find person node?\n"); + goto cleanup; + } + + if (!(person_children = ast_xml_node_get_children(person_node))) { + ast_log(LOG_NOTICE, "No tuples within person node.\n"); + goto cleanup; + } + + if (!(activities_node = ast_xml_find_element(person_children, "activities", NULL, NULL))) { + ast_log(LOG_NOTICE, "Couldn't find activities node?\n"); + goto cleanup; + } + + if (!(activities_children = ast_xml_node_get_children(activities_node))) { + ast_log(LOG_NOTICE, "No tuples within activities node.\n"); + goto cleanup; + } + + if ((dnd_node = ast_xml_find_element(activities_children, "dnd", NULL, NULL))) { + donotdisturb = TRUE; + } else if ((available_node = ast_xml_find_element(activities_children, "available", NULL, NULL))) { + donotdisturb = FALSE; + } else { + ast_log(LOG_NOTICE, "Couldn't find dnd or available node?\n"); + goto cleanup; + } + + if (!(peer = sip_peer_find(pvt->peername, NULL, TRUE, FALSE, 0))) { + ast_log(LOG_NOTICE, "No such peer '%s'\n", pvt->peername); + goto cleanup; + } + + if (peer->donotdisturb != donotdisturb) { + peer->donotdisturb = donotdisturb; + 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->donotdisturb = peer->donotdisturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", alias->peer->name); + } + } + + if (!peer->is_realtime) { + ast_db_put("SIP/DoNotDisturb", peer->name, peer->donotdisturb ? "yes" : "no"); + } else if (sip_config.peer_rtupdate && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name, "donotdisturb", donotdisturb ? "yes" : "no", SENTINEL); + } + } + + ao2_t_cleanup(peer, "from sip_peer_find call"); + res = 0; + +cleanup: + if (res) { + sip_send_response(pvt, "400 Bad Request", req); + } + + return res; +#else + return -1; +#endif /* HAVE_LIBXML2 */ +} + +/* Handle incoming remotecc request */ +static int sip_handle_refer_remotecc(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer) +{ +#ifdef HAVE_LIBXML2 + const char *content_type = sip_request_get_header(req, "Content-Type"); + char *remotecc_body; + RAII_VAR(struct ast_xml_doc *, remotecc_doc, NULL, ast_xml_close); + struct ast_xml_node *remotecc_request_node, *remotecc_request_children; + struct ast_xml_node *softkeyeventmsg_node, *softkeyeventmsg_children; + struct ast_xml_node *datapassthroughreq_node, *datapassthroughreq_children; + struct sip_remotecc_data remotecc_data; + int start, end, done = FALSE; + char *boundary = NULL; + + 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 ((start = sip_request_find_boundary(req, boundary, 0, &done)) == -1) { + return -1; + } + + start += 1; + + if ((end = sip_request_find_boundary(req, boundary, start, &done)) == -1) { + return -1; + } + + content_type = NULL; + + while (start < end) { + const char *line = SIP_REQUEST_PART(req, line[start++]); + + if (!strncasecmp(line, "Content-Type:", 13)) { + content_type = ast_skip_blanks(line + 13); + } else if (ast_strlen_zero(line)) { + break; + } + } + + if (ast_strlen_zero(content_type)) { + return -1; + } + } else { + start = 0; + end = req->lines; + } + + 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 (!(remotecc_body = sip_request_get_content(req, start, end - 1))) { + ast_log(LOG_WARNING, "Unable to get remotecc body\n"); + return -1; + } + + if (!(remotecc_doc = ast_xml_read_memory(remotecc_body, strlen(remotecc_body)))) { + ast_log(LOG_WARNING, "Unable to open XML remotecc document. Is it malformed?\n"); + return -1; + } + + if (!(remotecc_request_node = ast_xml_get_root(remotecc_doc))) { + ast_log(LOG_WARNING, "Unable to get root node\n"); + return -1; + } + + if (strcasecmp(ast_xml_node_get_name(remotecc_request_node), "x-cisco-remotecc-request")) { + ast_log(LOG_WARNING, "Missing x-cisco-remotecc-request node\n"); + return -1; + } + + if (!(remotecc_request_children = ast_xml_node_get_children(remotecc_request_node))) { + ast_log(LOG_WARNING, "No tuples in x-cisco-remotecc-request node\n"); + return -1; + } + + memset(&remotecc_data, 0, sizeof(remotecc_data)); + + if ((softkeyeventmsg_node = ast_xml_find_element(remotecc_request_children, "softkeyeventmsg", NULL, NULL)) && + (softkeyeventmsg_children = ast_xml_node_get_children(softkeyeventmsg_node))) { + struct ast_xml_node *softkeyevent_node; + struct ast_xml_node *dialogid_node, *dialogid_children; + struct ast_xml_node *consultdialogid_node, *consultdialogid_children; + struct ast_xml_node *joindialogid_node, *joindialogid_children; + struct ast_xml_node *callid_node, *localtag_node, *remotetag_node; + + if ((softkeyevent_node = ast_xml_find_element(softkeyeventmsg_children, "softkeyevent", NULL, NULL))) { + RAII_VAR(const char *, softkeyevent_text, NULL, ast_xml_free_text); + + softkeyevent_text = ast_xml_get_text(softkeyevent_node); + remotecc_data.softkeyevent = ast_strdupa(softkeyevent_text); + } + + if ((dialogid_node = ast_xml_find_element(softkeyeventmsg_children, "dialogid", NULL, NULL)) && + (dialogid_children = ast_xml_node_get_children(dialogid_node))) { + if ((callid_node = ast_xml_find_element(dialogid_children, "callid", NULL, NULL))) { + RAII_VAR(const char *, callid_text, NULL, ast_xml_free_text); + + callid_text = ast_xml_get_text(callid_node); + remotecc_data.dialogid.callid = ast_strdupa(callid_text); + } + + if ((localtag_node = ast_xml_find_element(dialogid_children, "localtag", NULL, NULL))) { + RAII_VAR(const char *, localtag_text, NULL, ast_xml_free_text); + + localtag_text = ast_xml_get_text(localtag_node); + remotecc_data.dialogid.localtag = ast_strdupa(localtag_text); + } + + if ((remotetag_node = ast_xml_find_element(dialogid_children, "remotetag", NULL, NULL))) { + RAII_VAR(const char *, remotetag_text, NULL, ast_xml_free_text); + + remotetag_text = ast_xml_get_text(remotetag_node); + remotecc_data.dialogid.remotetag = ast_strdupa(remotetag_text); + } + } + + if ((consultdialogid_node = ast_xml_find_element(softkeyeventmsg_children, "consultdialogid", NULL, NULL)) && + (consultdialogid_children = ast_xml_node_get_children(consultdialogid_node))) { + if ((callid_node = ast_xml_find_element(consultdialogid_children, "callid", NULL, NULL))) { + RAII_VAR(const char *, callid_text, NULL, ast_xml_free_text); + + callid_text = ast_xml_get_text(callid_node); + remotecc_data.consultdialogid.callid = ast_strdupa(callid_text); + } + + if ((localtag_node = ast_xml_find_element(consultdialogid_children, "localtag", NULL, NULL))) { + RAII_VAR(const char *, localtag_text, NULL, ast_xml_free_text); + + localtag_text = ast_xml_get_text(localtag_node); + remotecc_data.consultdialogid.localtag = ast_strdupa(localtag_text); + } + + if ((remotetag_node = ast_xml_find_element(consultdialogid_children, "remotetag", NULL, NULL))) { + RAII_VAR(const char *, remotetag_text, NULL, ast_xml_free_text); + + remotetag_text = ast_xml_get_text(remotetag_node); + remotecc_data.consultdialogid.remotetag = ast_strdupa(remotetag_text); + } + } + + if ((joindialogid_node = ast_xml_find_element(softkeyeventmsg_children, "joindialogid", NULL, NULL)) && + (joindialogid_children = ast_xml_node_get_children(joindialogid_node))) { + if ((callid_node = ast_xml_find_element(joindialogid_children, "callid", NULL, NULL))) { + RAII_VAR(const char *, callid_text, NULL, ast_xml_free_text); + + callid_text = ast_xml_get_text(callid_node); + remotecc_data.joindialogid.callid = ast_strdupa(callid_text); + } + + if ((localtag_node = ast_xml_find_element(joindialogid_children, "localtag", NULL, NULL))) { + RAII_VAR(const char *, localtag_text, NULL, ast_xml_free_text); + + localtag_text = ast_xml_get_text(localtag_node); + remotecc_data.joindialogid.localtag = ast_strdupa(localtag_text); + } + + if ((remotetag_node = ast_xml_find_element(joindialogid_children, "remotetag", NULL, NULL))) { + RAII_VAR(const char *, remotetag_text, NULL, ast_xml_free_text); + + remotetag_text = ast_xml_get_text(remotetag_node); + remotecc_data.joindialogid.remotetag = ast_strdupa(remotetag_text); + } + } + } else if ((datapassthroughreq_node = ast_xml_find_element(remotecc_request_children, "datapassthroughreq", NULL, NULL)) && + (datapassthroughreq_children = ast_xml_node_get_children(datapassthroughreq_node))) { + struct ast_xml_node *applicationid_node, *confid_node; + + if ((applicationid_node = ast_xml_find_element(datapassthroughreq_children, "applicationid", NULL, NULL))) { + RAII_VAR(const char *, applicationid_text, NULL, ast_xml_free_text); + + applicationid_text = ast_xml_get_text(applicationid_node); + remotecc_data.applicationid = atoi(S_OR(applicationid_text, "")); + } + + if ((confid_node = ast_xml_find_element(datapassthroughreq_children, "confid", NULL, NULL))) { + RAII_VAR(const char *, confid_text, NULL, ast_xml_free_text); + + confid_text = ast_xml_get_text(confid_node); + remotecc_data.confid = atoi(S_OR(confid_text, "")); + } + } + + if (boundary && !done) { + start = end + 1; + + if ((end = sip_request_find_boundary(req, boundary, start, &done)) == -1) { + ast_log(LOG_WARNING, "Failed to find end boundary\n"); + return -1; + } + + content_type = NULL; + + while (start < end) { + const char *line = SIP_REQUEST_PART(req, line[start++]); + + if (!strncasecmp(line, "Content-Type:", 13)) { + content_type = ast_skip_blanks(line + 13); + } else if (ast_strlen_zero(line)) { + break; + } + } + + if (ast_strlen_zero(content_type)) { + return -1; + } + + if (!strcasecmp(content_type, "application/x-cisco-remotecc-cm+xml")) { + char *usercalldata; + + if (!(usercalldata = sip_request_get_content(req, start, end - 1))) { + ast_log(LOG_WARNING, "Unable to get usercalldata body\n"); + return -1; + } + + remotecc_data.usercalldata = ast_trim_blanks(ast_strdupa(usercalldata)); + } + } + + if (!ast_strlen_zero(remotecc_data.softkeyevent)) { + if (!strcmp(remotecc_data.softkeyevent, "IDivert")) { + return sip_remotecc_idivert(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "HLog")) { + return sip_remotecc_hlog(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "Conference")) { + return sip_remotecc_conference(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "ConfList")) { + return sip_remotecc_conflist(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "ConfDetails")) { + return sip_remotecc_conflist(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "RmLastConf")) { + return sip_remotecc_rmlastconf(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "Cancel")) { + sip_send_response(pvt, "202 Accepted", req); + return 0; + } else if (!strcmp(remotecc_data.softkeyevent, "Select")) { + return sip_remotecc_select(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "Unselect")) { + return sip_remotecc_unselect(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "Join")) { + return sip_remotecc_join(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "Park")) { + return sip_remotecc_park(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "ParkMonitor")) { + return sip_remotecc_parkmonitor(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "StartRecording")) { + return sip_remotecc_startrecording(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "StopRecording")) { + return sip_remotecc_stoprecording(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "QRT")) { + return sip_remotecc_qrt(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "MCID")) { + return sip_remotecc_mcid(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "CallBack")) { + return sip_remotecc_callback(pvt, req, peer, &remotecc_data); + } + + ast_log(LOG_WARNING, "Unsupported softkeyevent: %s\n", remotecc_data.softkeyevent); + } else if (remotecc_data.applicationid) { + if (remotecc_data.applicationid == SIP_REMOTECC_CONFLIST) { + return sip_remotecc_conflist(pvt, req, peer, &remotecc_data); + } else if (remotecc_data.applicationid == SIP_REMOTECC_CALLBACK) { + return sip_remotecc_callback(pvt, req, peer, &remotecc_data); + } + + ast_log(LOG_WARNING, "Unsupported applicationid: %d\n", remotecc_data.applicationid); + } 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_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data) +{ + struct sip_pvt *target_pvt; + struct ast_channel *chan, *bridge_chan; + + /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ + if (!(target_pvt = sip_pvt_find(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { + ast_debug(1, "dialogid call leg does not exist\n"); + return -1; + } + + ao2_lock(target_pvt); + + if (!(chan = target_pvt->owner)) { + ast_debug(1, "no owner channel\n"); + ao2_unlock(target_pvt); + ao2_t_cleanup(target_pvt, "drop target_pvt"); + return -1; + } + + ast_channel_ref(chan); + ao2_unlock(target_pvt); + + ao2_t_cleanup(target_pvt, "drop target_pvt"); + sip_send_response(pvt, "202 Accepted", req); + + if (ast_channel_state(chan) == AST_STATE_RINGING) { + ast_queue_control(chan, AST_CONTROL_BUSY); + } else if (ast_channel_state(chan) == AST_STATE_UP) { + if ((bridge_chan = ast_channel_bridge_peer(chan))) { + pbx_builtin_setvar_helper(bridge_chan, "IDIVERT_PEERNAME", peer->name); + ast_async_goto(bridge_chan, peer->context, "idivert", 1); + + ast_channel_unref(bridge_chan); + } + } + + ast_channel_unref(chan); + + return 0; +} + +/* Handle hlog request */ +static int sip_remotecc_hlog(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data) +{ + struct sip_alias *alias; + + sip_send_response(pvt, "202 Accepted", req); + + peer->huntgroup = !peer->huntgroup; + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (alias->peer) { + alias->peer->huntgroup = peer->huntgroup; + } + } + + if (!peer->is_realtime) { + ast_db_put("SIP/HuntGroup", peer->name, peer->huntgroup ? "yes" : "no"); + } else if (sip_config.peer_rtupdate && ast_check_realtime("sippeers")) { + ast_update_realtime("sippeers", "name", peer->name, "huntgroup", peer->huntgroup ? "yes" : "no", SENTINEL); + } + + sip_peer_send_huntgroup(peer); + + return 0; +} + +/* Handle remotecc quality reporting tool requests */ +static int sip_remotecc_qrt(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data) +{ + struct sip_pvt *target_pvt, *refer_pvt; + struct ast_str *content = ast_str_alloca(8192); + + if (!ast_strlen_zero(remotecc_data->dialogid.callid)) { + /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ + if (!(target_pvt = sip_pvt_find(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { + ast_debug(1, "dialogid call leg does not exist\n"); + return -1; + } + + ast_set_flag(&target_pvt->flags[2], SIP_RTP_STATS_ON_BYE); + ao2_t_cleanup(target_pvt, "drop target_pvt"); + + sip_send_response(pvt, "202 Accepted", req); + + if (!(refer_pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + return 0; + } + + sip_pvt_copy_data(refer_pvt, pvt); + + 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->dialogid.callid); + ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.localtag); + ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.remotetag); + 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_pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_t_cleanup(refer_pvt, "bump down the count of refer_pvt since we're done with it."); + } else { + sip_send_response(pvt, "202 Accepted", req); + sip_peer_send_qrt_url(peer); + } + + return 0; +} + +/* Handle remotecc malicious call requests */ +static int sip_remotecc_mcid(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data) +{ + struct sip_pvt *target_pvt, *refer_pvt; + struct ast_channel *chan, *bridge_chan; + struct ast_str *content = ast_str_alloca(8192); + + /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ + if (!(target_pvt = sip_pvt_find(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { + ast_debug(1, "dialogid call leg does not exist\n"); + return -1; + } + + ao2_lock(target_pvt); + + if (!(chan = target_pvt->owner)) { + ast_debug(1, "no owner channel\n"); + + ao2_unlock(target_pvt); + ao2_t_cleanup(target_pvt, "drop target_pvt"); + + return -1; + } + + ast_channel_ref(chan); + + ao2_unlock(target_pvt); + ao2_t_cleanup(target_pvt, "drop target_pvt"); + + sip_send_response(pvt, "202 Accepted", req); + + if ((bridge_chan = ast_channel_bridge_peer(chan))) { + ast_verb(3, "%s has a malicious call from '%s'\n", target_pvt->peername, ast_channel_name(bridge_chan)); + + ast_queue_control(chan, AST_CONTROL_MCID); + ast_channel_unref(bridge_chan); + } + + ast_channel_unref(chan); + + if (!(refer_pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + return 0; + } + + sip_pvt_copy_data(refer_pvt, pvt); + + 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->dialogid.callid); + ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.localtag); + ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.remotetag); + 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->dialogid.callid); + ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.localtag); + ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.remotetag); + 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_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(refer_pvt, "bump down the count of refer_pvt since we're done with it."); + + return 0; +} + +/* Handle remotecc start recording requests */ +static int sip_remotecc_startrecording(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data) +{ + sip_send_response(pvt, "202 Accepted", req); + sip_recording_start(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag, TRUE); + + return 0; +} + +/* Handle remotecc stop recording requests */ +static int sip_remotecc_stoprecording(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data) +{ + sip_send_response(pvt, "202 Accepted", req); + sip_recording_stop(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag); + + return 0; +} + +/* Handle SIP response in dialog, only called by sip_handle_request */ +static void sip_handle_response(struct sip_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno) +{ + struct ast_channel *owner; + int method; + const char *cseq = ast_strdupa(sip_request_get_header(req, "Cseq")); + /* GCC 4.2 complains if I try to cast c as a char * when passing it to ast_skip_nonblanks, so make a copy of it */ + /* Skip the Cseq and its subsequent spaces */ + const char *msg = ast_skip_blanks(ast_skip_nonblanks(cseq)); + int ack_res = FALSE; + + if (!msg) { + msg = ""; + } + + method = sip_method_find(msg); + + if ((owner = pvt->owner)) { + ast_channel_hangupcause_set(owner, 0); + + if (sip_parse_reason(pvt, req)) { + /* Use the SIP cause */ + ast_channel_hangupcause_set(owner, sip_hangup2cause(resp)); + } + } + + /* Acknowledge whatever it is destined for */ + if (resp >= 100 && resp <= 199) { + /* NON-INVITE messages do not ack a 1XX response. RFC 3261 section 17.1.2.2 */ + if (method == SIP_INVITE) { + ack_res = sip_packet_semi_ack(pvt, seqno, FALSE, method); + } + } else { + ack_res = sip_packet_ack(pvt, seqno, FALSE, method); + } + + if (ack_res == FALSE) { + /* RFC 3261 13.2.2.4 and 17.1.1.2 - We must re-send ACKs to re-transmitted final responses */ + if (method == SIP_INVITE && resp >= 200) { + sip_send_request(pvt, SIP_ACK, seqno, SIP_SEND_UNRELIABLE, resp < 300); + } + + sip_history_append(pvt, "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 (!pvt->owner && method == SIP_NOTIFY && pvt->pendinginvite) { + pvt->pendinginvite = 0; + } + + /* Get their tag if we haven't already */ + if (ast_strlen_zero(pvt->theirtag) || (resp >= 200)) { + char tag[128]; + + sip_request_get_tag(req, "To", tag, sizeof(tag)); + ast_string_field_set(pvt, theirtag, tag); + } else { + /* Store theirtag to track for changes when 200 responses to invites are received without SDP */ + ast_string_field_set(pvt, theirprovtag, pvt->theirtag); + } + + /* 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 ((resp == 404 || resp == 408 || resp == 481) && method == SIP_BYE) { + sip_pvt_set_need_destroy(pvt, "received 4XX response to a BYE"); + return; + } + + if (pvt->relatedpeer && method == SIP_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 (resp != 100) { + sip_handle_response_options(pvt, resp, req, seqno); + } + } else if (method == SIP_REFER && resp >= 200) { + sip_handle_response_refer(pvt, resp, req, seqno); + } else if (method == SIP_INFO) { + /* More good gravy! */ + sip_handle_response_info(pvt, resp, req, seqno); + } else if (method == SIP_MESSAGE) { + /* More good gravy! */ + sip_handle_response_message(pvt, resp, req, seqno); + } else if (method == SIP_NOTIFY) { + /* The gravy train continues to roll */ + sip_handle_response_notify(pvt, resp, req, seqno); + } else if (ast_test_flag(&pvt->flags[0], SIP_OUTGOING)) { + switch (resp) { + 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 (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } + + break; + case 200: /* 200 OK */ + pvt->authtries = 0; /* Reset authentication counter */ + + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } else if (method == SIP_REGISTER) { + sip_handle_response_register(pvt, resp, req, seqno); + } else if (method == SIP_SUBSCRIBE) { + ast_set_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED); + sip_handle_response_subscribe(pvt, resp, req, seqno); + } else if (method == SIP_BYE) { /* Ok, we're ready to go */ + sip_pvt_set_need_destroy(pvt, "received 200 response"); + ast_clear_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED); + } + + break; + case 401: /* Not www-authorized on SIP method */ + case 407: /* Proxy auth required */ + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } else if (method == SIP_SUBSCRIBE) { + sip_handle_response_subscribe(pvt, resp, req, seqno); + } else if (pvt->registry && method == SIP_REGISTER) { + sip_handle_response_register(pvt, resp, req, seqno); + } else if (method == SIP_UPDATE) { + sip_handle_response_update(pvt, resp, req, seqno); + } else if (method == SIP_BYE) { + if (pvt->options) { + pvt->options->auth_type = resp; + } + + if (ast_strlen_zero(pvt->authname)) { + ast_log(LOG_WARNING, "Asked to authenticate %s, to %s but we have no matching peer!\n", + msg, ast_sockaddr_stringify(&pvt->recv)); + sip_pvt_set_need_destroy(pvt, "unable to authenticate BYE"); + } else if ((pvt->authtries == SIP_MAX_AUTHTRIES) || sip_pvt_handle_proxy_auth(pvt, req, resp, method, SIP_INIT_NONE)) { + ast_log(LOG_NOTICE, "Failed to authenticate on %s to '%s'\n", msg, sip_request_get_header(&pvt->initreq, "From")); + sip_pvt_set_need_destroy(pvt, "failed to authenticate BYE"); + } + } else { + ast_log(LOG_WARNING, "Got authentication request (%d) on %s to '%s'\n", + resp, sip_methods[method].name, sip_request_get_header(req, "To")); + + sip_pvt_set_need_destroy(pvt, "received 407 response"); + } + + break; + case 403: /* Forbidden - we failed authentication */ + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } else if (method == SIP_SUBSCRIBE) { + sip_handle_response_subscribe(pvt, resp, req, seqno); + } else if (pvt->registry && method == SIP_REGISTER) { + sip_handle_response_register(pvt, resp, req, seqno); + } else { + ast_log(LOG_WARNING, "Forbidden - maybe wrong password on authentication for %s\n", msg); + sip_pvt_set_need_destroy(pvt, "received 403 response"); + } + + break; + case 400: /* Bad Request */ + case 414: /* Request URI too long */ + case 493: /* Undecipherable */ + case 404: /* Not found */ + if (pvt->registry && method == SIP_REGISTER) { + sip_handle_response_register(pvt, resp, req, seqno); + } else if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } else if (method == SIP_SUBSCRIBE) { + sip_handle_response_subscribe(pvt, resp, req, seqno); + } else if (owner) { + ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION); + } + + break; + case 423: /* Interval too brief */ + if (method == SIP_REGISTER) { + sip_handle_response_register(pvt, resp, req, seqno); + } + + break; + case 408: /* Request timeout - terminate dialog */ + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } else if (method == SIP_REGISTER) { + sip_handle_response_register(pvt, resp, req, seqno); + } else if (method == SIP_BYE) { + sip_pvt_set_need_destroy(pvt, "received 408 response"); + ast_debug(4, "Got timeout on bye. Thanks for the answer. Now, kill this call\n"); + } else { + if (owner) { + ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION); + } + + sip_pvt_set_need_destroy(pvt, "received 408 response"); + } + + break; + case 428: + case 422: /* Session-Timers: Session Interval Too Small */ + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } + + break; + case 480: + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } else if (method == SIP_SUBSCRIBE) { + sip_handle_response_subscribe(pvt, resp, req, seqno); + } else if (owner) { + /* No specific handler. Default to congestion */ + ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION); + } + + break; + case 481: /* Call leg does not exist */ + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } else if (method == SIP_SUBSCRIBE) { + sip_handle_response_subscribe(pvt, resp, req, seqno); + } else if (method == SIP_BYE) { + /* The other side has no transaction to bye, + just assume it's all right then */ + ast_log(LOG_WARNING, "Remote host can't match request %s to call '%s'. Giving up.\n", sip_methods[method].name, pvt->callid); + } else if (method == SIP_CANCEL) { + /* The other side has no transaction to cancel, + just assume it's all right then */ + ast_log(LOG_WARNING, "Remote host can't match request %s to call '%s'. Giving up.\n", sip_methods[method].name, pvt->callid); + } else { + ast_log(LOG_WARNING, "Remote host can't match request %s to call '%s'. Giving up.\n", sip_methods[method].name, pvt->callid); + /* Guessing that this is not an important request */ + } + + break; + case 487: + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } + + break; + case 415: /* Unsupported media type */ + case 488: /* Not acceptable here - codec error */ + case 606: /* Not Acceptable */ + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } + + break; + case 491: /* Pending */ + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } else { + ast_debug(1, "Got 491 on %s, unsupported. Call ID %s\n", sip_methods[method].name, pvt->callid); + sip_pvt_set_need_destroy(pvt, "received 491 response"); + } + + break; + case 405: /* Method not allowed */ + case 501: /* Not Implemented */ + sip_method_set_disallowed(&pvt->allowed_methods, method); + + if (pvt->relatedpeer) { + sip_method_set_allowed(&pvt->relatedpeer->disallowed_methods, method); + } + + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } else { + ast_log(LOG_WARNING, "Host '%s' does not implement '%s'\n", ast_sockaddr_stringify(&pvt->sa), msg); + } + + break; + default: + if (resp >= 200 && resp < 300) { /* on any 2XX response do the following */ + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } + } else if (resp >= 300 && resp < 700) { + /* Fatal response */ + if (resp != 487) { + ast_verb(3, "Got SIP response %d \"%s\" back from %s\n", + resp, SIP_REQUEST_PART(req, rlpart2), ast_sockaddr_stringify(&pvt->sa)); + } + + if (method == SIP_INVITE) { + sip_pvt_stop_rtp(pvt); /* Immediately stop RTP, VRTP and UDPTL as applicable */ + } + + /* XXX Locking issues?? XXX */ + switch (resp) { + case 300: /* Multiple Choices */ + case 301: /* Moved permanently */ + case 302: /* Moved temporarily */ + case 305: /* Use Proxy */ + if (pvt->owner) { + struct ast_party_redirecting redirecting; + struct ast_set_party_redirecting update_redirecting; + + ast_party_redirecting_init(&redirecting); + memset(&update_redirecting, 0, sizeof(update_redirecting)); + + sip_pvt_set_redirecting(pvt, req, &redirecting, &update_redirecting, TRUE); + + ast_channel_set_redirecting(pvt->owner, &redirecting, &update_redirecting); + ast_party_redirecting_free(&redirecting); + } + /* Fall through */ + case 486: /* Busy here */ + case 600: /* Busy everywhere */ + case 603: /* Decline */ + if (pvt->owner) { + ast_queue_control(pvt->owner, AST_CONTROL_BUSY); + } + + break; + case 482: /* Loop Detected */ + case 404: /* Not Found */ + case 410: /* Gone */ + case 400: /* Bad Request */ + case 500: /* Server error */ + if (method == SIP_SUBSCRIBE) { + sip_handle_response_subscribe(pvt, resp, req, seqno); + break; + } + /* Fall through */ + case 502: /* Bad gateway */ + case 503: /* Service Unavailable */ + case 504: /* Server Timeout */ + if (owner) { + ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION); + } + + break; + case 484: /* Address Incomplete */ + if (owner && method != SIP_BYE) { + switch (ast_test_flag(&pvt->flags[1], SIP_ALLOWOVERLAP)) { + case SIP_ALLOWOVERLAP_YES: + ast_queue_hangup_with_cause(pvt->owner, sip_hangup2cause(resp)); + break; + default: + ast_queue_hangup_with_cause(pvt->owner, sip_hangup2cause(404)); + break; + } + } + + break; + default: + /* Send hangup */ + if (owner && method != SIP_BYE) { + ast_queue_hangup_with_cause(pvt->owner, sip_hangup2cause(resp)); + } + + break; + } + + /* ACK on invite */ + if (method == SIP_INVITE) { + sip_send_request(pvt, SIP_ACK, seqno, SIP_SEND_UNRELIABLE, FALSE); + } + + sip_pvt_set_already_gone(pvt); + + if (!pvt->owner) { + sip_pvt_set_need_destroy(pvt, "transaction completed"); + } + } else if (resp >= 100 && resp < 200) { + if (method == SIP_INVITE) { + if (!req->ignore) { + sip_pvt_cancel_destroy(pvt); + } + + if (sip_request_find_sdp(req)) { + sip_sdp_parse(pvt, req, SIP_SDP_T38_NONE, FALSE); + } + + if (pvt->owner) { + /* Queue a progress frame */ + ast_queue_control(pvt->owner, AST_CONTROL_PROGRESS); + } + } + } else { + ast_log(LOG_NOTICE, "Don't know how to handle a %d %s response from %s\n", + resp, SIP_REQUEST_PART(req, rlpart2), pvt->owner ? ast_channel_name(pvt->owner) : ast_sockaddr_stringify(&pvt->sa)); + } + } + } else { + /* Responses to OUTGOING SIP requests on INCOMING calls get handled here. As well as out-of-call message responses */ + if (req->debug) { + ast_verb(3, "SIP Response message for INCOMING dialog %s arrived\n", msg); + } + + if (method == SIP_INVITE && resp == 200) { + /* Tags in early session is replaced by the tag in 200 OK, which is the final reply to our INVITE */ + char tag[128]; + + sip_request_get_tag(req, "To", tag, sizeof(tag)); + ast_string_field_set(pvt, theirtag, tag); + } + + switch (resp) { + case 200: + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } else if (method == SIP_CANCEL) { + ast_debug(1, "Got 200 OK on CANCEL\n"); + + /* Wait for 487, then destroy */ + } else if (method == SIP_BYE) { + if (ast_test_flag(&pvt->flags[2], SIP_RTP_STATS_ON_BYE)) { + sip_parse_rtp_stats(pvt, req); + } + + sip_pvt_set_need_destroy(pvt, "transaction completed"); + } + + break; + case 401: /* www-auth */ + case 407: + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } else if (method == SIP_BYE) { + if (pvt->authtries == SIP_MAX_AUTHTRIES || sip_pvt_handle_proxy_auth(pvt, req, resp, method, SIP_INIT_NONE)) { + ast_log(LOG_NOTICE, "Failed to authenticate on %s to '%s'\n", msg, sip_request_get_header(&pvt->initreq, "From")); + sip_pvt_set_need_destroy(pvt, "failed to authenticate BYE"); + } + } + + break; + case 481: /* Call leg does not exist */ + if (method == SIP_INVITE) { + /* Re-invite failed */ + sip_handle_response_invite(pvt, resp, req, seqno); + } else if (method == SIP_BYE) { + sip_pvt_set_need_destroy(pvt, "received 481 response"); + } else if (sip_debug) { + ast_debug(1, "Remote host can't match request %s to call '%s'. Giving up\n", sip_methods[method].name, pvt->callid); + } + + break; + case 501: /* Not Implemented */ + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } + + break; + default: /* Errors without handlers */ + if (resp >= 100 && resp < 200) { + if (method == SIP_INVITE) { /* re-invite */ + if (!req->ignore) { + sip_pvt_cancel_destroy(pvt); + } + } + } else if (resp >= 200 && resp < 300) { /* on any unrecognized 2XX response do the following */ + if (method == SIP_INVITE) { + sip_handle_response_invite(pvt, resp, req, seqno); + } + } else if (resp >= 300 && resp < 700) { + if (resp != 487) { + ast_verb(3, "Incoming call: Got SIP response %d \"%s\" back from %s\n", + resp, SIP_REQUEST_PART(req, rlpart2), ast_sockaddr_stringify(&pvt->sa)); + } + + switch (resp) { + case 415: /* Unsupported media type */ + case 488: /* Not acceptable here - codec error */ + case 603: /* Decline */ + case 500: /* Server error */ + case 502: /* Bad gateway */ + case 503: /* Service Unavailable */ + case 504: /* Server timeout */ + /* re-invite failed */ + if (method == SIP_INVITE) { + sip_pvt_cancel_destroy(pvt); + } + + break; + } + } + + break; + } + } +} + +/* Handle SIP response to INVITE dialogue */ +static void sip_handle_response_invite(struct sip_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno) +{ + int res = 0, send_res = 0; + int outgoing = !!ast_test_flag(&pvt->flags[0], SIP_OUTGOING); + int reinvite = !!ast_test_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED); + const char *min_se; + + if (reinvite) { + ast_debug(4, "SIP response %d to RE-invite on %s call %s\n", resp, outgoing ? "outgoing" : "incoming", pvt->callid); + } else { + ast_debug(4, "SIP response %d to standard invite\n", resp); + } + + if (pvt->alreadygone) { /* This call is already gone */ + ast_debug(1, "Got response on call that is already terminated: %s (ignoring)\n", pvt->callid); + 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, pvt->initid, + ao2_t_cleanup(pvt, "when you delete the initid sched, you should dec the refcount for the stored dialog ptr")); + + /* RFC3261 says we must treat every 1xx response (but not 100) that we don't recognize as if it was 183. */ + if (resp > 100 && resp < 200 && resp!=101 && resp != 180 && resp != 181 && resp != 182 && resp != 183) { + resp = 183; + } + + /* For INVITE, treat all 2XX responses as we would a 200 response */ + if (resp >= 200 && resp < 300) { + resp = 200; + } + + /* Any response between 100 and 199 is PROCEEDING */ + if (resp >= 100 && resp < 200 && pvt->invitestate == SIP_INVITE_CALLING) { + pvt->invitestate = SIP_INVITE_PROCEEDING; + } + + /* Final response, not 200 ? */ + if (resp >= 300 && (pvt->invitestate == SIP_INVITE_CALLING || pvt->invitestate == SIP_INVITE_PROCEEDING || pvt->invitestate == SIP_INVITE_EARLY_MEDIA)) { + pvt->invitestate = SIP_INVITE_COMPLETED; + } + + if (resp >= 200 && reinvite) { + pvt->ongoing_reinvite = FALSE; + sip_pvt_stop_reinvite(pvt); + } + + /* Final response, clear out pending invite */ + if ((resp == 200 || resp >= 300) && pvt->pendinginvite && seqno == pvt->pendinginvite) { + pvt->pendinginvite = 0; + } + + /* If this is a response to our initial INVITE, we need to set what we can use for this peer. */ + if (!reinvite) { + sip_pvt_set_allowed_methods(pvt, req); + } + + switch (resp) { + case 100: /* Trying */ + case 101: /* Dialog establishment */ + if (!req->ignore && pvt->invitestate != SIP_INVITE_CANCELLED) { + sip_pvt_cancel_destroy(pvt); + } + + sip_pvt_sched_check_pendings(pvt); + break; + case 180: /* 180 Ringing */ + case 182: /* 182 Queued */ + if (!req->ignore && pvt->invitestate != SIP_INVITE_CANCELLED) { + sip_pvt_cancel_destroy(pvt); + } + + /* Store Route-set from provisional SIP responses so early-dialog request can be routed properly */ + sip_parse_ok_contact(pvt, req); + + if (!reinvite) { + sip_pvt_build_route(pvt, req, TRUE, resp); + } + + if (!req->ignore && pvt->owner) { + if (sip_parse_rpid(pvt, req)) { + sip_pvt_queue_connected_line_update(pvt, AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER); + } + + ast_queue_control(pvt->owner, AST_CONTROL_RINGING); + + if (ast_channel_state(pvt->owner) != AST_STATE_UP) { + ast_setstate(pvt->owner, AST_STATE_RINGING); + + if (pvt->relatedpeer) { + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_NOT_CACHABLE, "SIP/%s", pvt->relatedpeer->name); + } + } + + if (ast_test_flag(&pvt->flags[1], SIP_CISCO_USECALLMANAGER)) { + const char *autoanswer = pbx_builtin_getvar_helper(pvt->owner, "CISCO_AUTOANSWER"); + + if (ast_true(autoanswer)) { + struct sip_pvt *answer_pvt; + + if ((answer_pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + struct ast_str *content = ast_str_alloca(8192); + + sip_pvt_copy_data(answer_pvt, pvt); + + 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", pvt->callid); + ast_str_append(&content, 0, "%s\n", pvt->theirtag); + ast_str_append(&content, 0, "%s\n", pvt->tag); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + ao2_lock(answer_pvt); + sip_send_refer_with_content(answer_pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_unlock(answer_pvt); + + ao2_t_cleanup(answer_pvt, "bump down the count of pvt since we're done with it."); + } + } + } + } + + if (sip_request_find_sdp(req)) { + if (pvt->invitestate != SIP_INVITE_CANCELLED) { + pvt->invitestate = SIP_INVITE_EARLY_MEDIA; + } + + res = sip_sdp_parse(pvt, req, SIP_SDP_T38_NONE, FALSE); + + if (!req->ignore && pvt->owner) { + /* Queue a progress frame only if we have SDP in 180 or 182 */ + ast_queue_control(pvt->owner, AST_CONTROL_PROGRESS); + /* We have not sent progress, but we have been sent progress so enable early media */ + ast_set_flag(&pvt->flags[0], SIP_PROGRESS_SENT); + } + + ast_rtp_instance_activate(pvt->rtp); + } + + sip_pvt_sched_check_pendings(pvt); + break; + case 181: /* Call Is Being Forwarded */ + if (!req->ignore && pvt->invitestate != SIP_INVITE_CANCELLED) { + sip_pvt_cancel_destroy(pvt); + } + + /* Store Route-set from provisional SIP responses so early-dialog request can be routed properly */ + sip_parse_ok_contact(pvt, req); + + if (!reinvite) { + sip_pvt_build_route(pvt, req, TRUE, resp); + } + + if (!req->ignore && pvt->owner) { + struct ast_party_redirecting redirecting; + struct ast_set_party_redirecting update_redirecting; + + ast_party_redirecting_init(&redirecting); + memset(&update_redirecting, 0, sizeof(update_redirecting)); + + sip_pvt_set_redirecting(pvt, req, &redirecting, &update_redirecting, FALSE); + + /* Invalidate any earlier private redirecting id representations */ + ast_set_party_id_all(&update_redirecting.priv_orig); + ast_set_party_id_all(&update_redirecting.priv_from); + ast_set_party_id_all(&update_redirecting.priv_to); + + ast_channel_queue_redirecting_update(pvt->owner, &redirecting, &update_redirecting); + ast_party_redirecting_free(&redirecting); + } + + sip_pvt_sched_check_pendings(pvt); + break; + case 183: /* Session progress */ + if (!req->ignore && pvt->invitestate != SIP_INVITE_CANCELLED) { + sip_pvt_cancel_destroy(pvt); + } + + /* Store Route-set from provisional SIP responses so early-dialog request can be routed properly */ + sip_parse_ok_contact(pvt, req); + + if (!reinvite) { + sip_pvt_build_route(pvt, req, TRUE, resp); + } + + if (!req->ignore && pvt->owner) { + if (sip_parse_rpid(pvt, req)) { + sip_pvt_queue_connected_line_update(pvt, AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER); + } + } + + if (sip_request_find_sdp(req)) { + if (pvt->invitestate != SIP_INVITE_CANCELLED) { + pvt->invitestate = SIP_INVITE_EARLY_MEDIA; + } + + res = sip_sdp_parse(pvt, req, SIP_SDP_T38_NONE, FALSE); + + if (!req->ignore && pvt->owner) { + /* Queue a progress frame */ + ast_queue_control(pvt->owner, AST_CONTROL_PROGRESS); + /* We have not sent progress, but we have been sent progress so enable early media */ + ast_set_flag(&pvt->flags[0], SIP_PROGRESS_SENT); + } + + ast_rtp_instance_activate(pvt->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 (!req->ignore && pvt->owner) { + ast_queue_control(pvt->owner, AST_CONTROL_RINGING); + } + } + + sip_pvt_sched_check_pendings(pvt); + break; + case 200: /* 200 OK on invite - someone's answering our call */ + if (!req->ignore && pvt->invitestate != SIP_INVITE_CANCELLED) { + sip_pvt_cancel_destroy(pvt); + } + + pvt->authtries = 0; + + if (sip_request_find_sdp(req)) { + res = sip_sdp_parse(pvt, req, SIP_SDP_T38_ACCEPT, FALSE); + + if (res && !req->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(&pvt->flags[0], SIP_PENDINGBYE); + pvt->hangupcause = AST_CAUSE_BEARERCAPABILITY_NOTAVAIL; + + if (pvt->owner) { + ast_channel_hangupcause_set(pvt->owner, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); + sip_queue_hangup_cause(pvt, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); + } + } + } + + ast_rtp_instance_activate(pvt->rtp); + } else if (!reinvite) { + struct ast_sockaddr remote_address = {{0}}; + + ast_rtp_instance_get_requested_target_address(pvt->rtp, &remote_address); + + if (ast_sockaddr_isnull(&remote_address) || (!ast_strlen_zero(pvt->theirprovtag) && strcmp(pvt->theirtag, pvt->theirprovtag))) { + ast_log(LOG_WARNING, "Received response: \"200 OK\" from '%s' without SDP\n", pvt->relatedpeer->name); + ast_set_flag(&pvt->flags[0], SIP_PENDINGBYE); + ast_rtp_instance_activate(pvt->rtp); + } + } + + if (!req->ignore && pvt->owner) { + if (sip_parse_rpid(pvt, req) || !reinvite) { + sip_pvt_queue_connected_line_update(pvt, 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_pvt_update_call_counter(pvt, SIP_DEC_CALL_RINGING); + sip_parse_ok_contact(pvt, req); + + /* Save Record-Route for any later requests we make on this dialogue */ + if (!reinvite) { + sip_pvt_build_route(pvt, req, TRUE, resp); + } + + if (ast_test_flag(&pvt->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 */ + pvt->sa = pvt->recv; + } else if (sip_parse_contact(pvt->fullcontact, &pvt->sa)) { + /* Bad contact - we don't know how to reach this device. We need to ACK, but then send a bye */ + if (sip_route_empty(&pvt->route) && !req->ignore) { + ast_set_flag(&pvt->flags[0], SIP_PENDINGBYE); + } + } + } + + if (!req->ignore && pvt->owner) { + if (!reinvite && !res) { + ast_queue_control(pvt->owner, AST_CONTROL_ANSWER); + } else { /* RE-invite */ + if (pvt->t38.state == SIP_T38_DISABLED || pvt->t38.state == SIP_T38_REJECTED) { + ast_queue_control(pvt->owner, AST_CONTROL_UPDATE_RTP_PEER); + } else { + ast_queue_frame(pvt->owner, &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 (!req->ignore) { + ast_set_flag(&pvt->flags[0], SIP_PENDINGBYE); + } + } + + /* Check for Session-Timers related headers */ + if (sip_stimer_get_mode(pvt, FALSE) != SIP_STIMER_MODE_REFUSE) { + const char *expires = sip_request_get_header(req, "Session-Expires"); + + if (!ast_strlen_zero(expires)) { + /* UAS supports Session-Timers */ + int refresher; + int interval = 0; + + if (sip_parse_session_expires(expires, &interval, &refresher)) { + ast_set_flag(&pvt->flags[0], SIP_PENDINGBYE); + } else if (interval < sip_stimer_get_expiry(pvt, FALSE)) { + ast_log(LOG_WARNING, "Got Session-Expires less than local Min-SE in 200 OK, tearing down call\n"); + ast_set_flag(&pvt->flags[0], SIP_PENDINGBYE); + } + + pvt->stimer->refresher = refresher; + + if (interval) { + pvt->stimer->interval = interval; + } + + pvt->stimer->active = TRUE; + pvt->stimer->peer_active = TRUE; + + sip_stimer_start(pvt); + } else { + /* UAS doesn't support Session-Timers */ + if (sip_stimer_get_mode(pvt, FALSE) == SIP_STIMER_MODE_ORIGINATE) { + pvt->stimer->refresher = SIP_STIMER_REFRESHER_US; + pvt->stimer->peer_active = FALSE; + + sip_stimer_start(pvt); + } + } + } + + /* If I understand this right, the branch is different for a non-200 ACK only */ + pvt->invitestate = SIP_INVITE_TERMINATED; + ast_set_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED); + + send_res = sip_send_request(pvt, SIP_ACK, seqno, SIP_SEND_UNRELIABLE, TRUE); + sip_pvt_sched_check_pendings(pvt); + + if (ast_test_flag(&pvt->flags[2], SIP_RELAY_NEAREND) || ast_test_flag(&pvt->flags[2], SIP_RELAY_FAREND)) { + if (ast_test_flag(&pvt->flags[2], SIP_RELAY_NEAREND)) { + ast_clear_flag(&pvt->flags[2], SIP_RELAY_NEAREND); + sip_recording_start(pvt->join_callid, pvt->join_tag, pvt->join_theirtag, FALSE); + } else { + ast_clear_flag(&pvt->flags[2], SIP_RELAY_FAREND); + } + + ast_set_flag(&pvt->flags[2], SIP_SDP_ACK); + sip_send_invite(pvt, SIP_INVITE, FALSE, SIP_INIT_BRANCH, NULL); + } else if (ast_test_flag(&pvt->flags[2], SIP_SDP_ACK)) { + ast_clear_flag(&pvt->flags[2], SIP_SDP_ACK); + ast_set_flag(&pvt->flags[2], SIP_CISCO_RECORDING); + } + + break; + case 407: /* Proxy authentication */ + case 401: /* Www auth */ + /* First we ACK */ + send_res = sip_send_request(pvt, SIP_ACK, seqno, SIP_SEND_UNRELIABLE, FALSE); + + if (pvt->options) { + pvt->options->auth_type = resp; + } + + /* Then we AUTH */ + ast_string_field_set(pvt, theirtag, NULL); /* forget their old tag, so we don't match tags when getting response */ + + if (!req->ignore) { + if (pvt->authtries < SIP_MAX_AUTHTRIES) { + pvt->invitestate = SIP_INVITE_CALLING; + } + + if (pvt->authtries == SIP_MAX_AUTHTRIES || sip_pvt_handle_proxy_auth(pvt, req, resp, SIP_INVITE, SIP_INIT_BRANCH)) { + ast_log(LOG_NOTICE, "Failed to authenticate on INVITE to '%s'\n", sip_request_get_header(&pvt->initreq, "From")); + + sip_pvt_set_need_destroy(pvt, "failed to authenticate on INVITE"); + sip_pvt_set_already_gone(pvt); + + if (pvt->owner) { + ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION); + } + } + } + + break; + case 403: /* Forbidden */ + /* First we ACK */ + send_res = sip_send_request(pvt, SIP_ACK, seqno, SIP_SEND_UNRELIABLE, FALSE); + + if (!req->ignore && pvt->owner) { + sip_queue_hangup_cause(pvt, sip_hangup2cause(resp)); + } + + ast_log(LOG_WARNING, "Received response: \"Forbidden\" from '%s'\n", sip_request_get_header(&pvt->initreq, "From")); + break; + case 400: /* Bad Request */ + case 414: /* Bad request URI */ + case 493: /* Undecipherable */ + case 404: /* Not found */ + send_res = sip_send_request(pvt, SIP_ACK, seqno, SIP_SEND_UNRELIABLE, FALSE); + + if (pvt->owner && !req->ignore) { + sip_queue_hangup_cause(pvt, sip_hangup2cause(resp)); + } + + break; + case 481: /* Call leg does not exist */ + /* Could be REFER caused INVITE with replaces */ + send_res = sip_send_request(pvt, SIP_ACK, seqno, SIP_SEND_UNRELIABLE, FALSE); + + if (pvt->owner) { + ast_queue_hangup_with_cause(pvt->owner, sip_hangup2cause(resp)); + } + + ast_log(LOG_WARNING, "Re-invite to non-existing call leg on other UA. SIP dialog '%s'. Giving up.\n", pvt->callid); + break; + case 422: /* Session-Timers: Session interval too small */ + send_res = sip_send_request(pvt, SIP_ACK, seqno, SIP_SEND_UNRELIABLE, FALSE); + + ast_string_field_set(pvt, theirtag, NULL); + pvt->invitestate = SIP_INVITE_CALLING; + + min_se = sip_request_get_header(req, "Min-SE"); + + if (ast_strlen_zero(min_se) || sip_parse_min_se(min_se, &pvt->stimer->cached_min_se) == -1) { + ast_log(LOG_WARNING, "Parsing of Min-SE header failed %s\n", min_se); + pvt->stimer->cached_min_se = sip_config.min_se; + } + + if (pvt->stimer->interval < pvt->stimer->cached_min_se) { + pvt->stimer->interval = pvt->stimer->cached_min_se; + } + + sip_send_invite(pvt, SIP_INVITE, TRUE, SIP_INIT_REQUEST, NULL); + break; + case 428: /* Use identity header - rfc 4474 - not supported by Asterisk yet */ + send_res = sip_send_request(pvt, SIP_ACK, seqno, SIP_SEND_UNRELIABLE, FALSE); + + if (pvt->owner && !req->ignore) { + ast_queue_hangup_with_cause(pvt->owner, sip_hangup2cause(resp)); + } + + sip_history_append(pvt, "Identity", "SIP identity is required. Not supported by Asterisk."); + ast_log(LOG_WARNING, "SIP identity required by proxy. SIP dialog '%s'. Giving up.\n", pvt->callid); + + 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. */ + send_res = sip_send_request(pvt, SIP_ACK, seqno, SIP_SEND_UNRELIABLE, FALSE); + + if (pvt->owner && !req->ignore) { + struct ast_party_redirecting redirecting; + struct ast_set_party_redirecting update_redirecting; + int code; + char reason[64]; + + ast_party_redirecting_set_init(&redirecting, ast_channel_redirecting(pvt->owner)); + memset(&update_redirecting, 0, sizeof(update_redirecting)); + + if (sscanf(SIP_REQUEST_PART(req, rlpart2), "%30d %63s", &code, reason) != 2) { + reason[0] = '\0'; + } + + if ((redirecting.reason.code = ast_redirecting_reason_parse(reason)) < 0) { + char *quoted_reason; + + quoted_reason = ast_alloca(strlen(reason + 3)); + sprintf(quoted_reason, "\"%s\"", reason); /* Safe */ + + redirecting.reason.code = AST_REDIRECTING_REASON_UNKNOWN; + redirecting.reason.str = quoted_reason; + } else { + redirecting.reason.str = ""; + } + + ast_channel_queue_redirecting_update(pvt->owner, &redirecting, &update_redirecting); + ast_queue_control(pvt->owner, AST_CONTROL_BUSY); + } + + sip_history_append(pvt, "TempUnavailable", "Endpoint is temporarily unavailable."); + break; + case 487: /* Cancelled transaction */ + /* We have sent CANCEL on an outbound INVITE. This transaction is already scheduled to be killed by sip_hangup(). */ + send_res = sip_send_request(pvt, SIP_ACK, seqno, SIP_SEND_UNRELIABLE, FALSE); + + if (pvt->owner && !req->ignore) { + ast_queue_hangup_with_cause(pvt->owner, AST_CAUSE_NORMAL_CLEARING); + sip_history_append(pvt, "Hangup", "Got 487 on CANCEL request from us. Queued AST hangup request"); + } else if (!req->ignore) { + sip_pvt_update_call_counter(pvt, SIP_DEC_CALL_LIMIT); + sip_history_append(pvt, "Hangup", "Got 487 on CANCEL request from us on call without owner. Killing this dialog."); + } + + sip_pvt_sched_check_pendings(pvt); + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + break; + case 415: /* Unsupported media type */ + case 488: /* Not acceptable here */ + case 606: /* Not Acceptable */ + send_res = sip_send_request(pvt, SIP_ACK, seqno, SIP_SEND_UNRELIABLE, FALSE); + + if (pvt->udptl && pvt->t38.state == SIP_T38_LOCAL_REINVITE) { + sip_pvt_change_t38_state(pvt, SIP_T38_REJECTED); + /* Try to reset RTP timers. Trigger a reinvite back to audio */ + sip_send_reinvite_with_sdp(pvt, FALSE, FALSE); + } else { + /* We can't set up this call, so give up */ + if (pvt->owner && !req->ignore) { + ast_queue_hangup_with_cause(pvt->owner, sip_hangup2cause(resp)); + } + } + + break; + case 491: /* Pending */ + send_res = sip_send_request(pvt, SIP_ACK, seqno, SIP_SEND_UNRELIABLE, FALSE); + + if (pvt->owner && !req->ignore) { + if (ast_channel_state(pvt->owner) != AST_STATE_UP) { + ast_queue_hangup_with_cause(pvt->owner, sip_hangup2cause(resp)); + } else { + /* This is a re-invite that failed. Reset the flag after a while */ + int wait; + + /* RFC 3261, if owner of call, wait between 2.1 to 4 seconds, if not owner of call, wait 0 to 2 seconds */ + if (pvt->outgoing_call) { + wait = 2100 + ast_random() % 2000; + } else { + wait = ast_random() % 2000; + } + + ao2_t_bump(pvt, "Schedule waitid for sip_pvt_start_reinvite_retry."); + pvt->waitid = ast_sched_add(sip_sched_context, wait, sip_pvt_start_reinvite_retry, pvt); + + if (pvt->waitid < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(pvt, "Failed to schedule waitid"); + } + + ast_debug(2, "Reinvite race. Scheduled sip_pvt_start_reinvite_retry in %d secs in sip_handle_response_invite (waitid %d, dialog '%s')\n", + wait, pvt->waitid, pvt->callid); + } + } + + break; + case 408: /* Request timeout */ + case 405: /* Not allowed */ + case 501: /* Not implemented */ + send_res = sip_send_request(pvt, SIP_ACK, seqno, SIP_SEND_UNRELIABLE, FALSE); + + if (pvt->owner) { + ast_queue_hangup_with_cause(pvt->owner, sip_hangup2cause(resp)); + } + + break; + } + + if (send_res == -1) { + ast_log(LOG_WARNING, "Could not transmit message in dialog %s\n", pvt->callid); + } +} + +/* 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_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno) +{ + if (pvt->options) { + pvt->options->auth_type = (resp == 401 ? SIP_WWW_AUTH : SIP_PROXY_AUTH); + } + + if (pvt->authtries == SIP_MAX_AUTHTRIES || sip_pvt_handle_proxy_auth(pvt, req, resp, SIP_UPDATE, SIP_INIT_BRANCH)) { + ast_log(LOG_NOTICE, "Failed to authenticate on UPDATE to '%s'\n", sip_request_get_header(&pvt->initreq, "From")); + } +} + +/* Handle SIP response in SUBSCRIBE transaction */ +static void sip_handle_response_subscribe(struct sip_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno) +{ + if (pvt->subscribed != SIP_SUBSCRIBED_MESSAGE_SUMMARY) { + return; + } + + if (!pvt->mwi) { + return; + } + + switch (resp) { + case 200: /* Subscription accepted */ + ast_debug(3, "Got 200 OK on subscription for MWI\n"); + sip_pvt_set_allowed_methods(pvt, req); + + if (pvt->options) { + if (pvt->options->outboundproxy) { + ao2_ref(pvt->options->outboundproxy, -1); + } + + ast_free(pvt->options); + pvt->options = NULL; + } + + pvt->mwi->subscribed = TRUE; + sip_mwi_subscription_start(pvt->mwi, sip_config.mwi_expiry * 1000); + + break; + case 401: + case 407: + ast_string_field_set(pvt, theirtag, NULL); + + if (pvt->authtries > 1 || sip_pvt_handle_proxy_auth(pvt, req, resp, SIP_SUBSCRIBE, SIP_INIT_NONE)) { + ast_log(LOG_NOTICE, "Failed to authenticate on SUBSCRIBE to '%s'\n", sip_request_get_header(&pvt->initreq, "From")); + + pvt->mwi->pvt = NULL; + ao2_t_ref(pvt->mwi, -1, "failed to authenticate SUBSCRIBE"); + + sip_pvt_set_need_destroy(pvt, "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(pvt, "200 OK", req); + + pvt->mwi->pvt = NULL; + ao2_t_ref(pvt->mwi, -1, "received 403 response"); + + sip_pvt_set_already_gone(pvt); + sip_pvt_set_need_destroy(pvt, "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"); + + pvt->mwi->pvt = NULL; + ao2_t_ref(pvt->mwi, -1, "received 404 response"); + + sip_pvt_set_need_destroy(pvt, "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"); + + pvt->mwi->pvt = NULL; + ao2_t_ref(pvt->mwi, -1, "received 481 response"); + + sip_pvt_set_need_destroy(pvt, "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. The remote side may have suffered a heart attack.\n"); + + pvt->mwi->pvt = NULL; + ao2_t_ref(pvt->mwi, -1, "received 500/501 response"); + + sip_pvt_set_need_destroy(pvt, "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_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno) +{ + switch (resp) { + case 200: /* Notify accepted */ + /* They got the notify, this is the end */ + if (pvt->owner) { + if (pvt->refer) { + 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(pvt->owner)); + } + } else { + if (pvt->subscribed == SIP_SUBSCRIBED_NONE && !pvt->refer) { + ast_debug(4, "Got 200 accepted on NOTIFY %s\n", pvt->callid); + sip_pvt_set_need_destroy(pvt, "received 200 response"); + } + + if (ast_test_flag(&pvt->flags[1], SIP_STATECHANGEQUEUE)) { + struct sip_extension_state_data state_data = { + .device_state_info = pvt->last_device_state_info, + .exten_state = pvt->last_exten_state, + .presence_state = pvt->last_presence_state, + .presence_subtype = pvt->last_presence_subtype, + .presence_message = pvt->last_presence_message, + }; + + /* Ready to send the next state we have on queue */ + ast_clear_flag(&pvt->flags[1], SIP_STATECHANGEQUEUE); + sip_pvt_extension_state_update(pvt, &state_data, TRUE); + } + } + + break; + case 401: /* Not www-authorized on SIP method */ + case 407: /* Proxy auth */ + if (!pvt->notify) { + break; /* Only device notify can use NOTIFY auth */ + } + + ast_string_field_set(pvt, theirtag, NULL); + + if (ast_strlen_zero(pvt->authname)) { + ast_log(LOG_WARNING, "Asked to authenticate NOTIFY to %s but we have no matching peer or realm auth!\n", ast_sockaddr_stringify(&pvt->recv)); + sip_pvt_set_need_destroy(pvt, "unable to authenticate NOTIFY"); + } + + if (pvt->authtries > 1 || sip_pvt_handle_proxy_auth(pvt, req, resp, SIP_NOTIFY, SIP_INIT_NONE)) { + ast_log(LOG_NOTICE, "Failed to authenticate on NOTIFY to '%s'\n", sip_request_get_header(&pvt->initreq, "From")); + sip_pvt_set_need_destroy(pvt, "failed to authenticate NOTIFY"); + } + + break; + case 481: /* Call leg does not exist */ + sip_pvt_set_need_destroy(pvt, "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_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno) +{ + enum ast_control_transfer transfer = AST_TRANSFER_FAILED; + + /* If no refer structure exists, then do nothing */ + if (!pvt->refer) { + return; + } + + switch (resp) { + case 200: /* Out of Dialog REFER */ + case 202: /* Transfer accepted */ + /* We need to do something here */ + /* The transferee is now sending INVITE to target */ + pvt->refer->status = SIP_REFER_ACCEPTED; + /* Now wait for next transfer */ + ast_debug(3, "Got 202 accepted on transfer\n"); + + /* We should hang along, waiting for NOTIFY's here */ + break; + case 401: /* Not www-authorized on SIP method */ + case 407: /* Proxy auth */ + if (ast_strlen_zero(pvt->authname)) { + ast_log(LOG_WARNING, "Asked to authenticate REFER to %s but we have no matching peer or realm auth!\n", + ast_sockaddr_stringify(&pvt->recv)); + + if (pvt->owner) { + ast_queue_control_data(pvt->owner, AST_CONTROL_TRANSFER, &transfer, sizeof(transfer)); + } + + sip_pvt_set_need_destroy(pvt, "unable to authenticate REFER"); + } + + if (pvt->authtries > 1 || sip_pvt_handle_proxy_auth(pvt, req, resp, SIP_REFER, SIP_INIT_NONE)) { + ast_log(LOG_NOTICE, "Failed to authenticate on REFER to '%s'\n", sip_request_get_header(&pvt->initreq, "From")); + pvt->refer->status = SIP_REFER_NOAUTH; + + if (pvt->owner) { + ast_queue_control_data(pvt->owner, AST_CONTROL_TRANSFER, &transfer, sizeof(transfer)); + } + + sip_pvt_set_need_destroy(pvt, "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", pvt->refer->refer_to); + + sip_pvt_set_need_destroy(pvt, "received 405 response"); + pvt->refer->status = SIP_REFER_FAILED; + + if (pvt->owner) { + ast_queue_control_data(pvt->owner, 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 to call '%s'. Giving up.\n", pvt->callid); + + if (pvt->owner) { + ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION); + } + + sip_pvt_set_need_destroy(pvt, "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, "SIP transfer to %s failed, call miserably fails. \n", pvt->refer->refer_to); + + sip_pvt_set_need_destroy(pvt, "received 500/501 response"); + pvt->refer->status = SIP_REFER_FAILED; + + if (pvt->owner) { + ast_queue_control_data(pvt->owner, AST_CONTROL_TRANSFER, &transfer, sizeof(transfer)); + } + + break; + case 603: /* Transfer declined */ + ast_log(LOG_NOTICE, "SIP transfer to %s declined, call miserably fails. \n", pvt->refer->refer_to); + + pvt->refer->status = SIP_REFER_FAILED; + sip_pvt_set_need_destroy(pvt, "received 603 response"); + + if (pvt->owner) { + ast_queue_control_data(pvt->owner, 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 (resp < 299) { /* 1xx cases don't get here */ + ast_log(LOG_WARNING, "SIP transfer to %s had unexpected 2xx response (%d), confusion is possible. \n", pvt->refer->refer_to, resp); + } else { + ast_log(LOG_WARNING, "SIP transfer to %s with response (%d). \n", pvt->refer->refer_to, resp); + } + + pvt->refer->status = SIP_REFER_FAILED; + sip_pvt_set_need_destroy(pvt, "received failure response"); + + if (pvt->owner) { + ast_queue_control_data(pvt->owner, AST_CONTROL_TRANSFER, &transfer, sizeof(transfer)); + } + + break; + } +} + +/* Handle responses on REGISTER to services */ +static void sip_handle_response_register(struct sip_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno) +{ + int expires, expires_ms; + struct sip_registry *registry = pvt->registry; + + switch (resp) { + case 401: /* Unauthorized */ + if (pvt->authtries == SIP_MAX_AUTHTRIES || sip_pvt_handle_register_auth(pvt, req, resp)) { + ast_log(LOG_NOTICE, "Failed to authenticate on REGISTER to '%s@%s' (Tries %d)\n", + pvt->registry->username, pvt->registry->hostname, pvt->authtries); + sip_pvt_set_need_destroy(pvt, "failed to authenticate REGISTER"); + } + + break; + case 403: /* Forbidden */ + if (sip_config.reg_retry_403) { + ast_log(LOG_NOTICE, "Treating 403 response to REGISTER as non-fatal for %s@%s\n", + pvt->registry->username, pvt->registry->hostname); + ast_string_field_set(registry, nonce, ""); + ast_string_field_set(pvt, nonce, ""); + + break; + } + + ast_log(LOG_WARNING, "Forbidden - wrong password on authentication for REGISTER for '%s' to '%s'\n", + pvt->registry->username, pvt->registry->hostname); + + registry->state = SIP_REGISTRY_NOAUTH; + sip_registry_stop_timeout(registry); + + ast_system_publish_registry("SIP", registry->username, registry->hostname, sip_registry_state2str(registry->state), NULL); + sip_pvt_set_need_destroy(pvt, "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", pvt->registry->username, pvt->registry->hostname); + + if (registry->pvt) { + ao2_t_cleanup(registry->pvt, "unsetting registry->call pointer-- case 404"); + registry->pvt = NULL; + } + + 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_pvt_set_need_destroy(pvt, "received 404 response"); + + break; + case 407: /* Proxy auth */ + if (pvt->authtries == SIP_MAX_AUTHTRIES || sip_pvt_handle_register_auth(pvt, req, resp)) { + ast_log(LOG_NOTICE, "Failed to authenticate on REGISTER to '%s' (tries '%d')\n", + sip_request_get_header(&pvt->initreq, "From"), pvt->authtries); + sip_pvt_set_need_destroy(pvt, "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 call %s after we had destroyed the registry object\n", pvt->callid); + } + + break; + case 423: /* Interval too brief */ + ast_log(LOG_WARNING, "Got 423 Interval too brief for service %s@%s, minimum is %d seconds\n", + pvt->registry->username, pvt->registry->hostname, registry->expiry); + registry->expiry = atoi(sip_request_get_header(req, "Min-Expires")); + + if (registry->pvt) { + ao2_t_cleanup(registry->pvt, "unsetting registry->call pointer-- case 423"); + registry->pvt = NULL; + + sip_pvt_set_need_destroy(pvt, "received 423 response"); + } + + if (registry->expiry > sip_config.max_expiry) { + ast_log(LOG_WARNING, "Required expiration time from %s@%s is too high, giving up\n", pvt->registry->username, pvt->registry->hostname); + + registry->expiry = registry->configured_expiry; + registry->state = SIP_REGISTRY_REJECTED; + + sip_registry_stop_timeout(registry); + } else { + registry->state = SIP_REGISTRY_UNREGISTERED; + sip_send_register(registry, SIP_REGISTER, NULL, NULL); + } + + ast_system_publish_registry("SIP", registry->username, registry->hostname, sip_registry_state2str(registry->state), NULL); + 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", resp, pvt->registry->username, pvt->registry->hostname); + + if (registry->pvt) { + ao2_t_cleanup(registry->pvt, "unsetting registry->call pointer-- case 4xx"); + registry->pvt = NULL; + } + + 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_pvt_set_need_destroy(pvt, "received 4xx response"); + + 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(pvt->peername, pvt->username)); + sip_pvt_set_need_destroy(pvt, "received erroneous 200 response"); + + return; + } + + ast_debug(1, "Registration successful\n"); + + if (registry->timeout > -1) { + ast_debug(1, "Cancelling timeout %d\n", registry->timeout); + } + + 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_pvt_set_need_destroy(pvt, "Registration successfull"); + + if (registry->pvt) { + ao2_t_cleanup(registry->pvt, "unsetting registry->call pointer-- case 200"); + registry->pvt = NULL; + } + + ao2_t_replace(pvt->registry, NULL, "unref registry entry pvt->registry"); + + /* set us up for re-registering figure out how long we got registered for according to section 6.13 of RFC, contact headers + * override expires headers, so check those first */ + expires = 0; + + /* XXX todo: try to save the extra call */ + if (!ast_strlen_zero(sip_request_get_header(req, "Contact"))) { + const char *contact = NULL; + const char *tag, *our_contact; + int start = 0; + + for (;;) { + contact = sip_request_get_header_full(req, "Contact", &start); + + /* this loop ensures we get a contact header about our register request */ + if (!ast_strlen_zero(contact)) { + if ((our_contact = strstr(contact, pvt->our_contact))) { + contact = our_contact; + break; + } + } else { + break; + } + } + + if ((tag = strcasestr(contact, "expires="))) { + if (sscanf(tag + 8, "%30d", &expires) != 1) { + expires = 0; + } + } + } + + if (!expires) { + if (!(expires = atoi(sip_request_get_header(req, "Expires")))) { + expires = sip_config.default_expiry; + } + } + + expires_ms = expires * 1000; + + if (expires <= SIP_EXPIRY_GUARD_LIMIT) { + expires_ms -= MAX((expires_ms * SIP_EXPIRY_GUARD_PERCENT), SIP_EXPIRY_GUARD_MIN); + } else { + expires_ms -= SIP_EXPIRY_GUARD_SECS * 1000; + } + + if (sip_debug) { + ast_log(LOG_NOTICE, "Outbound Registration: Expiry for %s is %d sec (Scheduling re-registration in %ds)\n", + registry->hostname, expires, expires_ms/1000); + } + + registry->refresh = (int) expires_ms / 1000; + + /* Schedule re-registration before we expire */ + sip_registry_sched_send(registry, expires_ms); + } +} + +/* Handle qualification responses (OPTIONS) */ +static void sip_handle_response_options(struct sip_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno) +{ + struct sip_peer *peer = pvt->relatedpeer; /* hope this is already refcounted! */ + int state_changed, is_reachable, was_reachable; + const char *status; + int old_lastms; + char lastms[20]; + struct sip_alias *alias; + + old_lastms = peer->lastms; + peer->lastms = ast_tvdiff_ms(ast_tvnow(), peer->qualifystart); + + /* Compute the response time to a ping (goes in peer->lastms.) -1 means did not respond, 0 means unknown, 1..maxms is a valid + * response, >maxms means late response. */ + if (peer->lastms < 1) { /* zero = unknown, so round up to 1 */ + peer->lastms = 1; + } + + if (!peer->maxms) { /* this should never happens */ + sip_pvt_set_need_destroy(pvt, "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_lastms > 0 && old_lastms <= peer->maxms; + is_reachable = peer->lastms <= peer->maxms; + + state_changed = old_lastms == 0 /* yes, unknown before */ || was_reachable != is_reachable; + status = is_reachable ? "Reachable" : "Lagged"; + + snprintf(lastms, sizeof(lastms), "%d", peer->lastms); + + ao2_t_cleanup(peer->qualifypvt, "unref dialog peer->qualifypvt"); + peer->qualifypvt = NULL; + + if (state_changed) { + ast_verb(3, "Peer '%s' is now %s. (%dms / %dms)\n", peer->name, status, peer->lastms, peer->maxms); + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + + if (sip_config.peer_rtupdate) { + ast_update_realtime(ast_check_realtime("sipregs") ? "sipregs" : "sippeers", "name", peer->name, "lastms", lastms, 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", status, "time", peer->lastms); + ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); + } + + if (is_reachable && sip_config.regextenonqualify) { + sip_peer_register_exten(peer, TRUE); + } + } + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (!alias->peer) { + continue; + } + + alias->peer->lastms = peer->lastms; + + if (state_changed) { + ast_verb(3, "Peer '%s' is now %s. (%dms / %dms)\n", alias->peer->name, status, alias->peer->lastms, alias->peer->maxms); + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); + + if (sip_config.peer_rtupdate) { + ast_update_realtime("sippeers", "name", alias->peer->name, "lastms", lastms, SENTINEL); + } + + if (alias->peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + + ast_endpoint_set_state(alias->peer->endpoint, AST_ENDPOINT_ONLINE); + + blob = ast_json_pack("{s: s, s: i}", "peer_status", status, "time", peer->lastms); + ast_endpoint_blob_publish(alias->peer->endpoint, ast_endpoint_state_type(), blob); + } + + if (is_reachable && sip_config.regextenonqualify) { + sip_peer_register_exten(alias->peer, TRUE); + } + } + } + + sip_pvt_set_need_destroy(pvt, "got OPTIONS response"); + + /* Try again eventually */ + AST_SCHED_REPLACE_UNREF(peer->qualifyexpire, sip_sched_context, + is_reachable ? peer->qualifyfreq : SIP_QUALIFY_FREQ_NOTOK, __sip_peer_qualify, peer, + ao2_t_cleanup(_data, "removing qualify peer ref"), + ao2_t_cleanup(peer, "removing qualify peer ref"), + ao2_t_bump(peer, "adding qualify peer ref")); +} + +/* 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_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno) +{ + switch (resp) { + 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(&pvt->sa), resp, sip_methods[SIP_INFO].name); + break; + case 405: /* Method not allowed */ + case 501: /* Not Implemented */ + sip_method_set_disallowed(&pvt->allowed_methods, SIP_INFO); + + if (pvt->relatedpeer) { + sip_method_set_allowed(&pvt->relatedpeer->disallowed_methods, SIP_INFO); + } + + ast_log(LOG_WARNING, "Host '%s' does not implement '%s'\n", ast_sockaddr_stringify(&pvt->sa), sip_methods[SIP_INFO].name); + break; + default: + if (resp >= 300 && resp < 700) { + ast_verb(3, "Got SIP %s response %d \"%s\" back from host '%s'\n", + sip_methods[SIP_INFO].name, resp, SIP_REQUEST_PART(req, rlpart2), ast_sockaddr_stringify(&pvt->sa)); + } + + 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_pvt *pvt, int resp, struct sip_request *req, uint32_t seqno) +{ + switch (resp) { + case 401: /* Not www-authorized on SIP method */ + case 407: /* Proxy auth required */ + if (sip_pvt_check_message_auth(pvt, resp, req) && !ast_test_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED)) { + sip_pvt_set_need_destroy(pvt, "MESSAGE authentication failed"); + } + + break; + case 405: /* Method not allowed */ + case 501: /* Not Implemented */ + sip_method_set_disallowed(&pvt->allowed_methods, SIP_MESSAGE); + + if (pvt->relatedpeer) { + sip_method_set_allowed(&pvt->relatedpeer->disallowed_methods, SIP_MESSAGE); + } + + ast_log(LOG_WARNING, "Host '%s' does not implement '%s'\n", + ast_sockaddr_stringify(&pvt->sa), sip_methods[SIP_MESSAGE].name); + + if (!ast_test_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED)) { + sip_pvt_set_need_destroy(pvt, "MESSAGE not implemented or allowed"); + } + + break; + default: + if (resp >= 100 && resp < 200) { + /* Must allow provisional responses for out-of-dialog requests. */ + } else if (resp >= 200 && resp < 300) { + pvt->authtries = 0; /* Reset authentication counter */ + + if (!ast_test_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED)) { + sip_pvt_set_need_destroy(pvt, "MESSAGE delivery accepted"); + } + } else if (resp >= 300 && resp < 700) { + ast_verb(3, "Got SIP %s response %d \"%s\" back from host '%s'\n", + sip_methods[SIP_MESSAGE].name, resp, SIP_REQUEST_PART(req, rlpart2), ast_sockaddr_stringify(&pvt->sa)); + + if (!ast_test_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED)) { + sip_pvt_set_need_destroy(pvt, resp >= 300 && resp < 600 ? "MESSAGE delivery failed" : "MESSAGE delivery refused"); + } + } + + break; + } +} diff -durN asterisk-22.2.0.orig/channels/sip/include/auth.h asterisk-22.2.0/channels/sip/include/auth.h --- asterisk-22.2.0.orig/channels/sip/include/auth.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/auth.h 2025-02-18 17:14:46.800902858 +1300 @@ -0,0 +1,51 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_AUTH_H +#define _SIP_AUTH_H + +/* Credentials for authentication to other SIP services */ +struct sip_auth { + AST_LIST_ENTRY(sip_auth) 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. */ +struct sip_auth_head { + AST_LIST_HEAD_NOLOCK(, sip_auth) list; +}; + +extern struct sip_auth_head *sip_auths; +extern ast_mutex_t sip_auth_lock; + +void sip_auth_build(struct sip_auth_head **auths, const char *config, int lineno); +void sip_auth_destroy(void *data); +void sip_auth_destroy_all(void); +struct sip_auth *sip_auth_find(struct sip_auth_head *auths, const char *realm); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/callback.h asterisk-22.2.0/channels/sip/include/callback.h --- asterisk-22.2.0.orig/channels/sip/include/callback.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/callback.h 2025-02-18 17:14:46.801902831 +1300 @@ -0,0 +1,41 @@ +/* + * 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_pvt; +struct sip_remotecc_data; + +struct sip_callback { + int stateid; + char *exten; + int busy:1; +}; + +void sip_callback_destroy(struct sip_peer *peer); +int sip_remotecc_callback(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/chan_tech.h asterisk-22.2.0/channels/sip/include/chan_tech.h --- asterisk-22.2.0.orig/channels/sip/include/chan_tech.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/chan_tech.h 2025-02-18 17:14:46.802902804 +1300 @@ -0,0 +1,56 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_CHAN_TECH_H +#define _SIP_CHAN_TECH_H + +/* Forward declarations */ +struct sip_pvt; + +extern struct ast_channel_tech sip_tech; +extern struct ast_sip_api_tech chan_sip_api_tech; + +struct ast_channel *sip_requester(const char *type, struct ast_format_cap *cap, + const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *dest, 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 *chan, const char *text); +int sip_call(struct ast_channel *chan, const char *dest, int timeout); +int sip_send_html(struct ast_channel *chan, int subclass, const char *data, int datalen); +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 *chan, struct ast_frame *frame); +int sip_indicate(struct ast_channel *chan, int condition, const void *data, size_t datalen); +int sip_transfer(struct ast_channel *chan, const char *dest); +int sip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); +int sip_send_digit_begin(struct ast_channel *chan, char digit); +int sip_send_digit_end(struct ast_channel *chan, char digit, unsigned int duration); +int sip_setoption(struct ast_channel *chan, int option, void *data, int datalen); +int sip_queryoption(struct ast_channel *chan, int option, void *data, int *datalen); +const char *sip_get_pvt_uniqueid(struct ast_channel *chan); + +int sip_sipinfo_send(struct ast_channel *chan, struct ast_variable *headers, const char *content_type, + const char *content, const char *useragent_filter); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/cli.h asterisk-22.2.0/channels/sip/include/cli.h --- asterisk-22.2.0.orig/channels/sip/include/cli.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/cli.h 2025-02-18 17:14:46.802902804 +1300 @@ -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_donotdisturb(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_callforward(struct ast_cli_entry *entry, int cmd, struct ast_cli_args *args); +char *sip_cli_huntgroup(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_channelstats(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.2.0.orig/channels/sip/include/conference.h asterisk-22.2.0/channels/sip/include/conference.h --- asterisk-22.2.0.orig/channels/sip/include/conference.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/conference.h 2025-02-18 17:14:46.802902804 +1300 @@ -0,0 +1,79 @@ +/* + * 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_pvt; +struct sip_request; +struct sip_remotecc_data; + +struct sip_conference { + AST_LIST_ENTRY(sip_conference) next; + int confid; + int next_callid; + struct ast_bridge *bridge; + int keep:1; + int multiadmin: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 callid; + struct sip_conference *conference; + struct ast_channel *chan; + int administrator:1; + int removed:1; + int muted:1; + int talking:1; +}; + +struct sip_selected { + AST_LIST_ENTRY(sip_selected) next; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(callid); + AST_STRING_FIELD(tag); + AST_STRING_FIELD(theirtag); + ); +}; + +extern struct sip_conferences_head sip_conferences; + +int sip_remotecc_conference(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data); +int sip_remotecc_conflist(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data); +int sip_remotecc_rmlastconf(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data); +int sip_remotecc_join(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data); +int sip_remotecc_select(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data); +int sip_remotecc_unselect(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data); + +void sip_selected_destroy(struct sip_selected *selected); +void sip_selected_destroy_all(struct sip_peer *peer); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/config.h asterisk-22.2.0/channels/sip/include/config.h --- asterisk-22.2.0.orig/channels/sip/include/config.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/config.h 2025-02-18 17:14:46.803902778 +1300 @@ -0,0 +1,170 @@ +/* + * 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_NOTIFYCID_DISABLED, + SIP_NOTIFYCID_ENABLED, + SIP_NOTIFYCID_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[3]; /* Global SIP_ flags */ + int ignore_regexpire; /* G: Ignore expiration of peer */ + int directrtpsetup; /* Enable support for Direct RTP setup (no re-invites) */ + int pedanticsipchecking; /* Extra checking ? Default off */ + int srvlookup; /* SRV Lookup on or off. Default is on */ + int alwaysauthreject; /* Send 401 Unauthorized for all failing requests */ + int auth_options_requests; /* Authenticate OPTIONS requests */ + int auth_message_requests; /* Authenticate MESSAGE requests */ + int accept_outofcall_message; /* Accept MESSAGE outside of a call */ + int allow_external_domains; /* Accept calls to external SIP domains? */ + int regextenonqualify; /* Whether to add/remove regexten when qualifying peers */ + int send_diversion; /* Whether to Send SIP Diversion headers */ + int matchexternaddrlocally; /* Match externaddr/externhost setting against localnet setting */ + char regcontext[AST_MAX_CONTEXT]; /* Context for auto-extensions */ + char messagecontext[AST_MAX_CONTEXT]; /* Default context for out of dialog msgs. */ + unsigned int disallowed_methods; /* methods that we should never try to use */ + int notifyringing; /* Send notifications on ringing */ + int notifyhold; /* Send notifications on hold */ + int notifycid; /* Send CID with ringing notifications */ + int allowtransfer; /* SIP Refer restriction scheme */ + int allowsubscribe; /* Flag for disabling ALL subscriptions, this is FALSE only if all peers are FALSE the global setting is in flags[1] */ + char realm[MAXHOSTNAMELEN]; /* Default realm */ + int domainsasrealm; /* Use domains lists as realms */ + struct sip_proxy outboundproxy; /* Outbound proxy */ + char context[AST_MAX_CONTEXT]; + char subscribecontext[AST_MAX_CONTEXT]; + char language[MAX_LANGUAGE]; /* Default language setting for new channels */ + char callerid[AST_MAX_EXTENSION]; /* Default caller ID for sip messages */ + char mwi_from[80]; /* Default caller ID for MWI updates */ + char fromdomain[AST_MAX_EXTENSION]; /* Default domain on outbound messages */ + int fromdomainport; /* Default domain port on outbound messages */ + char vmexten[AST_MAX_EXTENSION]; /* Default From Username on MWI updates */ + int keepalive; /* Default keepalive= setting */ + char mohinterpret[MAX_MUSICCLASS]; /* Global setting for moh class to use when put on hold */ + char mohsuggest[MAX_MUSICCLASS]; /* Global setting for moh class to suggest when putting a bridged channel on hold */ + char parkinglot[AST_MAX_CONTEXT]; /* Parkinglot */ + char engine[256]; /* Default RTP engine */ + int maxcallbitrate; /* 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 */ + int match_auth_username; /* Match auth username if available instead of From: Default off. */ + int relaxdtmf; /* Relax DTMF */ + int prematuremediafilter; /* Enable/disable premature frames in a call (causing 183 early media) */ + unsigned int recordhistory; /* Record SIP history. Off by default */ + unsigned int dumphistory; /* Dump history to verbose before destroying SIP dialog */ + int tcpauthlimit; /* TCP auth limit */ + int tcpauthtimeout; /* TCP auth timeout */ + struct ast_acl_list *contact_acl; /* Global list of addresses dynamic peers are not allowed to use */ + struct ast_format_cap *caps; /* Supported codecs */ + int tcp_enabled; + int max_forwards; /* Default max forwards (SIP Anti-loop) */ + int rtptimeout; /* Time out call if no RTP */ + int rtpholdtimeout; /* Time out call if no RTP during hold */ + int rtpkeepalive; /* Send RTP keepalives */ + int stimer_mode; /* Mode of operation for Session-Timers */ + int stimer_refresher; /* Session-Timer refresher */ + int min_se; /* Lowest threshold for session refresh interval */ + int max_se; /* Highest threshold for session refresh interval */ + int dynamic_exclude_static; /* Exclude static peers from contact registrations */ + int qualify_maxms; /* 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 t1min; /* T1 roundtrip time minimum */ + int timer_b; /* Timer B - RFC 3261 Section 17.1.1.2 */ + unsigned int autoframing; /* Turn autoframing on or off. */ + char useragent[AST_MAX_EXTENSION]; /* Useragent for the SIP channel */ + char sdpsession[AST_MAX_EXTENSION]; /* SDP session name for the SIP channel */ + char sdpowner[AST_MAX_EXTENSION]; /* SDP owner name for the SIP channel */ + int authfailureevents; /* Whether we send authentication failure manager events or not. Default no. */ + unsigned int t38_maxdatagram; /* Global T.38 FaxMaxDatagram override */ + int reg_timeout; /* Global time between attempts for outbound registrations */ + int regattempts_max; /* Registration attempts before giving up */ + int reg_retry_403; /* Treat 403 responses to registrations as 401 responses */ + int shrinkcallerid; /* enable or disable shrinking of caller id */ + int callcounter; /* 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 bindaddr; /* UDP: The address we bind to */ + struct ast_sockaddr rtpbindaddr; /* RTP: The address we bind to */ + struct ast_sockaddr externaddr; /* External IP address if we are behind NAT */ + char externhost[MAXHOSTNAMELEN]; /* External host name */ + time_t externexpire; /* Expiration counter for re-resolving external host name in dynamic DNS */ + int externrefresh; /* Refresh timer for DNS-based external address (dyndns) */ + uint16_t externtcpport; /* External tcp port */ + uint16_t externtlsport; /* External tls port */ + struct ast_sockaddr mediaaddr; /* External RTP IP address if we are behind NAT */ + struct ast_ha *localaddr; /* 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; + int min_subexpiry; /* Minimum accepted subscription time */ + int max_subexpiry; /* Maximum accepted subscription time */ + int mwi_expiry; + unsigned char refer_addheaders; /* Add extra headers to outgoing REFER */ + int peer_rtupdate; /* G: Update database with registration data for peer? */ + int rtsave_sysname; /* G: Save system name at registration? */ + int rtsave_path; /* G: Save path header on registration */ + int rtautoclear; /* Realtime ?? */ +}; + +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 char sip_used_context[AST_MAX_CONTEXT]; + +extern struct ast_sockaddr sip_internip; + +void sip_config_reload(void); + +int sip_config_parse(enum channelreloadreason reason); +int sip_config_parse_common(struct ast_flags *flags, struct ast_flags *mask, struct ast_variable *var); +int sip_config_parse_t38(struct ast_flags *flags, struct ast_flags *mask, struct ast_variable *var, unsigned int *maxdatagram); +void sip_config_parse_insecure(struct ast_flags *flags, const char *value, int lineno); + +void sip_startup_event(void *data, struct stasis_subscription *sub, struct stasis_message *msg); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/dialplan_apps.h asterisk-22.2.0/channels/sip/include/dialplan_apps.h --- asterisk-22.2.0.orig/channels/sip/include/dialplan_apps.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/dialplan_apps.h 2025-02-18 17:14:46.803902778 +1300 @@ -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 *chan, const char *data); +int sip_app_addheader(struct ast_channel *chan, const char *data); +int sip_app_removeheader(struct ast_channel *chan, const char *data); +int sip_app_ciscopage(struct ast_channel *chan, const char *data); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/dialplan_funcs.h asterisk-22.2.0/channels/sip/include/dialplan_funcs.h --- asterisk-22.2.0.orig/channels/sip/include/dialplan_funcs.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/dialplan_funcs.h 2025-02-18 17:14:46.804902751 +1300 @@ -0,0 +1,39 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_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 *chan, const char *function, char *data, char *buf, size_t buflen); +int sip_func_checkdomain_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len); +int sip_func_header_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len); +int sip_func_headers_read(struct ast_channel *chan, const char *function, char *data, struct ast_str **buf, ssize_t maxlen); +int sip_func_peer_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len); +int sip_func_peer_write(struct ast_channel *chan, const char *function, char *data, const char *value); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/domain.h asterisk-22.2.0/channels/sip/include/domain.h --- asterisk-22.2.0.orig/channels/sip/include/domain.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/domain.h 2025-02-18 17:14:46.804902751 +1300 @@ -0,0 +1,49 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_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 mode, const char *context); +void sip_domain_destroy_all(void); +int sip_domain_check(const char *name, char *context, size_t len); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/events.h asterisk-22.2.0/channels/sip/include/events.h --- asterisk-22.2.0.orig/channels/sip/include/events.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/events.h 2025-02-18 17:14:46.804902751 +1300 @@ -0,0 +1,32 @@ +/* + * 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_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.2.0.orig/channels/sip/include/handlers.h asterisk-22.2.0/channels/sip/include/handlers.h --- asterisk-22.2.0.orig/channels/sip/include/handlers.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/handlers.h 2025-02-18 17:14:46.805902724 +1300 @@ -0,0 +1,61 @@ +/* + * 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_CONFLIST = 1, + SIP_REMOTECC_CALLBACK = 2 +}; + +/* Forward declarations */ +struct sip_request; + +/* Information about a remotecc request */ +struct sip_remotecc_data { + char *softkeyevent; + struct { + char *callid; + char *localtag; + char *remotetag; + } dialogid; + struct { + char *callid; + char *localtag; + char *remotetag; + } consultdialogid; + struct { + char *callid; + char *localtag; + char *remotetag; + } joindialogid; + int applicationid; + int confid; + char *usercalldata; +}; + +int sip_handle_incoming(struct sip_request *req, struct ast_sockaddr *addr); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/manager.h asterisk-22.2.0/channels/sip/include/manager.h --- asterisk-22.2.0.orig/channels/sip/include/manager.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/manager.h 2025-02-18 17:14:46.805902724 +1300 @@ -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 *chan, const char *source); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/message.h asterisk-22.2.0/channels/sip/include/message.h --- asterisk-22.2.0.orig/channels/sip/include/message.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/message.h 2025-02-18 17:14:46.805902724 +1300 @@ -0,0 +1,43 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_MESSAGE_H +#define _SIP_MESSAGE_H + +/* Forward declarations */ +struct sip_pvt; + +/* Additional headers to send with MESSAGE method packet. */ +struct sip_msg_header { + AST_LIST_ENTRY(sip_msg_header) next; + const char *name; /* Name of header to stick in MESSAGE */ + const char *value; /* Value of header to stick in MESSAGE */ + char data[0]; /* The name and value strings are stuffed here in that order. */ +}; + +extern const struct ast_msg_tech sip_msg_tech; + +int sip_msg_send(const struct ast_msg *msg, const char *to, const char *from); +void sip_msg_free_headers(struct sip_pvt *pvt); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/mwi_subscriptions.h asterisk-22.2.0/channels/sip/include/mwi_subscriptions.h --- asterisk-22.2.0.orig/channels/sip/include/mwi_subscriptions.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/mwi_subscriptions.h 2025-02-18 17:14:46.806902698 +1300 @@ -0,0 +1,60 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_MWI_SUBSCRIPTIONS_H +#define _SIP_MWI_SUBSCRIPTIONS_H + +/* Forward declarations */ +struct sip_pvt; + +struct sip_mwi_subscription { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(username); /* Who we are sending the subscription as */ + AST_STRING_FIELD(authuser); /* 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 resub; /* Sched ID of resubscription */ + unsigned int subscribed:1; /* Whether we are currently subscribed or not */ + struct sip_pvt *pvt; /* Outbound subscription dialog */ + struct ast_dnsmgr_entry *dnsmgr; /* DNS refresh manager for subscription */ + struct ast_sockaddr us; /* Who the server thinks we are */ +}; + +/* The MWI subscription list */ +extern struct ao2_container *sip_mwi_subscriptions; + +int sip_mwi_subscription_build(const char *value, int lineno); +void sip_mwi_subscription_destroy(void *data); +void sip_mwi_subscription_destroy_all(void); +void sip_mwi_subscription_dnsmgr_lookup(struct ast_sockaddr *oldaddr, struct ast_sockaddr *newaddr, void *data); +int sip_mwi_subscription_send(const void *data); +void sip_mwi_subscription_stop(struct sip_mwi_subscription *mwi); +void sip_mwi_subscription_start(struct sip_mwi_subscription *mwi, int ms); +void sip_mwi_subscription_send_all(void); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/netsock.h asterisk-22.2.0/channels/sip/include/netsock.h --- asterisk-22.2.0.orig/channels/sip/include/netsock.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/netsock.h 2025-02-18 17:14:46.806902698 +1300 @@ -0,0 +1,109 @@ +/* + * 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_pvt; + +/* The SIP socket definition */ +struct sip_socket { + enum ast_transport type; /* UDP, TCP or TLS */ + int fd; /* Filed descriptor, the actual socket */ + uint16_t unused; /* since 1.6.2, retained not to change order/size of struct */ + struct ast_tcptls_session_instance *tcptls_session; /* If tcp or tls, a socket manager */ +}; + +/* 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_pvt they belong to. Each packet holds a reference to the parent struct sip_pvt. 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 retrans; /* Retransmission number */ + int method; /* SIP method for this packet */ + uint32_t seqno; /* Sequence number */ + char is_resp; /* 1 if this is a response packet (e.g. 200 OK), 0 if it is a request */ + char is_fatal; /* non-zero if there is a fatal error */ + int response_code; /* If this is a response, the response code */ + struct sip_pvt *owner; /* Owner AST call */ + int retransid; /* Retransmission ID */ + int timer_a; /* SIP timer A, retransmission timer */ + int timer_t1; /* SIP Timer T1, estimated RTT or 500 ms */ + struct timeval time_sent; /* When pkt was sent */ + int64_t retrans_stop_time; /* Time in ms after 'now' that retransmission must stop */ + int retrans_stop; /* Timeout is reached, stop retransmission */ + struct ast_str *data; +}; + +/* Definition of a thread that handles a socket */ +struct sip_tcptls_thread { + int stop; /* TRUE if the thread needs to kill itself. (The module is being unloaded.) */ + 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; + enum ast_transport type; /* We keep a copy of the type here so we can display it in the connection list */ + AST_LIST_HEAD_NOLOCK(, sip_tcptls_packet) packet_queue; +}; + +struct sip_tcptls_packet { + AST_LIST_ENTRY(sip_tcptls_packet) next; + struct ast_str *data; + size_t len; +}; + +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_data(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_thread_cmp_addr(void *data, void *arg, int flags); +struct sip_tcptls_thread *sip_tcptls_thread_alloc(struct ast_tcptls_session_instance *tcptls_session, int transport); + +void *sip_tcptls_session_thread(void *data); +void sip_tcptls_session_args_free(void *obj); +struct ast_tcptls_session_instance *sip_tcptls_session_find(struct ast_sockaddr *addr); +int sip_tcptls_session_read(struct sip_request *req, struct ast_tcptls_session_instance *tcptls_session, int authenticated, time_t start); +int sip_tcptls_session_write(struct ast_tcptls_session_instance *tcptls_session, const void *buf, size_t len); + +int sip_packet_send(struct sip_pvt *pvt, struct ast_str *data); +int sip_packet_send_reliable(struct sip_pvt *pvt, uint32_t seqno, int is_resp, struct ast_str *data, int is_fatal, int method); +int sip_packet_resend(const void *data); +void sip_packet_cancel_resend(struct sip_packet *packet); +int sip_packet_semi_ack(struct sip_pvt *pvt, uint32_t seqno, int resp, int sipmethod); +void sip_packet_pretend_ack(struct sip_pvt *pvt); +int sip_packet_ack(struct sip_pvt *pvt, uint32_t seqno, int resp, int sipmethod); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/parking.h asterisk-22.2.0/channels/sip/include/parking.h --- asterisk-22.2.0.orig/channels/sip/include/parking.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/parking.h 2025-02-18 17:14:46.806902698 +1300 @@ -0,0 +1,36 @@ +/* + * 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_pvt; +struct sip_request; +struct sip_peer; +struct sip_remotecc_data; + +int sip_remotecc_park(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data); +int sip_remotecc_parkmonitor(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/peers.h asterisk-22.2.0/channels/sip/include/peers.h --- asterisk-22.2.0.orig/channels/sip/include/peers.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/peers.h 2025-02-18 17:14:46.807902671 +1300 @@ -0,0 +1,234 @@ +/* + * 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_NOTOK (10 * 1000) /* Qualification: How often to check, if the host is down... */ + +/* Forward declarations */ +struct sip_pvt; +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(remotesecret); /* Remote secret (trunks, remote devices) */ + AST_STRING_FIELD(context); /* Default context for incoming calls */ + AST_STRING_FIELD(messagecontext); /* Default context for outofcall messages. */ + AST_STRING_FIELD(subscribecontext); /* Default context for subscriptions */ + AST_STRING_FIELD(username); /* Temporary username until registration */ + AST_STRING_FIELD(accountcode); /* Account code */ + AST_STRING_FIELD(tohost); /* If not dynamic, IP address */ + AST_STRING_FIELD(regexten); /* Extension to register (if regcontext is used) */ + AST_STRING_FIELD(fromuser); /* From: user when calling this peer */ + AST_STRING_FIELD(fromdomain); /* From: domain when calling this peer */ + AST_STRING_FIELD(fullcontact); /* Contact registered with us (not in sip.conf) */ + AST_STRING_FIELD(cid_num); /* Caller ID num */ + AST_STRING_FIELD(cid_name); /* Caller ID name */ + AST_STRING_FIELD(cid_tag); /* Caller ID tag */ + AST_STRING_FIELD(vmexten); /* Dialplan extension for MWI notify message*/ + AST_STRING_FIELD(language); /* Default language for prompts */ + AST_STRING_FIELD(mohinterpret); /* Music on Hold class */ + AST_STRING_FIELD(mohsuggest); /* 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(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(callforward); /* Call forwarding extension */ + AST_STRING_FIELD(regcallid); /* Call-ID of the REGISTER dialog */ + AST_STRING_FIELD(cisco_authname); /* Name of the primary line */ + AST_STRING_FIELD(cisco_softkey); /* Name of the last soft-key sent via remotecc */ + AST_STRING_FIELD(cisco_devicename); /* Name of the device */ + AST_STRING_FIELD(cisco_activeload); /* Name of the active firmware load */ + AST_STRING_FIELD(cisco_inactiveload); /* 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_outbound_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 short transports:5; /* Transports (enum ast_transport) that are acceptable for this peer */ + unsigned short is_realtime:1; /* this is a 'realtime' peer */ + unsigned short rt_fromcontact:1;/* copy fromcontact from realtime */ + unsigned short host_dynamic:1; /* Dynamic Peers register with Asterisk */ + unsigned short removed:1; /* That which bears the_mark should be deleted! */ + unsigned short autoframing: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 short deprecated_username:1; /* If it's a realtime peer, are they using the deprecated "username" instead of "defaultuser" */ + struct sip_auth_head *auth; /* Realm authentication credentials */ + int amaflags; /* AMA Flags (for billing) */ + int callingpres; /* Calling id presentation */ + int inuse; /* Number of calls in use */ + int ringing; /* Number of calls ringing */ + int onhold; /* Peer has someone on hold */ + int donotdisturb:1; /* Peer has set DoNotDisturb */ + int huntgroup: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_maxdatagram; /* T.38 FaxMaxDatagram override */ + int busy_level; /* Level of active channels where we signal busy */ + int maxforwards; /* SIP Loop prevention */ + int allowtransfer; /* SIP Refer restriction scheme */ + int lastmsgssent; /* The last known VM message counts (new/old) */ + int newmsgs; + int oldmsgs; + unsigned int sipoptions; /* Supported SIP options */ + struct ast_flags flags[3]; /* 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 maxcallbitrate; /* Maximum Bitrate for a video call */ + int expire; /* When to expire this peer registration */ + struct ast_format_cap *caps; /* Codec capability */ + int rtptimeout; /* RTP timeout */ + int rtpholdtimeout; /* RTP Hold Timeout */ + int rtpkeepalive; /* 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 *outboundproxy;/* Outbound proxy for this peer */ + struct ast_dnsmgr_entry *dnsmgr;/* DNS refresh manager for peer */ + struct ast_sockaddr addr; /* IP address of peer */ + unsigned int portinuri:1; /* Whether the port should be included in the URI */ + struct sip_pvt *qualifypvt; /* Call pointer */ + int qualifyexpire; /* Qualification: When to expire qualify (qualify= checking) */ + int lastms; /* Qualification: How long last response took (in ms), or -1 for no response */ + int maxms; /* Qualification: Max ms we will accept for the host to be up, 0 to not monitor */ + int qualifyfreq; /* Qualification: Qualification: How often to check for the host to be up */ + struct timeval qualifystart; /* Qualification: Time for sending SIP OPTION in sip_peer_qualify() */ + int keepalive; /* Keepalive: How often to send keep alive packet */ + int keepalivesend; /* Keepalive: Scheduled item for sending keep alive packet */ + struct ast_sockaddr defaddr; /* Default IP address, used until registration */ + struct ast_acl_list *acl; /* Access control list */ + struct ast_acl_list *contactacl; /* Restrict what IPs are allowed in the Contact header (for registration) */ + struct ast_acl_list *directmediaacl; /* Restrict what IPs are allowed to interchange direct media with */ + struct ast_variable *chanvars; /* Variables to set for channel created by user */ + struct sip_pvt *mwipvt; /* Subscription for MWI */ + struct sip_pvt *fepvt; /* Subscription for Feature Events */ + struct sip_callback *callback; /* Extension State watcher for Call Back requests */ + struct sip_stimer_config stimer; /* SIP Session-Timers */ + int timer_t1; /* The maximum T1 value for the peer */ + int timer_b; /* The maximum timer B (transaction timeouts) */ + int fromdomainport; /* The From: domain port */ + struct sip_route path; /* List of out-of-dialog outgoing routing steps (fm Path headers) */ + int cisco_lineindex; /* Line index number */ + int cisco_pickupnotify_timer; /* Toast timer for pickup notify */ + time_t cisco_pickupnotify_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 *event_sub; + struct sip_peer *peer; + unsigned int removed:1; + char name[0]; +}; + +/* A peer's bulk register aliases */ +struct sip_alias { + AST_LIST_ENTRY(sip_alias) next; + char *name; + struct sip_peer *peer; + int lineindex; + unsigned int removed:1; +}; + +/* A peer's subscription */ +struct sip_subscription { + AST_LIST_ENTRY(sip_subscription) next; + struct sip_pvt *pvt; + 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_addr; + +extern int sip_static_count; +extern int sip_realtime_count; + +extern struct sip_peer *sip_bogus_peer; + +int sip_peer_hash(const void *obj, const int flags); +int sip_peer_cmp(void *obj, void *arg, int flags); +int sip_peer_hash_addr(const void *obj, const int flags); +int sip_peer_cmp_addr(void *obj, void *arg, int flags); +struct sip_peer *sip_peer_find(const char *peer, struct ast_sockaddr *addr, int realtime, int devstate_only, int transport); + +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 *vars, struct ast_variable *alt, int realtime, + int devstate_only); +int sip_peer_check_transport(struct sip_peer *peer, int type); + +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_extension_state_subscriptions(struct sip_peer *peer); +void sip_peer_mailbox_subscriptions(struct sip_peer *peer); +void sip_peer_set_lastmsgs(struct sip_peer *peer, int newmsgs, int oldmsgs, int locked); +void sip_peer_dnsmgr_lookup(struct ast_sockaddr *oldaddr, struct ast_sockaddr *newaddr, void *data); + +int sip_peer_get_status(struct sip_peer *peer, char *status, int statuslen); +void sip_peer_get_mailboxes(struct sip_peer *peer, struct ast_str **mailboxes); +int sip_peer_get_cached_mwi(struct sip_peer *peer, int *newmsgs, int *oldmsgs); +int sip_peer_send_mwi(struct sip_peer *peer, int cache_only); + +void sip_peer_keepalive_all(void); +int sip_peer_qualify_now(const void *data); +int sip_peer_qualify(struct sip_peer *peer, int force); +int __sip_peer_qualify(const void *data); +int sip_peer_qualify_noanswer(const void *data); +void sip_peer_qualify_all(void); + +int sip_peer_send_bulkupdate(struct sip_peer *peer); +int sip_peer_send_donotdisturb(struct sip_peer *peer); +int sip_peer_send_huntgroup(struct sip_peer *peer); +int sip_peer_send_callforward(struct sip_peer *peer); +void sip_peer_send_qrt_url(struct sip_peer *peer); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/pickup.h asterisk-22.2.0/channels/sip/include/pickup.h --- asterisk-22.2.0.orig/channels/sip/include/pickup.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/pickup.h 2025-02-18 17:14:46.807902671 +1300 @@ -0,0 +1,33 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_PICKUP_H +#define _SIP_PICKUP_H + +int sip_pickup_call(struct ast_channel *chan); +int sip_pickup_exten(struct ast_channel *chan, const char *exten, const char *context); + +void sip_pickup_notify_subscribe(void); +void sip_pickup_notify_unsubscribe(void); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/proxy.h asterisk-22.2.0/channels/sip/include/proxy.h --- asterisk-22.2.0.orig/channels/sip/include/proxy.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/proxy.h 2025-02-18 17:14:46.807902671 +1300 @@ -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_PROXY_H +#define _SIP_PROXY_H + +/* Forward declarations */ +struct sip_peer; +struct sip_pvt; + +/* 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_pvt may also contain a reference to a peer's outboundproxy, or it may contain a reference to the + * sip_config.outboundproxy. */ +struct sip_proxy { + enum ast_transport transport; + char host[MAXHOSTNAMELEN]; /* DNS name of domain/host or IP */ + int port; + struct ast_sockaddr addr; /* Currently used IP address and port */ + time_t last_dnsupdate; /* 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_ref(struct sip_pvt *pvt, struct sip_proxy *proxy); +struct sip_proxy *sip_proxy_get(struct sip_pvt *pvt, struct sip_peer *peer); +struct sip_proxy *sip_proxy_build(const char *line, int lineno, struct sip_proxy *dest); +int sip_proxy_update(struct sip_proxy *proxy); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/pvt.h asterisk-22.2.0/channels/sip/include/pvt.h --- asterisk-22.2.0.orig/channels/sip/include/pvt.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/pvt.h 2025-02-18 17:23:19.288245225 +1300 @@ -0,0 +1,513 @@ +/* + * 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_PVT_H +#define _SIP_PVT_H + +#define SIP_INITIAL_CSEQ 1 /* Our initial sip sequence number */ +#define SIP_MAX_AUTHTRIES 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) */ + +/* 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 */ +}; + +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, +}; + +/* 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 */ +}; + +/* Authentication result from check_auth* functions */ +enum { + SIP_AUTH_DONT_KNOW = -10, /* no result, need to check further */ + SIP_AUTH_SESSION_LIMIT = -9, + SIP_AUTH_RTP_FAILED = -8, + SIP_AUTH_BAD_TRANSPORT = -7, + SIP_AUTH_ACL_FAILED = -6, + SIP_AUTH_PEER_NOT_DYNAMIC = -5, + SIP_AUTH_UNKNOWN_DOMAIN = -4, + SIP_AUTH_NOT_FOUND = -3, /* returned by register_verify */ + SIP_AUTH_USERNAME_MISMATCH = -2, + SIP_AUTH_SECRET_FAILED = -1, + SIP_AUTH_SUCCESSFUL = 0, + SIP_AUTH_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_MATCHMORE = 1, +}; + +/* Type of subscription, based on the packages we do support */ +enum { + SIP_SUBSCRIBED_NONE = 0, + SIP_SUBSCRIBED_DIALOG_INFO_XML, + SIP_SUBSCRIBED_PIDF_XML, + SIP_SUBSCRIBED_MESSAGE_SUMMARY, + SIP_SUBSCRIBED_FEATURE_EVENTS, + SIP_SUBSCRIBED_REMOTECC_XML, +}; + +/* Parameters to know status of transfer */ +enum { + SIP_REFER_SENT = 0, /* Sent REFER to transferee */ + SIP_REFER_ACCEPTED, /* Accepted by transferee */ + SIP_REFER_200OK, /* Answered by transfer target */ + SIP_REFER_FAILED, /* REFER declined - go on */ + SIP_REFER_NOAUTH /* We had no auth for REFER */ +}; + +/* Forward declarations */ +struct sip_auth; +struct sip_peer; +struct sip_registry; +struct sip_mwi_subscription; + +/* T.38 channel settings (at some point we need to make this alloc'ed */ +struct sip_t38_properties { + int state; /* T.38 state */ + struct ast_control_t38_parameters our_parms; + struct ast_control_t38_parameters their_parms; +}; + +/* Structure used for each SIP dialog, ie. a call, a registration, a subscribe. Created and initialized by sip_pvt_alloc(), the + * descriptor goes into the list of descriptors (sip_pvts). */ +struct sip_pvt { + struct sip_pvt *next; /* Next dialog in chain */ + int invitestate; /* Track state of SIP_INVITEs */ + ast_callid logger_callid; /* Identifier for call used in log messages */ + int method; /* SIP method that opened this dialog */ + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(callid); /* Global CallID */ + AST_STRING_FIELD(initviabranch); /* The branch ID from the topmost Via header in the initial request */ + AST_STRING_FIELD(initviasentby); /* The sent-by from the topmost Via header in the initial request */ + 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(messagecontext); /* Default context for outofcall messages. */ + AST_STRING_FIELD(subscribecontext); /* Subscribecontext */ + AST_STRING_FIELD(subscribeuri); /* Subscribecontext */ + AST_STRING_FIELD(fromdomain); /* Domain to show in the from field */ + AST_STRING_FIELD(fromuser); /* User to show in the user field */ + AST_STRING_FIELD(fromname); /* Name to show in the user field */ + AST_STRING_FIELD(tohost); /* Host we should put in the "to" field */ + AST_STRING_FIELD(todnid); /* DNID of this call (overrides host) */ + AST_STRING_FIELD(language); /* Default language for this call */ + AST_STRING_FIELD(mohinterpret); /* MOH class to use when put on hold */ + AST_STRING_FIELD(mohsuggest); /* MOH class to suggest when putting a peer on hold */ + AST_STRING_FIELD(rdnis); /* Referring DNIS */ + AST_STRING_FIELD(redircause); /* Referring cause */ + AST_STRING_FIELD(theirtag); /* Their tag */ + AST_STRING_FIELD(theirprovtag); /* Provisional their tag, used when evaluating responses to invites */ + AST_STRING_FIELD(tag); /* Our tag for this session */ + AST_STRING_FIELD(username); /* [user] name */ + AST_STRING_FIELD(peername); /* [peer] name, not set if [user] */ + AST_STRING_FIELD(authname); /* Who we use for authentication */ + AST_STRING_FIELD(uri); /* Original requested URI */ + AST_STRING_FIELD(okcontacturi); /* URI from the 200 OK on INVITE */ + AST_STRING_FIELD(peersecret); /* Password */ + AST_STRING_FIELD(peermd5secret); + AST_STRING_FIELD(cid_num); /* Caller*ID number */ + AST_STRING_FIELD(cid_name); /* Caller*ID name */ + AST_STRING_FIELD(cid_tag); /* Caller*ID tag */ + AST_STRING_FIELD(mwi_from); /* Name to place in the From header in outgoing NOTIFY requests */ + AST_STRING_FIELD(fullcontact); /* 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(url); /* URL to be sent with next message to peer */ + AST_STRING_FIELD(parkinglot); /* Parkinglot */ + AST_STRING_FIELD(engine); /* RTP engine to use */ + AST_STRING_FIELD(dialstring); /* 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(msg_body); /* Text for a MESSAGE body */ + AST_STRING_FIELD(sessionunique_remote); /* Remote UA's SDP Session unique parts */ + AST_STRING_FIELD(callforward); /* Call Forward target */ + AST_STRING_FIELD(join_callid); /* Join callid */ + AST_STRING_FIELD(join_tag); /* Join tag */ + AST_STRING_FIELD(join_theirtag); /* Join theirtag */ + AST_STRING_FIELD(via); /* Via: header */ + AST_STRING_FIELD(lastmsg); /* Last Message sent/received */ + ); + int maxforwards; /* SIP Loop prevention */ + struct sip_socket socket; /* The socket used for this dialog */ + uint32_t ocseq; /* Current outgoing seqno */ + uint32_t icseq; /* Current incoming seqno */ + uint32_t init_icseq; /* Initial incoming seqno from first request */ + 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 */ + uint32_t lastinvite; /* Last seqno of invite */ + struct ast_flags flags[3]; /* SIP_ flags */ + unsigned short recordhistory:1; /* Set if we want to record history */ + unsigned short alreadygone:1; /* the peer has sent a message indicating termination of the dialog */ + unsigned short needdestroy:1; /* this dialog needs to be destroyed by the monitor thread */ + unsigned short final_destruction_scheduled:1; /* final dialog destruction is scheduled. Keep dialog around until then + * to handle retransmits. */ + unsigned short outgoing_call:1; /* this is an outgoing call */ + unsigned short answered_elsewhere:1; /* This call is cancelled due to answer on another channel */ + unsigned short novideo:1; /* Didn't get video in invite, don't offer */ + unsigned short notext:1; /* Text not supported (?) */ + unsigned short session_modify:1; /* Session modification request true/false */ + unsigned short route_persistent:1; /* Is this the "real" route? */ + unsigned short autoframing: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 short req_secure_signaling:1;/* Whether we are required to have secure signaling or not */ + unsigned short natdetected:1; /* Whether we detected a NAT when processing the Via */ + int timer_t1; /* SIP timer T1, ms rtt */ + int timer_b; /* SIP timer B, ms */ + unsigned int sipoptions; /* Supported SIP options on the other end */ + unsigned int reqsipoptions; /* Required SIP options on the other end */ + struct ast_format_cap *caps; /* Special capability (codec) */ + struct ast_format_cap *jointcaps; /* Supported capability at both ends (codecs) */ + struct ast_format_cap *peercaps; /* Supported peer capability */ + struct ast_format_cap *redircaps; /* Redirect codecs */ + struct ast_format_cap *prefcaps; /* Preferred codec (outbound only) */ + int noncodeccapability; /* DTMF RFC2833 telephony-event */ + int jointnoncodeccapability; /* Joint Non codec capability */ + int maxcallbitrate; /* Maximum Call Bitrate for Video Calls */ + int t38_maxdatagram; /* T.38 FaxMaxDatagram override */ + 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 */ + const char *last_provisional; /* The last successfully transmitted provisional response message */ + int authtries; /* Times we've tried to authenticate */ + struct sip_proxy *outboundproxy; /* Outbound proxy for this dialog. Use ref_proxy to set this instead of setting it directly*/ + struct sip_t38_properties t38; /* T38 settings */ + struct ast_sockaddr udptlredirip; /* Where our T.38 UDPTL should be going if not to us */ + struct ast_udptl *udptl; /* T.38 UDPTL session */ + char zone[MAX_TONEZONE_COUNTRY]; /* Default tone zone for channels created by this dialog */ + int callingpres; /* Calling presentation */ + int expiry; /* How long we take to expire */ + int sessionversion; /* SDP Session Version */ + int sessionid; /* SDP Session ID */ + long branch; /* The branch identifier of this session */ + long invite_branch; /* The branch used when we sent the initial INVITE */ + int64_t sessionversion_remote; /* Remote UA's SDP Session Version */ + unsigned int portinuri:1; /* Non zero if a port has been specified, will also disable srv lookups */ + struct ast_sockaddr sa; /* Our peer */ + struct ast_sockaddr redirip; /* Where our RTP should be going if not to us */ + struct ast_sockaddr vredirip; /* Where our Video RTP should be going if not to us */ + struct ast_sockaddr tredirip; /* Where our Text RTP should be going if not to us */ + time_t lastrtprx; /* Last RTP received */ + time_t lastrtptx; /* Last RTP sent */ + int rtptimeout; /* RTP timeout time */ + int rtpholdtimeout; /* RTP timeout time on hold*/ + int rtpkeepalive; /* RTP send packets for keepalive */ + struct ast_acl_list *directmediaacl; /* Which IPs are allowed to interchange direct media with this peer, copied from sip_peer */ + struct ast_sockaddr recv; /* Received as */ + struct ast_sockaddr ourip; /* Our IP (as seen from the outside) */ + int allowtransfer; /* REFER: restriction scheme */ + struct ast_channel *owner; /* Who owns us (if we have an owner) */ + struct sip_route route; /* List of routing steps (fm Record-Route) */ + struct sip_notify *notify; /* Custom notify type */ + struct sip_auth_head *peerauth; /* Realm authentication credentials */ + int noncecount; /* Nonce-count */ + unsigned int stalenonce: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 */ + int amaflags; /* AMA Flags */ + uint32_t pendinginvite; /* Any pending INVITE or state NOTIFY (in subscribe pvt's) ? (seqno of this) */ + uint32_t glareinvite; /* A invite received while a pending invite is already present is stored here. Its seqno is the value. Since + * this glare invite's seqno is not the same as the pending invite's, it must be held in order to properly + * process acknowledgements for our 491 response. */ + struct sip_request initreq; /* Latest request that opened a new transactiom within this dialog. NOT the request that opened the + * dialog */ + int initid; /* Auto-congest ID if appropriate (scheduler) */ + int waitid; /* Wait ID for scheduler after 491 or other delays */ + int reinviteid; /* Reinvite in case of provisional, but no final response */ + int autokillid; /* Auto-kill ID (scheduler) */ + int t38id; /* T.38 Response ID */ + struct sip_refer *refer; /* REFER: SIP transfer data structure */ + int subscribed; /* SUBSCRIBE: Is this dialog a subscription? */ + int stateid; /* SUBSCRIBE: ID for devicestate subscriptions */ + struct ao2_container *last_device_state_info; /* SUBSCRIBE: last known extended extension state (take care of refs) */ + struct timeval last_ringing_channel_time; /* SUBSCRIBE: channel timestamp of the channel which caused the last early-state + * notification */ + int last_exten_state; /* SUBSCRIBE: Last known extension state */ + int last_presence_state; /* SUBSCRIBE: Last known presence state */ + uint32_t dialogver; /* SUBSCRIBE: Version for subscription dialog-info */ + struct ast_dsp *dsp; /* Inband DTMF or Fax CNG tone Detection dsp */ + struct sip_peer *relatedpeer; /* 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 ast_rtp_instance *rtp; /* RTP Session */ + struct ast_rtp_instance *vrtp; /* Video RTP session */ + struct ast_rtp_instance *trtp; /* Text RTP session */ + AST_LIST_HEAD_NOLOCK(, sip_packet) packet_queue; /* Packets scheduled for re-transmission */ + struct sip_history_head *history; /* History of this SIP dialog */ + size_t history_entries; /* Number of entires in the history */ + struct ast_variable *chanvars; /* Channel variables to set for inbound call */ + AST_LIST_HEAD_NOLOCK(, sip_msg_header) msg_headers; /* Additional MESSAGE headers to send. */ + AST_LIST_HEAD_NOLOCK(, sip_request) request_queue; /* Requests that arrived but could not be processed immediately */ + struct sip_invite_options *options; /* Options for INVITE */ + struct sip_stimer *stimer; /* SIP Session-Timers */ + struct ast_sdp_srtp *srtp; /* Structure to hold Secure RTP session data for audio */ + struct ast_sdp_srtp *vsrtp; /* Structure to hold Secure RTP session data for video */ + struct ast_sdp_srtp *tsrtp; /* Structure to hold Secure RTP session data for text */ + int donotdisturb:1; /* Peer has set DoNotDisturb */ + struct sip_pvt *recordoutpvt; /* Pvt for the outbound recording leg */ + struct sip_pvt *recordinpvt; /* Pvt for the inbound recording leg */ + int red; /* T.140 RTP Redundancy */ + int hangupcause; /* Storage of hangupcause copied from our owner before we disconnect from the AST channel (only used at hangup) */ + struct sip_mwi_subscription *mwi; /* If this is a subscription MWI dialog, to which subscription */ + 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_pvt at various stages of dialog establishment */ + /* 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_offered_media) offered_media; + int fromdomainport; /* Domain port to show in from field */ + struct sip_conference *conference; /* Ad-hoc n-way conference support */ +}; + +/* Parameters to the sip_send_invite function */ +struct sip_invite_options { + int addsipheaders; /* Add extra SIP headers */ + const char *uri_options; /* URI options to add to the URI */ + char *auth; /* Authentication */ + char *authheader; /* Auth header */ + int auth_type; /* Authentication type */ + const char *replaces; /* Replaces header for call transfers */ + struct sip_proxy *outboundproxy; /* Outbound proxy URI */ +}; + +/* Structure for remembering offered media in an INVITE, to make sure we reply to all media streams. */ +struct sip_offered_media { + AST_LIST_ENTRY(sip_offered_media) next; + int type; /* The type of media that was offered */ + char *decline_m_line; /* Used if the media type is unknown/unused or a media stream is declined */ +}; + +struct sip_extension_state_data { + int exten_state; + int presence_state; + struct ao2_container *device_state_info; + const char *presence_subtype; + const char *presence_message; +}; + +/* Structure to handle SIP transfers. Dynamically allocated when needed */ +struct sip_refer { + AST_DECLARE_STRING_FIELDS( + 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_urioption); /* Place to store REFER-TO uri options */ + 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(replaces_callid); /* Replace info: callid */ + AST_STRING_FIELD(replaces_callid_totag); /* Replace info: to-tag */ + AST_STRING_FIELD(replaces_callid_fromtag); /* Replace info: from-tag */ + 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 */ + ); + int attendedtransfer; /* Attended or blind transfer? */ + int localtransfer; /* Transfer to local domain? */ + int status; /* REFER status */ + struct ast_str *content; /* Outgoing content body */ +}; + +/* Struct to handle custom SIP notify requests. Dynamically allocated when needed */ +struct sip_notify { + struct ast_variable *headers; + struct ast_str *content; +}; + +/* 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 */ +}; + +AST_LIST_HEAD_NOLOCK(sip_history_head, sip_history); /* history list, entry in sip_pvt */ + +extern struct ao2_container *sip_pvts; +extern struct ao2_container *sip_pvts_need_destroy; +extern struct ao2_container *sip_pvts_rtp_check; + +int sip_pvt_hash(const void *obj, const int flags); +int sip_pvt_cmp(void *obj, void *arg, int flags); + +struct sip_pvt *sip_pvt_alloc(ast_string_field callid, struct ast_sockaddr *sin, int useglobal_nat, const int method, + struct sip_request *req, ast_callid logger_callid); +struct ast_channel *sip_pvt_channel_alloc(struct sip_pvt *pvt, int state, const char *peername, const struct ast_assigned_ids *assignedids, + const struct ast_channel *requestor, ast_callid callid); + +void sip_pvt_unlink(struct sip_pvt *pvt); + +struct sip_pvt *sip_pvt_find(const char *callid, const char *totag, const char *fromtag); +int sip_pvt_find_full(const char *callid, const char *totag, const char *fromtag, struct sip_pvt **found_pvt, struct ast_channel **found_chan); + +struct ast_channel *sip_pvt_lock_full(struct sip_pvt *pvt); +void sip_pvt_copy_data(struct sip_pvt *to_pvt, struct sip_pvt *from_pvt); + +int sip_pvt_sched_destroy(struct sip_pvt *p, int ms); +void sip_pvt_cancel_destroy(struct sip_pvt *pvt); + +int sip_pvt_need_destroy(void *data, void *arg, int flags); +int sip_pvt_rtp_check(void *data, void *arg, int flags); + +int sip_pvt_auto_destruct(const void *data); +int sip_pvt_auto_congest(const void *data); +void sip_pvt_check_pendings(struct sip_pvt *pvt); +void sip_pvt_sched_check_pendings(struct sip_pvt *pvt); + +void sip_pvt_set_need_destroy(struct sip_pvt *pvt, const char *reason); +void sip_pvt_set_already_gone(struct sip_pvt *pvt); +void sip_pvt_set_owner(struct sip_pvt *pvt, struct ast_channel *chan); +void sip_pvt_set_ouraddrfor(struct sip_pvt *pvt, const struct ast_sockaddr *them, struct ast_sockaddr *us); +void sip_pvt_set_socket_transport(struct sip_socket *socket, int transport); +unsigned int sip_pvt_set_allowed_methods(struct sip_pvt *pvt, struct sip_request *req); +void sip_pvt_set_realm(struct sip_pvt *pvt, const struct sip_request *req); +void sip_pvt_set_dsp_detect(struct sip_pvt *pvt, int enabled); +void sip_pvt_set_nat(const struct sip_pvt *pvt, struct sip_peer *peer); +void sip_pvt_set_rtp_nat(struct sip_pvt *pvt); + +void sip_pvt_check_for_nat(struct sip_pvt *pvt, const struct ast_sockaddr *them); +const struct ast_sockaddr *sip_pvt_real_dst(const struct sip_pvt *pvt); + +int sip_pvt_build_from_peer(struct sip_pvt *pvt, struct sip_peer *peer); +int sip_pvt_build(struct sip_pvt *pvt, const char *opeer, struct ast_sockaddr *addr, int newdialog); +void sip_pvt_build_contact(struct sip_pvt *pvt, struct sip_request *req, int incoming); +void sip_pvt_build_route(struct sip_pvt *pvt, struct sip_request *req, int backwards, int resp); +void sip_pvt_build_our_tag(struct sip_pvt *pvt); +void sip_pvt_build_nonce(struct sip_pvt *pvt, int forceupdate); +void sip_pvt_build_via(struct sip_pvt *pvt); + +int sip_pvt_get_destination(struct sip_pvt *pvt, struct sip_request *oreq); +void sip_pvt_set_destination(struct sip_pvt *pvt, const char *uri); + +const char *sip_pvt_get_transport(struct sip_pvt *pvt); +void sip_pvt_get_our_media_addr(struct sip_pvt *pvt, int needvideo, int needtext, struct ast_sockaddr *addr, struct ast_sockaddr *vaddr, + struct ast_sockaddr *taddr, struct ast_sockaddr *dest, struct ast_sockaddr *vdest, struct ast_sockaddr *tdest); + +void sip_pvt_initial_request(struct sip_pvt *pvt, struct sip_request *req); +void sip_pvt_change_callid(struct sip_pvt *pvt, const char *callid); + +void sip_pvt_try_suggested_codec(struct sip_pvt *pvt); +void sip_pvt_extract_uri(struct sip_pvt *pvt, struct sip_request *req); + +int sip_pvt_process_via(struct sip_pvt *pvt, const struct sip_request *req); +void sip_pvt_check_via(struct sip_pvt *pvt, const struct sip_request *req); + +int sip_pvt_setup_udptl(struct sip_pvt *pvt); +void sip_pvt_setup_rtcp(struct sip_pvt *pvt, struct ast_rtp_instance *instance, int which, int remote_rtcp_mux); +void sip_pvt_stop_rtp(struct sip_pvt *pvt); + +void sip_pvt_stop_reinvite(struct sip_pvt *pvt); +int sip_pvt_reinvite_timeout(const void *data); +int sip_pvt_start_reinvite_retry(const void *data); +void sip_pvt_stop_reinvite_retry(struct sip_pvt *pvt); + +int sip_pvt_parse_reply_digest(struct sip_pvt *pvt, struct sip_request *req, char *header, int method, char *digest, int digestlen); +int sip_pvt_build_reply_digest(struct sip_pvt *pvt, int method, char *digest, int digestlen); + +void sip_pvt_queue_connected_line_update(struct sip_pvt *pvt, int source); +void sip_pvt_update_connected_line(struct sip_pvt *pvt, const void *data, size_t datalen); + +void sip_pvt_update_redirecting(struct sip_pvt *pvt, const void *data, size_t datalen); +void sip_pvt_set_redirecting(struct sip_pvt *pvt, struct sip_request *req, struct ast_party_redirecting *redirecting, + struct ast_set_party_redirecting *update_redirecting, int set_call_forward); + +int sip_pvt_update_t38_capabilities(struct sip_pvt *pvt, const struct ast_control_t38_parameters *parms); +void sip_pvt_change_t38_state(struct sip_pvt *pvt, int state); +void sip_pvt_start_t38_abort_timer(struct sip_pvt *pvt); + +void sip_pvt_update_provisional_keepalive(struct sip_pvt *pvt, int with_sdp); +void sip_pvt_cancel_provisional_keepalive(struct sip_pvt *pvt); + +int sip_pvt_has_offered_media(struct sip_pvt *pvt, int type); +void sip_pvt_free_offered_media(struct sip_pvt *pvt); + +int sip_pvt_update_call_counter(struct sip_pvt *fup, int event); +void sip_pvt_change_onhold(struct sip_pvt *pvt, struct sip_request *req, int onhold, int sendonly); + +void sip_pvt_init_forked_invite(struct sip_request *req, const char *new_theirtag, struct sip_pvt *orig_pvt, struct ast_sockaddr *addr); + +void sip_pvt_extension_state_destroy(int id, void *data); +int sip_pvt_extension_state_update(struct sip_pvt *pvt, struct sip_extension_state_data *state_data, int force); +int sip_pvt_extension_state_event(const char *context, const char *exten, struct ast_state_cb_info *info, void *data); + +int sip_pvt_check_register_auth(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr); +int sip_pvt_check_peer_auth(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, struct sip_peer **authpeer, int reliable); +int sip_pvt_check_message_auth(struct sip_pvt *pvt, int resp, struct sip_request *req); +int sip_pvt_handle_register_auth(struct sip_pvt *pvt, struct sip_request *req, int resp); +int sip_pvt_handle_proxy_auth(struct sip_pvt *pvt, struct sip_request *req, int resp, int method, int init); + +void sip_pvt_start_ice(struct ast_rtp_instance *instance, int offer); +void sip_pvt_set_ice_components(struct sip_pvt *pvt, struct ast_rtp_instance *instance, int remote_rtcp_mux); + +int sip_notify_alloc(struct sip_pvt *pvt); +int sip_refer_alloc(struct sip_pvt *pvt); + +void sip_history_append(struct sip_pvt *pvt, const char *type, const char *fmt, ...); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/realtime.h asterisk-22.2.0/channels/sip/include/realtime.h --- asterisk-22.2.0.orig/channels/sip/include/realtime.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/realtime.h 2025-02-18 17:14:46.809902618 +1300 @@ -0,0 +1,33 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_REALTIME_H +#define _SIP_REALTIME_H + +/* Forward declarations */ +struct sip_peer; + +struct sip_peer *sip_realtime_load(const char *name, struct ast_sockaddr *addr, int devstate_only); +void sip_realtime_update(struct sip_peer *peer, int expiry); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/recording.h asterisk-22.2.0/channels/sip/include/recording.h --- asterisk-22.2.0.orig/channels/sip/include/recording.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/recording.h 2025-02-18 17:14:46.809902618 +1300 @@ -0,0 +1,40 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_RECORDING_H +#define _SIP_RECORDING_H + +/* Information required to record a call */ +struct sip_recording_data { + int outgoing:1; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(callid); + AST_STRING_FIELD(tag); + AST_STRING_FIELD(theirtag); + ); +}; + +int sip_recording_start(const char *callid, const char *tag, const char *theirtag, int outgoing); +int sip_recording_stop(const char *callid, const char *tag, const char *theirtag); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/registry.h asterisk-22.2.0/channels/sip/include/registry.h --- asterisk-22.2.0.orig/channels/sip/include/registry.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/registry.h 2025-02-18 17:14:46.809902618 +1300 @@ -0,0 +1,113 @@ +/* + * 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 + +/* guard limit must be larger than guard secs, guard min must be < 1000, and should be >= 250 */ +#define SIP_EXPIRY_GUARD_SECS 15 /* How long before expiry do we reregister */ +#define SIP_EXPIRY_GUARD_LIMIT 30 /* Below here, we use EXPIRY_GUARD_PCT instead of EXPIRY_GUARD_SECS */ +#define SIP_EXPIRY_GUARD_MIN 500 /* This is the minimum guard time applied. If GUARD_PCT turns out to be lower than this, it + * will use this time instead. This is in milliseconds. */ +#define SIP_EXPIRY_GUARD_PERCENT 0.20 /* Percentage of expires timeout to use when below EXPIRY_GUARD_LIMIT */ + +/* 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_REGSENT, /* Registration request sent sent initial request, waiting for an ack or a timeout to retransmit the initial + * request. */ + SIP_REGISTRY_AUTHSENT, /* 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_NOAUTH, /* 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_pvt; + +/* Registrations with other SIP proxies Created by sip_register_build(), the entry is linked in the 'sip_registry' list, and never + * deleted (other than at 'sip reload' or module unload times). The entry always has a pending timeout, either waiting for an ACK to + * the REGISTER message (in which case we have to retransmit the request), or waiting for the next REGISTER message to be sent (either + * the initial one, or once the previously completed registration one expires). The registration can be in one of many states, though + * at the moment the handling is a bit mixed. */ +struct sip_registry { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(config);/* register string from config */ + AST_STRING_FIELD(callid); /* Global 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(authdomain); /* Authorization domain */ + AST_STRING_FIELD(domain); /* Registration domain */ + AST_STRING_FIELD(username); /* Who we are registering as */ + AST_STRING_FIELD(authuser); /* 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(peername); /* Peer registering to */ + AST_STRING_FIELD(localtag); /* Local tag generated same time as callid */ + ); + enum ast_transport transport; /* Transport for this registration UDP, TCP or TLS */ + int port; /* Optional port override */ + int domainport; /* Port override for domainport */ + int expire; /* Sched ID of expiration */ + int configured_expiry; /* Configured value to use for the Expires header */ + int expiry; /* Negotiated value used for the Expires header */ + int attempts; /* Number of attempts (since the last success) */ + int timeout; /* sched id of sip_reg_timeout */ + int refresh; /* How often to refresh */ + struct sip_pvt *pvt; /* create a sip_pvt structure for each outbound "registration dialog" in progress */ + int state; /* Registration state (see above) */ + struct timeval time; /* Last successful registration time */ + int callid_valid; /* 0 means we haven't chosen callid for this registry yet. */ + uint32_t ocseq; /* Sequence number we got to for REGISTERs for this registry */ + struct ast_dnsmgr_entry *dnsmgr; /* DNS refresh manager for register */ + struct ast_sockaddr us; /* Who the server thinks we are */ + int noncecount; /* Nonce-count */ +}; + +extern struct ao2_container *sip_registry; + +const char *sip_registry_state2str(int state); +int sip_registry_hash(const void *obj, const int flags); +int sip_registry_cmp(void *obj, void *arg, int flags); +void sip_registry_destroy(void *data); +void sip_registry_destroy_all(void); +void sip_registry_dnsmgr_lookup(struct ast_sockaddr *oldaddr, struct ast_sockaddr *newaddr, void *data); +int sip_registry_build(const char *config, int lineno); +void sip_registry_send_all(void); +int sip_registry_send(const void *data); +void sip_registry_sched_send(struct sip_registry *registry, int ms); +void sip_registry_start_timeout(struct sip_registry *registry); +void sip_registry_stop_timeout(struct sip_registry *registry); +void sip_registry_build_callid(struct sip_registry *registry, const struct ast_sockaddr *ourip, const char *fromdomain); +void sip_registry_build_localtag(struct sip_registry *registry); +int sip_registry_timeout(const void *data); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/request.h asterisk-22.2.0/channels/sip/include/request.h --- asterisk-22.2.0.orig/channels/sip/include/request.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/request.h 2025-02-18 17:14:46.810902591 +1300 @@ -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_REQUEST_H +#define _SIP_REQUEST_H + +#define SIP_MIN_PACKET_SIZE 4096 /* Initialize size of memory to allocate for packets */ +#define SIP_MAX_PACKET_SIZE 20480 /* Max SIP packet size */ + +#define SIP_MAX_HEADERS 64 /* Max amount of SIP headers to read */ +#define SIP_MAX_LINES 256 /* Max amount of lines in SIP attachment (like SDP) */ + +/* SIP Methods we support. This string should be set dynamically. We only support REFER and SUBSCRIBE if we have allowsubscribe and + * allowrefer on in sip.conf. */ +#define SIP_ALLOWED_METHODS "INVITE,ACK,CANCEL,OPTIONS,BYE,REFER,SUBSCRIBE,NOTIFY,UPDATE,INFO,PUBLISH,MESSAGE" + +/* 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, /* Transmit SIP message without bothering with re-transmits */ + SIP_SEND_RELIABLE = 1, /* Transmit SIP message reliably, with re-transmits */ + SIP_SEND_CRITICAL = 2, /* Transmit 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 = 1, + SIP_INIT_REQUEST = 2, +}; + +/* Forward declarations */ +struct sip_route; +struct sip_pvt; +struct sip_extension_state_data; + +/* sip_request: The data grabbed from the socket. Incoming messages: we first store the data from the socket in data[], + * adding a trailing \0 to make string parsing routines happy. Then call parse_request() and req.method = sip_find_method(); + * to initialize the other fields. The \r\n at the end of each line is replaced by \0, so that data[] is not a conforming SIP + * message anymore. After this processing, rlpart1 is set to non-NULL to remember that we can run sip_get_header() on this + * kind of packet. + * + * sip_request_parse() splits the first line as follows: + * Requests have in the first line method uri SIP/2.0 + * rlpart1 = method; rlpart2 = uri; + * Responses have in the first line SIP/2.0 NNN description + * rlpart1 = SIP/2.0; rlpart2 = NNN + description; + * + * For outgoing packets, we initialize the fields with sip_init_request() or sip_init_response() (which fills the first line + * to "METHOD uri SIP/2.0" or "SIP/2.0 code text"), and then fill the rest with sip_request_add_header() and + * sip_request_add_content(). The \r\n at the end of the line are still there, so the sip_request_get_header() and similar + * functions don't work on those packets */ +struct sip_request { + AST_LIST_ENTRY(sip_request) next; + ptrdiff_t rlpart1; /* Offset of the SIP Method Name or "SIP/2.0" protocol version */ + ptrdiff_t rlpart2; /* Offset of the Request URI or Response Status */ + int headers; /* # of SIP Headers */ + int method; /* Method of this request */ + int lines; /* Body Content */ + unsigned int sdp_start; /* the line number where the SDP begins */ + unsigned int sdp_count; /* the number of lines of SDP */ + char debug; /* print extra debugging if non zero */ + char has_to_tag; /* non-zero if packet has To: tag */ + char ignore; /* if non-zero This is a re-transmit, ignore it */ + char authenticated; /* non-zero if this request was authenticated */ + ptrdiff_t header[SIP_MAX_HEADERS]; /* Array of offsets into the request string of each SIP header*/ + ptrdiff_t line[SIP_MAX_LINES]; /* Array of offsets into the request string of each SDP line*/ + struct ast_str *data; + struct ast_str *content; + struct sip_socket socket; /* The socket used for this request */ + unsigned int reqsipoptions; /* Items needed for Required header in responses */ +}; + +/* Given a sip_request and an offset, return the char * that resides there. It used to be that rlpart1, rlpart2, and the header and + * line arrays were character pointers. They are now offsets into the ast_str portion of the sip_request structure. To avoid adding + * a bunch of redundant pointer arithmetic to the code, this macro is provided to retrieve the string at a particular offset within + * the request's buffer. */ +#define SIP_REQUEST_PART(req, offset) (ast_str_buffer((req)->data) + ((req)->offset)) + +int sip_request_alloc(struct sip_request *req, int method, const char *uri); +void sip_request_init(struct sip_request *req, struct sip_pvt *pvt, int method, const char * const explicit_uri); +int sip_request_parse(struct sip_request *req); +int sip_request_parse_firstline(struct sip_request *req); +void sip_request_free(struct sip_request *req); +int sip_request_prepare(struct sip_request *req, struct sip_pvt *pvt, int method, uint32_t seqno, int newbranch); +int sip_request_send(struct sip_pvt *pvt, struct sip_request *req, int reliable, uint32_t seqno); +void sip_request_copy(struct sip_request *to_req, const struct sip_request *from_req); + +const char *sip_request_get_header_full(const struct sip_request *req, const char *name, int *start); +const char *sip_request_get_header(const struct sip_request *req, const char *name); +char *sip_request_get_content(struct sip_request *req, int start, int end); +char *sip_request_get_content_line(struct sip_request *req, char *name, char delimiter); +const char *sip_request_get_tag(const struct sip_request *req, const char *header, char *tagbuf, int tagbufsize); + +int sip_request_find_boundary(struct sip_request *req, const char *boundary, int start, int *done); +const char *sip_request_find_content_type(struct sip_request *req); +int sip_request_find_sdp(struct sip_request *req); + +void sip_request_add_header(struct sip_request *req, const char *name, const char *value); +void sip_request_build_header(struct sip_request *req, const char *name, const char *fmt, ...); +void sip_request_add_supported(struct sip_request *req, struct sip_pvt *pvt); +void sip_request_add_expires(struct sip_request *req, int expires); +void sip_request_add_date(struct sip_request *req); +void sip_request_add_rpid(struct sip_request *req, struct sip_pvt *pvt); +void sip_request_add_max_forwards(struct sip_request *req, struct sip_pvt *pvt); +void sip_request_add_text(struct sip_request *req, struct sip_pvt *pvt); +void sip_request_add_call_info(struct sip_request *req, struct sip_pvt *pvt); +void sip_request_add_vidupdate(struct sip_request *req); +void sip_request_add_route(struct sip_request *req, struct sip_route *route, int skip); +void sip_request_add_diversion(struct sip_request *req, struct sip_pvt *pvt); +void sip_request_add_join(struct sip_request *req, struct sip_pvt *pvt); +void sip_request_add_sdp(struct sip_request *resp, struct sip_pvt *pvt, int oldsdp, int add_audio, int add_t38); +void sip_request_add_require(struct sip_request *req); +void sip_request_add_text(struct sip_request *req, struct sip_pvt *pvt); + +void sip_request_copy_header(struct sip_request *req, const struct sip_request *orig, const char *field); +void sip_request_copy_via(struct sip_pvt *pvt, struct sip_request *req, const struct sip_request *orig_req, const char *name); + +void sip_request_add_content(struct sip_request *req, const char *line); +void sip_request_end_content(struct sip_request *req); + +struct sip_pvt *sip_request_find_pvt(struct sip_request *req, struct ast_sockaddr *addr, const int method); + +int sip_send_request(struct sip_pvt *pvt, int method, uint32_t seqno, int reliable, int newbranch); +int sip_send_request_with_auth(struct sip_pvt *pvt, int method, uint32_t seqno, int reliable, int newbranch); +int sip_send_invite(struct sip_pvt *pvt, int method, int sdp, int init, const char * const explicit_uri); +int sip_send_reinvite_with_sdp(struct sip_pvt *pvt, int t38version, int oldsdp); +int sip_send_register(struct sip_registry *registry, int method, const char *auth, const char *authheader); +int sip_send_notify_with_mwi(struct sip_pvt *pvt, int newmsgs, int oldmsgs, const char *vmexten); +int sip_send_notify_with_extension_state(struct sip_pvt *pvt, struct sip_extension_state_data *state_data, int timeout); +int sip_send_notify_with_sipfrag(struct sip_pvt *pvt, int cseq, char *message, int terminate); +int sip_send_refer(struct sip_pvt *pvt, const char *dest); +int sip_send_refer_with_content(struct sip_pvt *pvt, const char *content_type, const char *content); +int sip_send_info_with_vidupdate(struct sip_pvt *pvt); +int sip_send_message(struct sip_pvt *pvt, int init, int auth); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/response.h asterisk-22.2.0/channels/sip/include/response.h --- asterisk-22.2.0.orig/channels/sip/include/response.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/response.h 2025-02-18 17:14:46.810902591 +1300 @@ -0,0 +1,55 @@ +/* + * 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_pvt; +struct sip_request; + +int sip_response_prepare(struct sip_request *resp, struct sip_pvt *pvt, const char *msg, const struct sip_request *req); +int sip_response_needs_contact(const char *msg, int method); +int sip_response_send(struct sip_pvt *pvt, struct sip_request *resp, int reliable, uint32_t seqno); + +int sip_send_response(struct sip_pvt *pvt, const char *msg, const struct sip_request *req); +int sip_send_response_provisional(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, int with_sdp); +int sip_send_response_reliable(struct sip_pvt *pvt, const char *msg, const struct sip_request *req); +int sip_send_response_with_sdp(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, int reliable, int oldsdp, int rpid); +int sip_send_response_with_t38_sdp(struct sip_pvt *pvt, char *msg, struct sip_request *req, int retrans); +int sip_send_response_using_temp(ast_string_field callid, struct ast_sockaddr *addr, int useglobal_nat, + const int method, const struct sip_request *req, const char *msg); +int sip_send_response_with_date(struct sip_pvt *pvt, const char *msg, const struct sip_request *req); +int sip_send_response_with_unsupported(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, + const char *unsupported); +int sip_send_response_with_auth(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, const char *rand, + int reliable, const char *header, int stale); +void sip_send_response_with_auth_fail(struct sip_pvt *pvt, struct sip_request *req, int res, int reliable); +void sip_send_response_with_fake_auth(struct sip_pvt *pvt, struct sip_request *req); +int sip_send_response_with_min_se(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, int min_se); +int sip_send_response_with_min_expires(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, int min_expires); +int sip_send_response_with_optionsind(struct sip_pvt *pvt, const struct sip_request *req); +int sip_send_response_with_allow(struct sip_pvt *pvt, const char *msg, const struct sip_request *req); +int sip_send_response_with_retry_after(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, int retry); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/route.h asterisk-22.2.0/channels/sip/include/route.h --- asterisk-22.2.0.orig/channels/sip/include/route.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/route.h 2025-02-18 17:14:46.810902591 +1300 @@ -0,0 +1,56 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/* + * Added support for Cisco Enterprise IP phones, for more + * information see https://usecallmanager.nz + * + * Gareth Palmer + */ + +#ifndef _SIP_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) list; + int type; +}; + +/* Structure to save a route hop */ +struct sip_route_hop { + AST_LIST_ENTRY(sip_route_hop) next; + char uri[0]; +}; + +const char *sip_route_add(struct sip_route *route, const char *uri, size_t len, int inserthead); +void sip_route_parse(struct sip_route *route, const char *header, int inserthead); +void sip_route_copy(struct sip_route *dst, const struct sip_route *src); +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 formatcli, 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.2.0.orig/channels/sip/include/rtp_glue.h asterisk-22.2.0/channels/sip/include/rtp_glue.h --- asterisk-22.2.0.orig/channels/sip/include/rtp_glue.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/rtp_glue.h 2025-02-18 17:14:46.811902565 +1300 @@ -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 *chan, struct ast_format_cap *result); +int sip_rtp_update_peer(struct ast_channel *chan, struct ast_rtp_instance *instance, struct ast_rtp_instance *vinstance, + struct ast_rtp_instance *tinstance, const struct ast_format_cap *cap, int nat_active); +enum ast_rtp_glue_result sip_rtp_get_info(struct ast_channel *chan, struct ast_rtp_instance **instance); +enum ast_rtp_glue_result sip_vrtp_get_info(struct ast_channel *chan, struct ast_rtp_instance **instance); +enum ast_rtp_glue_result sip_trtp_get_info(struct ast_channel *chan, struct ast_rtp_instance **instance); +int sip_rtp_allow_remote(struct ast_channel *chan1, struct ast_rtp_instance *instance); +int sip_vrtp_allow_remote(struct ast_channel *chan1, struct ast_rtp_instance *instance); +int sip_rtp_allow_any_remote(struct ast_channel *chan, struct ast_rtp_instance *instance, const char *rtptype); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/sdp.h asterisk-22.2.0/channels/sip/include/sdp.h --- asterisk-22.2.0.orig/channels/sip/include/sdp.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/sdp.h 2025-02-18 17:14:46.811902565 +1300 @@ -0,0 +1,50 @@ +/* + * 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 + +/* Media types generate different "dummy answers" for not accepting the offer of a media stream. We need to add definitions for each RTP + * profile. Secure RTP is not the same as normal RTP and will require a new definition */ +enum { + SIP_SDP_UNKNOWN = 0, /* Unknown media type */ + SIP_SDP_AUDIO, /* RTP/AVP Audio */ + SIP_SDP_VIDEO, /* RTP/AVP Video */ + SIP_SDP_IMAGE, /* Image udptl, not TCP or RTP */ + SIP_SDP_TEXT, /* RTP/AVP Realtime Text */ +}; + +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 */ +}; + +/* Forward declarations */ +struct sip_pvt; +struct sip_request; + +int sip_sdp_parse(struct sip_pvt *pvt, struct sip_request *req, int t38action, int is_offer); +int sip_sdp_generate(struct sip_pvt *pvt, struct sip_request *resp, int oldsdp, int add_audio, int add_t38); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/security_events.h asterisk-22.2.0/channels/sip/include/security_events.h --- asterisk-22.2.0.orig/channels/sip/include/security_events.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/security_events.h 2025-02-18 17:14:46.811902565 +1300 @@ -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_pvt; +struct sip_request; + +void sip_report_inval_acct(const struct sip_pvt *pvt); +void sip_report_failed_acl(const struct sip_pvt *pvt, const char *aclname); +void sip_report_inval_password(const struct sip_pvt *pvt, const char *responsechallenge, const char *responsehash); +void sip_report_auth_success(const struct sip_pvt *pvt, uint32_t using_password); +void sip_report_session_limit(const struct sip_pvt *pvt); +void sip_report_chal_resp_failed(const struct sip_pvt *pvt, const char *response, const char *expected_response); +void sip_report_chal_sent(const struct sip_pvt *pvt); +void sip_report_inval_transport(const struct sip_pvt *pvt, const char *transport); + +int sip_report_security_event(const char *peer, struct ast_sockaddr *addr, const struct sip_pvt *pvt, const struct sip_request *req, const int res); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/sip.h asterisk-22.2.0/channels/sip/include/sip.h --- asterisk-22.2.0.orig/channels/sip/include/sip.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/sip.h 2025-02-18 17:14:46.812902538 +1300 @@ -0,0 +1,211 @@ +/* + * 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 + +#define SIP_BUFFER_SIZE 512 /* Buffer size for many operations */ + +/* 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 + +/* Various flags for the flags field in the pvt structure Trying to sort these up (one or more of the following): D: Dialog (pvt), + * 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_NEEDREINVITE = 1 << 3, /* D: Do we need to send another reinvite? */ + SIP_PENDINGBYE = 1 << 4, /* D: Need to send bye after we ack? */ + SIP_GOTREFER = 1 << 5, /* D: Got a refer? */ + SIP_CALL_LIMIT = 1 << 6, /* D: Call limit enforced for this call */ + SIP_INC_COUNT = 1 << 7, /* D: Did this dialog increment the counter of in-use calls? */ + SIP_INC_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_PROMISCREDIR = 1 << 10, /* DP: Promiscuous redirection */ + SIP_USEREQPHONE = 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 */ + SIP_REINVITE = SIP_REINVITE_NONE | SIP_DIRECT_MEDIA | SIP_DIRECT_MEDIA_NAT | SIP_REINVITE_UPDATE, /* DP: four settings, uses three bits */ + SIP_PROG_INBAND_NO = 0, + SIP_PROG_INBAND_NEVER = 1 << 22, + SIP_PROG_INBAND_YES = 1 << 23, + SIP_PROG_INBAND = SIP_PROG_INBAND_NO | SIP_PROG_INBAND_NEVER | SIP_PROG_INBAND_YES, /* DP: three settings, uses two bits */ + SIP_USEPATH = 1 << 24, /* GDP: Trust and use incoming Path headers? */ + SIP_TRUSTRPID = 1 << 25, /* DP: Trust RPID headers? */ + SIP_SENDRPID_NO = 0, + SIP_SENDRPID_RPID = 1 << 26, /* Use "Remote-Party-ID" for rpid */ + SIP_SENDRPID_PAI = 1 << 27, /* Use "P-Asserted-Identity" for rpid */ + SIP_SENDRPID = SIP_SENDRPID_NO | SIP_SENDRPID_RPID | SIP_SENDRPID_PAI, /* DP: Remote Party-ID Support */ + SIP_G726_NONSTANDARD = 1 << 28, /* DP: Use non-standard packing for G726-32 data */ + SIP_RFC2833_COMPENSATE = 1 << 29, /* DP: Compensate for buggy RFC2833 implementations */ + SIP_USE_SRTP = 1 << 30, /* DP: Whether we should offer only SRTP */ + + SIP_FLAGS0_TO_COPY = SIP_PROMISCREDIR | SIP_TRUSTRPID | SIP_SENDRPID | SIP_DTMF | SIP_REINVITE | SIP_PROG_INBAND | SIP_NAT_FORCE_RPORT | + SIP_G726_NONSTANDARD | SIP_USE_SRTP | SIP_USEREQPHONE | SIP_USEPATH | SIP_RFC2833_COMPENSATE +}; + +/* flags[1]: */ +enum { + SIP_RTCACHEFRIENDS = 1 << 0, /* GP: Should we keep RT objects in memory for extended time? */ + SIP_RTAUTOCLEAR = 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_SYMMETRICRTP = 1 << 3, /* GDP: Whether symmetric RTP is enabled or not */ + SIP_STATECHANGEQUEUE = 1 << 4, /* D: Unsent state pending change exists */ + SIP_CONNECTLINEUPDATE_PEND = 1 << 5, + SIP_RPORT_PRESENT = 1 << 6, /* Was rport received in the Via header? */ + SIP_PREFERRED_CODEC = 1 << 7, /* GDP: Only respond with single most preferred joint codec */ + SIP_VIDEOSUPPORT = 1 << 8, /* DP: Video supported if offered? */ + SIP_TEXTSUPPORT = 1 << 9, /* GDP: Global text enable */ + SIP_ALLOWSUBSCRIBE = 1 << 10, /* GP: Allow subscriptions from this peer? */ + SIP_ALLOWOVERLAP_NO = 0, /* No, terminate with 404 Not found */ + SIP_ALLOWOVERLAP_YES = 1 << 11, /* Yes, using the 484 Address Incomplete response */ + SIP_ALLOWOVERLAP_DTMF = 1 << 12, /* Yes, using the DTMF transmission through Early Media */ + SIP_ALLOWOVERLAP = SIP_ALLOWOVERLAP_NO | SIP_ALLOWOVERLAP_YES | SIP_ALLOWOVERLAP_DTMF, /* DP: Allow overlap dialing ? */ + SIP_SUBSCRIBEMWIONLY = 1 << 13, /* GP: Only issue MWI notification if subscribed to */ + SIP_IGNORESDPVERSION = 1 << 14, /* GDP: Ignore the SDP session version number we receive and treat all sessions as new */ + SIP_T38SUPPORT_UDPTL = 1 << 15, /* GDP: T.38 Fax Support (no error correction, */ + SIP_T38SUPPORT_UDPTL_FEC = 1 << 16, /* GDP: T.38 Fax Support (FEC error correction, */ + SIP_T38SUPPORT_UDPTL_REDUNDANCY = 1 << 17, /* GDP: T.38 Fax Support (redundancy error correction, */ + SIP_T38SUPPORT = SIP_T38SUPPORT_UDPTL | SIP_T38SUPPORT_UDPTL_FEC | SIP_T38SUPPORT_UDPTL_REDUNDANCY, /* GDP: T.38 Fax Support */ + SIP_CALL_ONHOLD_ACTIVE = 1 << 18, /* D: Active hold */ + SIP_CALL_ONHOLD_ONEDIR = 1 << 19, /* D: One directional hold */ + SIP_CALL_ONHOLD_INACTIVE = 1 << 20, /* D: Inactive hold */ + SIP_CALL_ONHOLD = SIP_CALL_ONHOLD_ACTIVE | SIP_CALL_ONHOLD_ONEDIR | SIP_CALL_ONHOLD_INACTIVE, /* D: Call hold states: */ + SIP_CISCO_USECALLMANAGER = 1 << 21, /* DP: Enable Cisco USECALLMANAGER phone support */ + SIP_DIALOG_ESTABLISHED = 1 << 22, /* 29: 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_VIDEOSUPPORT_ALWAYS = 1 << 26, /* DP: Always set up video, even if endpoints don't support it */ + SIP_HAVEPEERCONTEXT = 1 << 27, /*< Are we associated with a configured peer context? */ + SIP_TRUST_ID_OUTBOUND_NO = 1 << 28, /* No, 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 */ + SIP_TRUST_ID_OUTBOUND = SIP_TRUST_ID_OUTBOUND_NO | SIP_TRUST_ID_OUTBOUND_YES, /* DP: Do we trust the peer with private presence information? */ + SIP_TRANSFER_RESPONSE = 1 << 30, /* D: Send specific transfer-response for call */ + SIP_DIRECT_MEDIA_OUTGOING = 1 << 31, /* DP: Only send direct media reinvites on outgoing calls */ + + SIP_FLAGS1_TO_COPY = SIP_ALLOWSUBSCRIBE | SIP_ALLOWOVERLAP | SIP_IGNORESDPVERSION | SIP_VIDEOSUPPORT | SIP_T38SUPPORT | SIP_TEXTSUPPORT | + SIP_CISCO_USECALLMANAGER | SIP_FAX_DETECT | SIP_UDPTL_DESTINATION | SIP_VIDEOSUPPORT_ALWAYS | SIP_PREFERRED_CODEC | SIP_SYMMETRICRTP | + SIP_DIRECT_MEDIA_OUTGOING | SIP_Q850_REASON | SIP_HAVEPEERCONTEXT | SIP_TRUST_ID_OUTBOUND +}; + +/* 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_SYMMETRICRTP 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_PREFCAPS = 1 << 5, /* DP: Ignore prefcaps when setting up an outgoing call leg */ + SIP_DISCARD_REMOTE_HOLD_RETRIEVAL = 1 << 6, /* DGP: Stop telling the peer to start music on hold */ + SIP_FORCE_AVP = 1 << 7, /* DGP: Force 'RTP/AVP' for all streams, even DTLS */ + SIP_RTCP_MUX = 1 << 8, /* DGP: Attempt to negotiate RFC 5761 RTCP multiplexing */ + SIP_DND_BUSY = 1 << 9, /* DPG: Treat endpoint as busy when DND is enabled */ + SIP_SUBSCRIPTIONSTATE_ACTIVE = 1 << 10, /* D: Force Subscription-State to be active for NOTIFYs */ + SIP_CISCO_KEEP_CONFERENCE = 1 << 11, /* DGP: Keep ad-hoc conference after initiator hangs up */ + SIP_CISCO_MULTIADMIN_CONFERENCE = 1 << 12, /* DGP: Allow participants to administrate conference */ + SIP_RELAY_NEAREND = 1 << 13, /* D: Add x-relay-nearend attribute to SDP */ + SIP_RELAY_FAREND = 1 << 14, /* D: Add x-relay-farend attribute to SDP */ + SIP_SDP_ACK = 1 << 15, /* D: Add SDP to ACK */ + SIP_CISCO_RECORDING = 1 << 16, /* D: Call is now being recorded */ + SIP_RTP_STATS_ON_BYE = 1 << 17, /* D: Display RTP stats on BYE */ + SIP_HUNTGROUP_DEFAULT = 1 << 18, /* GP: If peer is logged into huntgroup by default */ + SIP_CISCO_PICKUPNOTIFY_FROM = 1 << 19, /* P: Include the from number in the statusline for pickup notify */ + SIP_CISCO_PICKUPNOTIFY_TO = 1 << 20, /* P: Include the to number in the statusline for pickup notify */ + SIP_CISCO_PICKUPNOTIFY_BEEP = 1 << 21, /* P: Play a beep for pickup notify */ + + SIP_FLAGS2_TO_COPY = SIP_SRTP_TAG_32 | SIP_NAT_AUTO_RPORT | SIP_NAT_AUTO_COMEDIA | SIP_USE_AVPF | SIP_ICE_SUPPORT | SIP_IGNORE_PREFCAPS | + SIP_DISCARD_REMOTE_HOLD_RETRIEVAL | SIP_RTCP_MUX | SIP_DND_BUSY | SIP_CISCO_KEEP_CONFERENCE | SIP_CISCO_MULTIADMIN_CONFERENCE | + SIP_HUNTGROUP_DEFAULT | SIP_CISCO_PICKUPNOTIFY_FROM | SIP_CISCO_PICKUPNOTIFY_TO | SIP_CISCO_PICKUPNOTIFY_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_pvt; +struct sip_registry; +struct sip_mwi_subscription; + +/* Generic data for ast_sched_add callbacks */ +struct sip_sched_data { + union { + struct sip_pvt *pvt; + struct sip_registry *registry; + struct sip_mwi_subscription *mwi; + }; + int ms; +}; + +extern int sip_log_level; +extern struct ast_sockaddr sip_debugaddr; +extern int sip_debug; + +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_debug_test_addr(const struct ast_sockaddr *addr); +int sip_debug_test_pvt(struct sip_pvt *pvt); + +int sip_monitor_restart(void); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/stimer.h asterisk-22.2.0/channels/sip/include/stimer.h --- asterisk-22.2.0.orig/channels/sip/include/stimer.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/stimer.h 2025-02-18 17:14:46.812902538 +1300 @@ -0,0 +1,79 @@ +/* + * 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_US, /* Initially prefer session refresh by Asterisk */ + SIP_STIMER_REFRESHER_THEM, /* Initially prefer session refresh by the other side */ +}; + +/* Forward declarations */ +struct sip_pvt; +struct sip_request; + +/* Structure that encapsulates all attributes related to running Session-Timers feature on a per dialog basis */ +struct sip_stimer { + int active; /* Session-Timers on/off */ + int interval; /* Session-Timers negotiated session refresh interval */ + int refresher; /* Session-Timers cached refresher */ + int schedid; /* Session-Timers ast_sched scheduler id */ + int peer_active; /* Session-Timers on/off in peer UA */ + 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 */ +}; + +/* Structure that encapsulates all attributes related to configuration of Session-Timers feature on a per user/peer basis. */ +struct sip_stimer_config { + int mode; /* Mode of operation for Session-Timers */ + int refresher; /* Session-Timer refresher */ + int min_se; /* Lowest threshold for session refresh interval */ + int max_se; /* Highest threshold for session refresh interval */ +}; + +const char *sip_stimer_mode2str(int mode); +const char *sip_stimer_refresher2str(int refresher); +int sip_stimer_handle_invite(struct sip_pvt *pvt, struct sip_request *req, int reinvite); +void sip_stimer_start(struct sip_pvt *pvt); +void sip_stimer_stop(struct sip_pvt *pvt); +int __sip_stimer_stop(const void *data); +void sip_stimer_restart(struct sip_pvt *pvt); +int sip_stimer_timeout(const void *data); +int sip_stimer_get_expiry(struct sip_pvt *pvt, int max); +int sip_stimer_get_refresher(struct sip_pvt *pvt); +int sip_stimer_get_mode(struct sip_pvt *pvt, int no_cached); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/include/utils.h asterisk-22.2.0/channels/sip/include/utils.h --- asterisk-22.2.0.orig/channels/sip/include/utils.h 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/include/utils.h 2025-02-18 17:14:46.813902511 +1300 @@ -0,0 +1,215 @@ +/* + * 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_RESPONSE = 0, /* Not request, response to outbound request */ + SIP_REGISTER, /* Registration to the mothership, tell us where you are located */ + SIP_OPTIONS, /* Check capabilities of a device, used for "ping" too */ + SIP_NOTIFY, /* Status update, Part of the event package standard, result of a SUBSCRIBE or a REFER */ + SIP_INVITE, /* Set up a session */ + SIP_ACK, /* End of a three-way handshake started with INVITE. */ + SIP_PRACK, /* Reliable pre-call signalling. Not supported in Asterisk. */ + SIP_BYE, /* End of a session */ + SIP_REFER, /* Refer to another URI (transfer) */ + SIP_SUBSCRIBE, /* Subscribe for updates (voicemail, session status, device status, presence) */ + SIP_MESSAGE, /* Text messaging */ + SIP_UPDATE, /* Update a dialog. We can send UPDATE; but not accept it */ + SIP_INFO, /* Information updates during a session */ + SIP_CANCEL, /* Cancel an INVITE */ + SIP_PUBLISH, /* Presence state */ + SIP_PING, /* Not supported at all, no standard but still implemented out there */ + SIP_UNKNOWN, /* Unknown response */ +}; + +/* Define SIP option tags, used in Require: and Supported: headers. We need to be aware of these properties in the phones to use + * the replace: 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_OPT_REPLACES = (1 << 0), + SIP_OPT_100REL = (1 << 1), + SIP_OPT_TIMER = (1 << 2), + SIP_OPT_EARLY_SESSION = (1 << 3), + SIP_OPT_JOIN = (1 << 4), + SIP_OPT_PATH = (1 << 5), + SIP_OPT_PREF = (1 << 6), + SIP_OPT_PRECONDITION = (1 << 7), + SIP_OPT_PRIVACY = (1 << 8), + SIP_OPT_SDP_ANAT = (1 << 9), + SIP_OPT_SEC_AGREE = (1 << 10), + SIP_OPT_EVENTLIST = (1 << 11), + SIP_OPT_GRUU = (1 << 12), + SIP_OPT_TARGET_DIALOG = (1 << 13), + SIP_OPT_NOREFERSUB = (1 << 14), + SIP_OPT_HISTINFO = (1 << 15), + SIP_OPT_RESPRIORITY = (1 << 16), + SIP_OPT_FROMCHANGE = (1 << 17), + SIP_OPT_RECLISTINV = (1 << 18), + SIP_OPT_RECLISTSUB = (1 << 19), + SIP_OPT_OUTBOUND = (1 << 20), + SIP_OPT_UNKNOWN = (1 << 21), +}; + +enum { + SIP_DIGEST_RESPONSE = 0, + SIP_DIGEST_URI, + SIP_DIGEST_USERNAME, + SIP_DIGEST_NONCE, + SIP_DIGEST_OPAQUE, + SIP_DIGEST_REALM, + SIP_DIGEST_DOMAIN, + SIP_DIGEST_QOP, + SIP_DIGEST_LAST, +}; + +/* Authentication types - proxy or www authentication. Endpoints, like Asterisk, should always use WWW authentication to allow multiple + * authentications in the same call - to the proxy and to the end point. */ +enum { + SIP_WWW_AUTH = 401, + SIP_PROXY_AUTH = 407, +}; + +/* Results from the parse_register() function */ +enum { + SIP_PARSE_REGISTER_DENIED = 0, + SIP_PARSE_REGISTER_FAILED, + SIP_PARSE_REGISTER_UPDATE, + SIP_PARSE_REGISTER_QUERY, +}; + +/* Forward declarations */ +struct sip_peer; +struct sip_pvt; + +struct sip_methods { + int id; + int need_rtp; /* when this is the 'primary' use for a pvt structure, does it need RTP? */ + const char *name; + int can_create; +}; + +/* List of well-known SIP options. If we get this in a require, we should check the list and answer accordingly. */ +struct sip_options { + int id; /* Bitmap ID */ + int supported; /* Supported by Asterisk ? */ + const char *name; /* Name, as in standard */ +}; + +struct sip_digest_keys { + const char *tag; + const char *value; +}; + +/* Structure to store Via information */ +struct sip_via { + char *via; + const char *protocol; + const char *sent_by; + const char *branch; + const char *maddr; + unsigned int port; + unsigned char ttl; +}; + +extern const struct sip_options sip_options[]; +extern const struct sip_methods sip_methods[]; + +int sip_is_token(const char *str); + +int sip_uri_parse(char *uri, const char *schemes, char **user, char **password, char **hostport, char **transport); +char *sip_uri_terminate(char *uri); +char *sip_uri_remove_parameters(char *uri); +int sip_uri_cmp(const char *input1, const char *input2); + +const char *sip_get_name(const char *input, char *output, size_t outputsize); +int sip_get_name_and_number(const char *header, char **name, char **number); +int sip_get_domain(const char *header, char *domain, int len); +char *sip_get_in_brackets(char *parse); +const char *sip_find_quote(const char *start, const char *end); + +int sip_method_cmp(int id, const char *name); +int sip_method_find(const char *msg); +void sip_method_set_allowed(unsigned int *allowed_methods, int method); +void sip_method_set_disallowed(unsigned int *allowed_methods, int method); +int sip_method_allowed(unsigned int *allowed_methods, int method); +void sip_method_parse(unsigned int *methods, const char *header); + +void sip_auth_headers(int code, char **reqheader, char **respheader); +void sip_compress_whitespace(struct ast_str *data); +void sip_pedantic_decode(char *str); +char *sip_generate_random_string(char *buf, size_t size); + +struct sip_via *sip_via_parse(const char *header); +void sip_via_free(struct sip_via *via); + +unsigned int sip_str2port(const char *value, unsigned int standard); +int sip_standard_port(enum ast_transport type, int port); +const char *sip_sanitized_host(const char *host); + +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_invitestate2str(int state); +const char *sip_allowoverlap2str(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_force_rport2str(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); +int sip_transport_family(unsigned int transport); + +const char *sip_srv_protocol(enum ast_transport transport); +const char *sip_srv_service(enum ast_transport transport); + +void sip_parse_digest(char *authresp, struct sip_digest_keys *keys); +unsigned int sip_parse_options(const char *options, char *unsupported, size_t unsupported_len); +int sip_parse_contact(const char *fullcontact, struct ast_sockaddr *addr); +int sip_parse_register_contact(struct sip_pvt *pvt, struct sip_peer *peer, struct sip_request *req, int *addrchanged); +int sip_parse_ok_contact(struct sip_pvt *pvt, struct sip_request *req); +void sip_parse_redirect_contact(struct sip_pvt *pvt, struct sip_request *req, char **name, char **number, int set_call_forward); +unsigned int sip_parse_allow(struct sip_request *req); +int sip_parse_reason(struct sip_pvt *pvt, struct sip_request *req); +int sip_parse_path(struct sip_pvt *pvt, struct sip_peer *peer, struct sip_request *req, const char *pathbuf); +int sip_parse_rpid(struct sip_pvt *pvt, struct sip_request *req); +int sip_parse_diversion(struct sip_pvt *pvt, struct sip_request *req, char **name, char **number, int *code, char **reason); +int sip_parse_refer(struct sip_pvt *transfer_pvt, struct sip_request *outgoing_req); +void sip_parse_oli(struct sip_request *req, struct ast_channel *chan); +int sip_parse_min_se(const char *min_se, int *interval); +int sip_parse_session_expires(const char *header, int *interval, int *refresher); +void sip_parse_rtp_stats(struct sip_pvt *pvt, struct sip_request *req); + +void sip_queue_hangup_cause(struct sip_pvt *pvt, int cause); +struct ast_channel *sip_find_ringing_channel(struct ao2_container *device_state_info); + +#endif diff -durN asterisk-22.2.0.orig/channels/sip/manager.c asterisk-22.2.0/channels/sip/manager.c --- asterisk-22.2.0.orig/channels/sip/manager.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/manager.c 2025-02-18 17:14:46.813902511 +1300 @@ -0,0 +1,778 @@ +/* + * 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/pvt.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 *name; + struct sip_peer *peer; + + name = astman_get_header(message, "Peer"); + + if (ast_strlen_zero(name)) { + astman_send_error(session, message, "Peer: missing."); + return 0; + } + + if ((peer = sip_peer_find(name, NULL, FALSE, FALSE, 0))) { + const char *id = astman_get_header(message, "ActionID"); + + astman_send_ack(session, message, "SIP peer found - will qualify"); + sip_peer_qualify(peer, TRUE); + sip_publish_qualify_peer(id, name); + + ao2_t_cleanup(peer, "qualify: done with 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) +{ + const char *id = astman_get_header(message, "ActionID"); + char idtext[256] = ""; + int total = 0; + struct ao2_iterator iter; + struct sip_registry *registry; + + if (!ast_strlen_zero(id)) { + snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id); + } + + astman_send_listack(session, message, "Registrations will follow", "start"); + iter = ao2_iterator_init(sip_registry, 0); + + while ((registry = ao2_t_iterator_next(&iter, "manager_show_registry iter"))) { + 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", + idtext, + registry->hostname, + registry->port ? registry->port : SIP_STANDARD_PORT, + registry->username, + S_OR(registry->domain, registry->hostname), + registry->domainport ? registry->domainport : SIP_STANDARD_PORT, + registry->refresh, + sip_registry_state2str(registry->state), + (long) registry->time.tv_sec); + + ao2_unlock(registry); + ao2_t_ref(registry, -1, "manager_show_registry iter"); + + total++; + } + + ao2_iterator_destroy(&iter); + + astman_send_list_complete_start(session, message, "RegistrationsComplete", total); + astman_send_list_complete_end(session); + + return 0; +} + +int sip_manager_notify(struct mansession *session, const struct message *message) +{ + const char *channame = astman_get_header(message, "Channel"); + struct ast_variable *vars = astman_get_variables_order(message, ORDER_NATURAL); + const char *callid = astman_get_header(message, "Call-ID"); + struct sip_pvt *pvt; + struct ast_variable *header, *var; + + if (ast_strlen_zero(channame)) { + astman_send_error(session, message, "SIPNotify requires a channel name"); + ast_variables_destroy(vars); + return 0; + } + + if (!strncasecmp(channame, "sip/", 4)) { + channame += 4; + } + + /* check if Call-ID header is set */ + if (!ast_strlen_zero(callid)) { + struct sip_pvt tmp_pvt = {.callid = callid}; + + if (!(pvt = ao2_find(sip_pvts, &tmp_pvt, OBJ_SEARCH_OBJECT))) { + astman_send_error(session, message, "Call-ID not found"); + ast_variables_destroy(vars); + + return 0; + } + + if (!pvt->notify) { + sip_notify_alloc(pvt); + } else { + ast_variables_destroy(pvt->notify->headers); + } + } else { + if (!(pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_NOTIFY, NULL, 0))) { + astman_send_error(session, message, "Unable to build sip pvt data for notify (memory/socket error)"); + ast_variables_destroy(vars); + + return 0; + } + + if (sip_pvt_build(pvt, channame, NULL, TRUE)) { + /* Maybe they're not registered, etc. */ + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "unref dialog inside for loop" ); + + /* sip_destroy(p); */ + astman_send_error(session, message, "Could not create address"); + ast_variables_destroy(vars); + + return 0; + } + + /* Notify is outgoing call */ + ast_set_flag(&pvt->flags[0], SIP_OUTGOING); + sip_notify_alloc(pvt); + + } + + pvt->notify->headers = header = ast_variable_new("Subscription-State", "terminated", ""); + + for (var = vars; var; var = var->next) { + if (!strcasecmp(var->name, "Content")) { + if (ast_str_strlen(pvt->notify->content)) { + ast_str_append(&pvt->notify->content, 0, "\r\n"); + } + + ast_str_append(&pvt->notify->content, 0, "%s", var->value); + } else if (!strcasecmp(var->name, "Content-Length")) { + ast_log(LOG_WARNING, "it is not necessary to specify Content-Length, ignoring\n"); + } else { + header->next = ast_variable_new(var->name, var->value, ""); + header = header->next; + } + } + + if (ast_strlen_zero(callid)) { + /* Now that we have the peer's address, set our ip and change callid */ + sip_pvt_set_ouraddrfor(pvt, &pvt->sa, &pvt->ourip); + sip_pvt_build_via(pvt); + sip_pvt_change_callid(pvt, NULL); + + sip_pvt_sched_destroy(pvt, SIP_TIMEOUT); + sip_send_invite(pvt, SIP_NOTIFY, FALSE, SIP_INIT_REQUEST, NULL); + } else { + sip_pvt_sched_destroy(pvt, SIP_TIMEOUT); + sip_send_invite(pvt, SIP_NOTIFY, FALSE, SIP_INIT_BRANCH, NULL); + } + + ao2_t_cleanup(pvt, "bump down the count of pvt since we're done with it."); + + astman_send_ack(session, message, "Notify Sent"); + ast_variables_destroy(vars); + + return 0; +} + +/* Show SIP peers in the manager API */ +int sip_manager_peers(struct mansession *session, const struct message *message) +{ + const char *id = astman_get_header(message, "ActionID"); + char idtext[256] = ""; + struct sip_peer *peer; + struct ao2_iterator iter; + int total = 0; + int realtimepeers; + char status[30]; + + if (!ast_strlen_zero(id)) { + snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id); + } + + realtimepeers = ast_check_realtime("sippeers"); + astman_send_listack(session, message, "Peer status list will follow", "start"); + + iter = ao2_iterator_init(sip_peers, 0); + + while ((peer = ao2_t_iterator_next(&iter, "iterate thru peers table"))) { + sip_peer_get_status(peer, status, sizeof(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", + idtext, + peer->name, + !ast_sockaddr_isnull(&peer->addr) ? ast_sockaddr_stringify_addr(&peer->addr) : "", + !ast_sockaddr_isnull(&peer->addr) ? ast_sockaddr_stringify_port(&peer->addr) : "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_SYMMETRICRTP) ? "yes" : "no", + ast_test_flag(&peer->flags[1], SIP_VIDEOSUPPORT) ? "yes" : "no", /* VIDEOSUPPORT=yes? */ + ast_test_flag(&peer->flags[1], SIP_TEXTSUPPORT) ? "yes" : "no", /* TEXTSUPPORT=yes? */ + ast_acl_list_is_empty(peer->acl) ? "no" : "yes", /* permit/deny/acl */ + status, + realtimepeers ? (peer->is_realtime ? "yes" : "no") : "no", + peer->description, + peer->accountcode); + + ao2_unlock(peer); + ao2_t_cleanup(peer, "toss iterator peer ptr"); + + total++; + } + + ao2_iterator_destroy(&iter); + + /* Send final confirmation */ + astman_send_list_complete_start(session, message, "PeerListComplete", total); + 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 *id = astman_get_header(message, "ActionID"); + const char *name; + char status[30]; + struct sip_peer *peer; + struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + struct ast_variable *var; + struct sip_alias *alias; + struct sip_subscription *subscription; + char callerid[80]; + char groups[256]; + struct ast_str *named_groups = ast_str_alloca(512); + struct ast_str *mailboxes = ast_str_alloca(512); + + name = astman_get_header(message, "Peer"); + + if (ast_strlen_zero(name)) { + astman_send_error(session, message, "Peer: missing."); + return 0; + } + + if (!(peer = sip_peer_find(name, NULL, FALSE, FALSE, 0))) { + astman_send_error(session, message, "Peer not found"); + return 0; + } + + astman_append(session, "Response: Success\r\n"); + + if (!ast_strlen_zero(id)) { + astman_append(session, "ActionID: %s\r\n", 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->remotesecret) ? "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->subscribecontext)) { + astman_append(session, "SubscribeContext: %s\r\n", peer->subscribecontext); + } + + astman_append(session, "Language: %s\r\n", peer->language); + astman_append(session, "ToneZone: %s\r\n", !ast_strlen_zero(peer->zone) ? 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, "CID-CallingPres: %s\r\n", ast_describe_caller_presentation(peer->callingpres)); + + if (!ast_strlen_zero(peer->fromuser)) { + astman_append(session, "SIP-FromUser: %s\r\n", peer->fromuser); + } + + if (!ast_strlen_zero(peer->fromdomain)) { + astman_append(session, "SIP-FromDomain: %s\r\n", peer->fromdomain); + astman_append(session, "Sip-FromDomain-Port: %d\r\n", peer->fromdomainport ? peer->fromdomainport : 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)); + + 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)); + + ast_str_reset(named_groups); + + astman_append(session, "MOHSuggest: %s\r\n", peer->mohsuggest); + + sip_peer_get_mailboxes(peer, &mailboxes); + astman_append(session, "VoiceMailbox: %s\r\n", ast_str_buffer(mailboxes)); + + astman_append(session, "TransferMode: %s\r\n", peer->allowtransfer ? "yes" : "no"); + astman_append(session, "LastMsgsSent: %d/%d\r\n", peer->newmsgs, peer->oldmsgs); + astman_append(session, "Maxforwards: %d\r\n", peer->maxforwards); + astman_append(session, "Call-limit: %d\r\n", peer->call_limit); + astman_append(session, "Busy-level: %d\r\n", peer->busy_level); + astman_append(session, "MaxCallBR: %d kbps\r\n", peer->maxcallbitrate); + astman_append(session, "Dynamic: %s\r\n", peer->host_dynamic?"Y":"N"); + astman_append(session, "Callerid: %s\r\n", ast_callerid_merge(callerid, sizeof(callerid), peer->cid_name, peer->cid_num, "")); + astman_append(session, "RegExpire: %ld seconds\r\n", ast_sched_when(sip_sched_context, peer->expire)); + + astman_append(session, "SIP-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, "SIP-Comedia: %s\r\n", + ast_test_flag(&peer->flags[2], SIP_NAT_AUTO_COMEDIA) ? + (ast_test_flag(&peer->flags[1], SIP_SYMMETRICRTP) ? "yes,auto" : "no,auto") : + (ast_test_flag(&peer->flags[1], SIP_SYMMETRICRTP) ? "yes" : "no")); + + astman_append(session, "ACL: %s\r\n", !ast_acl_list_is_empty(peer->acl) ? "yes" : "no"); + astman_append(session, "SIP-CanReinvite: %s\r\n", ast_test_flag(&peer->flags[0], SIP_DIRECT_MEDIA) ? "yes" : "no"); + astman_append(session, "SIP-DirectMedia: %s\r\n", ast_test_flag(&peer->flags[0], SIP_DIRECT_MEDIA) ? "yes" : "no"); + astman_append(session, "SIP-PromiscRedir: %s\r\n", ast_test_flag(&peer->flags[0], SIP_PROMISCREDIR) ? "yes" : "no"); + astman_append(session, "SIP-UserPhone: %s\r\n", ast_test_flag(&peer->flags[0], SIP_USEREQPHONE) ? "yes" : "no"); + astman_append(session, "SIP-VideoSupport: %s\r\n", ast_test_flag(&peer->flags[1], SIP_VIDEOSUPPORT) ? "yes" : "no"); + astman_append(session, "SIP-TextSupport: %s\r\n", ast_test_flag(&peer->flags[1], SIP_TEXTSUPPORT) ? "yes" : "no"); + astman_append(session, "SIP-T.38Support: %s\r\n", ast_test_flag(&peer->flags[1], SIP_T38SUPPORT) ? "yes" : "no"); + astman_append(session, "SIP-T.38EC: %s\r\n", sip_t38_ecmode2str(ast_test_flag(&peer->flags[1], SIP_T38SUPPORT))); + astman_append(session, "SIP-T.38MaxDtgrm: %u\r\n", peer->t38_maxdatagram); + astman_append(session, "SIP-Sess-Timers: %s\r\n", sip_stimer_mode2str(peer->stimer.mode)); + astman_append(session, "SIP-Sess-Refresh: %s\r\n", sip_stimer_refresher2str(peer->stimer.refresher)); + astman_append(session, "SIP-Sess-Expires: %d\r\n", peer->stimer.max_se); + astman_append(session, "SIP-Sess-Min: %d\r\n", peer->stimer.min_se); + astman_append(session, "SIP-RTP-Engine: %s\r\n", peer->engine); + astman_append(session, "SIP-Encryption: %s\r\n", ast_test_flag(&peer->flags[0], SIP_USE_SRTP) ? "yes" : "no"); + astman_append(session, "SIP-RTCP-Mux: %s\r\n", ast_test_flag(&peer->flags[2], SIP_RTCP_MUX) ? "yes" : "no"); + astman_append(session, "SIP-DTMFmode: %s\r\n", sip_dtmf_mode2str(ast_test_flag(&peer->flags[0], SIP_DTMF))); + astman_append(session, "ToHost: %s\r\n", peer->tohost); + astman_append(session, "Address-IP: %s\r\n", ast_sockaddr_stringify_addr(&peer->addr)); + astman_append(session, "Address-Port: %d\r\n", ast_sockaddr_port(&peer->addr)); + astman_append(session, "Default-addr-IP: %s\r\n", ast_sockaddr_stringify_addr(&peer->defaddr)); + astman_append(session, "Default-addr-port: %d\r\n", ast_sockaddr_port(&peer->defaddr)); + astman_append(session, "Default-Username: %s\r\n", peer->username); + + if (!ast_strlen_zero(sip_config.regcontext)) { + astman_append(session, "RegExtension: %s\r\n", peer->regexten); + } + + astman_append(session, "Codecs: %s\r\n", ast_format_cap_get_names(peer->caps, &codec_buf)); + + sip_peer_get_status(peer, status, sizeof(status)); + + astman_append(session, "Status: %s\r\n", status); + astman_append(session, "SIP-Useragent: %s\r\n", peer->useragent); + astman_append(session, "Reg-Contact: %s\r\n", peer->fullcontact); + astman_append(session, "QualifyFreq: %d ms\r\n", peer->qualifyfreq); + astman_append(session, "Parkinglot: %s\r\n", peer->parkinglot); + + if (peer->chanvars) { + for (var = peer->chanvars; var; var = var->next) { + astman_append(session, "ChanVariable: %s=%s\r\n", var->name, var->value); + } + } + + astman_append(session, "SIP-Use-Reason-Header: %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->donotdisturb ? "yes" : "no"); + astman_append(session, "CallForward: %s\r\n", peer->callforward); + astman_append(session, "HuntGroup: %s\r\n", peer->huntgroup ? "yes" : "no"); + astman_append(session, "CiscoDeviceName: %s\r\n", peer->cisco_devicename); + astman_append(session, "CiscoActiveLoad: %s\r\n", peer->cisco_activeload); + astman_append(session, "CiscoInactiveLoad: %s\r\n", peer->cisco_inactiveload); + astman_append(session, "CiscoLineIndex: %d\r\n", peer->cisco_lineindex); + + 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, "sip_show_peer: ao2_t_cleanup: done with peer"); + + return 0; +} + +/* Show SIP peers in the manager API */ +int sip_manager_peer_status(struct mansession *session, const struct message *message) +{ + const char *id = astman_get_header(message, "ActionID"); + const char *name = astman_get_header(message, "Peer"); + char idtext[256] = ""; + struct sip_peer *peer = NULL; + char *status; + int total = 0; + + if (!ast_strlen_zero(id)) { + snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id); + } + + if (!ast_strlen_zero(name)) { + /* strip SIP/ from the begining of the peer name */ + if (strlen(name) >= 4 && !strncasecmp("SIP/", name, 4)) { + name += 4; + } + + if (!(peer = sip_peer_find(name, NULL, TRUE, FALSE, 0))) { + astman_send_error(session, message, "No such peer"); + return 0; + } + } + + astman_send_listack(session, message, "Peer status will follow", "start"); + + if (!peer) { + struct ao2_iterator iter = ao2_iterator_init(sip_peers, 0); + + while ((peer = ao2_t_iterator_next(&iter, "iterate thru peers table for SIPPeerStatus"))) { + ao2_lock(peer); + + if (peer->maxms) { + if (peer->lastms < 0) { + status = "Unreachable"; + } else if (peer->lastms > peer->maxms) { + status = "Lagged"; + } else if (peer->lastms) { + 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->lastms, idtext); + + ao2_unlock(peer); + ao2_t_cleanup(peer, "unref peer for SIPPeerStatus"); + + total++; + } + + ao2_iterator_destroy(&iter); + } else { + ao2_lock(peer); + + if (peer->maxms) { + if (peer->lastms < 0) { + status = "Unreachable"; + } else if (peer->lastms > peer->maxms) { + status = "Lagged"; + } else if (peer->lastms) { + 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->lastms, idtext); + + ao2_unlock(peer); + ao2_t_cleanup(peer, "unref peer for SIPPeerStatus"); + + total++; + } + + astman_send_list_complete_start(session, message, "SIPpeerStatusComplete", total); + 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); + struct ast_channel_blob *chan_blob = stasis_message_data(message); + const char *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 *chan, const char *source) +{ + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + + ast_assert(chan != NULL); + ast_assert(source != NULL); + + if (!(blob = ast_json_pack("{s: s}", "source", source))) { + return; + } + + ast_channel_publish_blob(chan, 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.2.0.orig/channels/sip/message.c asterisk-22.2.0/channels/sip/message.c --- asterisk-22.2.0.orig/channels/sip/message.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/message.c 2025-02-18 17:14:46.814902485 +1300 @@ -0,0 +1,270 @@ +/* + * 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/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/netsock2.h" +#include "asterisk/channel.h" +#include "asterisk/callerid.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/stimer.h" +#include "include/peers.h" +#include "include/pvt.h" +#include "include/utils.h" +#include "include/message.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 int sip_msg_block_header(const char *header); +static void sip_msg_add_header(struct sip_pvt *pvt, const char *name, const char *value); + +const struct ast_msg_tech sip_msg_tech = { + .name = "sip", + .msg_send = sip_msg_send, +}; + +/* Check if the given header name is blocked */ +static int sip_msg_block_header(const char *header) +{ + int i; + static const char *headers[] = { /* Don't block Content-Type or Max-Forwards headers because the user can override them. */ + "To", + "From", + "Via", + "Route", + "Contact", + "Call-ID", + "CSeq", + "Allow", + "Content-Length", + "Request-URI", + }; + + for (i = 0; i < ARRAY_LEN(headers); i++) { + if (!strcasecmp(header, headers[i])) { + /* Block addition of this header. */ + return TRUE; + } + } + + return FALSE; +} + +/* Add a MESSAGE header to the dialog. */ +static void sip_msg_add_header(struct sip_pvt *pvt, const char *name, const char *value) +{ + size_t namelen, valuelen; + struct sip_msg_header *header; + + namelen = strlen(name) + 1; + valuelen = strlen(value) + 1; + + if (!(header = ast_calloc(1, sizeof(*header) + namelen + valuelen))) { + return; + } + + header->name = header->data; + memcpy(header->data, name, namelen); + + header->value = header->data + namelen; + memcpy(header->data + namelen, value, valuelen); + + AST_LIST_INSERT_TAIL(&pvt->msg_headers, header, next); +} + +/* Destroy all additional MESSAGE headers. */ +void sip_msg_free_headers(struct sip_pvt *pvt) +{ + struct sip_msg_header *header; + + while ((header = AST_LIST_REMOVE_HEAD(&pvt->msg_headers, next))) { + ast_free(header); + } +} + +int sip_msg_send(const struct ast_msg *msg, const char *to, const char *from) +{ + struct sip_pvt *pvt; + int res; + char *uri, *host, *user; + const char *name, *value; + struct ast_msg_var_iterator *iter; + struct sip_peer *peer; + + if (!(pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_MESSAGE, NULL, 0))) { + return -1; + } + + 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(pvt, fullcontact, value); + break; + } + } + + ast_msg_var_iterator_destroy(iter); + + uri = ast_strdupa(to); + uri = sip_get_in_brackets(uri); + + sip_uri_parse(uri, "sip:,sips:", &user, NULL, &host, NULL); + + if (ast_strlen_zero(host)) { + ast_log(LOG_WARNING, "MESSAGE(to) is invalid for SIP - '%s'\n", to); + + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "MESSAGE(to) is invalid for SIP"); + + return -1; + } + + if (!ast_strlen_zero(from)) { + if ((peer = sip_peer_find(from, NULL, FALSE, FALSE, 0))) { + ast_string_field_set(pvt, fromname, S_OR(peer->cid_name, peer->name)); + ast_string_field_set(pvt, fromuser, S_OR(peer->cid_num, peer->name)); + + ao2_t_cleanup(peer, "ao2_t_cleanup, from sip_msg_send, sip_peer_find"); + } else if (strchr(from, '<')) { /* from is callerid-style */ + char *sender; + char *cid_name = NULL, *cid_number = NULL, *user = NULL, *domain = NULL, *port = NULL; + + sender = ast_strdupa(from); + ast_callerid_parse(sender, &cid_name, &cid_number); + + if (ast_strlen_zero(cid_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. */ + cid_number = cid_name; + cid_name = NULL; + } + + ast_string_field_set(pvt, fromname, cid_name); + + if (strchr(cid_number, ':')) { /* Must be a URI */ + sip_uri_parse(cid_number, "sip:,sips:", &user, NULL, &domain, NULL); + + sip_pedantic_decode(user); + sip_pedantic_decode(domain); + + ast_sockaddr_split_hostport(domain, &domain, &port, PARSE_PORT_IGNORE); + + ast_string_field_set(pvt, fromuser, user); + ast_string_field_set(pvt, fromdomain, domain); + } else { /* Treat it as an exten/user */ + ast_string_field_set(pvt, fromuser, cid_number); + } + } else { /* assume we just have the name, use defaults for the rest */ + ast_string_field_set(pvt, fromname, from); + } + } + + ao2_lock(pvt); + + /* Look up the host to contact */ + if (sip_pvt_build(pvt, host, NULL, TRUE)) { + ao2_unlock(pvt); + + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "sip_pvt_build failed sending a MESSAGE"); + + return -1; + } + + if (!ast_strlen_zero(user)) { + ast_string_field_set(pvt, username, user); + } + + sip_pvt_set_ouraddrfor(pvt, &pvt->sa, &pvt->ourip); + sip_pvt_build_via(pvt); + ast_set_flag(&pvt->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, "Max-Forwards")) { + /* Decrement Max-Forwards for SIP loop prevention. */ + if (sscanf(value, "%30d", &pvt->maxforwards) != 1 || pvt->maxforwards < 1) { + ast_msg_var_iterator_destroy(iter); + + ao2_unlock(pvt); + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "MESSAGE(Max-Forwards) reached zero."); + + ast_log(LOG_NOTICE, "MESSAGE(Max-Forwards) reached zero. MESSAGE not sent.\n"); + + return -1; + } + + pvt->maxforwards--; + continue; + } + + if (sip_msg_block_header(name)) { + /* Block addition of this header. */ + continue; + } + + sip_msg_add_header(pvt, name, value); + } + + ast_msg_var_iterator_destroy(iter); + + ast_string_field_set(pvt, msg_body, ast_msg_get_body(msg)); + res = sip_send_message(pvt, TRUE, FALSE); + + ao2_unlock(pvt); + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + ao2_t_cleanup(pvt, "sent a MESSAGE"); + + return res; +} diff -durN asterisk-22.2.0.orig/channels/sip/mwi_subscriptions.c asterisk-22.2.0/channels/sip/mwi_subscriptions.c --- asterisk-22.2.0.orig/channels/sip/mwi_subscriptions.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/mwi_subscriptions.c 2025-02-18 17:14:46.814902485 +1300 @@ -0,0 +1,388 @@ +/* + * 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/pvt.h" +#include "include/mwi_subscriptions.h" + +static int sip_mwi_subscription_unlink(void *obj, void *arg, int flags); +static int __sip_mwi_subscription_send(struct sip_mwi_subscription *mwi); +static int __sip_mwi_subscription_start(const void *data); +static int __sip_mwi_subscription_stop(const void *data); + +/* The MWI subscription list */ +struct ao2_container *sip_mwi_subscriptions; + +/* Parse mwi=> line in sip.conf and add to list */ +int sip_mwi_subscription_build(const char *config, int lineno) +{ + struct sip_mwi_subscription *mwi; + enum ast_transport transport; + char *username, *hostname, *secret = NULL, *authuser = NULL, *port = NULL, *mailbox, *context = NULL; + + if (ast_strlen_zero(config)) { + return -1; + } + + username = ast_strdupa(config); + + 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 (!strncmp(username, "udp://", 6)) { + username += 6; + } + + transport = AST_TRANSPORT_UDP; + } + + if ((hostname = strchr(username, '@'))) { + *hostname++ = '\0'; + + if ((port = strchr(hostname, ':'))) { + *port++ = '\0'; + + if (!atoi(port)) { + ast_log(LOG_WARNING, "%s is not a valid port number at line %d\n", port, lineno); + return -1; + } + } + } else { + return -1; + } + + if ((secret = strchr(username, ':'))) { + *secret++ = '\0'; + + if ((authuser = strchr(secret, ':'))) { + *authuser++ = '\0'; + } + } + + 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 [proto://]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, "allocate an mwi struct"))) { + return -1; + } + + mwi->resub = -1; + + if (ast_string_field_init(mwi, 256)) { + ao2_t_ref(mwi, -1, "failed to string_field_init, drop mwi"); + return -1; + } + + ast_string_field_set(mwi, username, username); + + if (secret) { + ast_string_field_set(mwi, secret, secret); + } + + if (authuser) { + ast_string_field_set(mwi, authuser, authuser); + } + + 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 new mwi object"); + ao2_t_ref(mwi, -1, "unref to match ao2_t_alloc"); + + return 0; +} + +/* Destroy MWI subscription object */ +void sip_mwi_subscription_destroy(void *data) +{ + struct sip_mwi_subscription *mwi = data; + + if (mwi->pvt) { + mwi->pvt->mwi = NULL; + ao2_t_cleanup(mwi->pvt, "sip_mwi_subscription destruction"); + } + + ast_string_field_free_memory(mwi); +} + +static int sip_mwi_subscription_unlink(void *obj, void *arg, int flags) +{ + struct sip_mwi_subscription *mwi = obj; + + 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, "remove all SIP mwi subscriptions"); +} + +void sip_mwi_subscription_dnsmgr_lookup(struct ast_sockaddr *oldaddr, struct ast_sockaddr *newaddr, void *data) +{ + struct sip_mwi_subscription *mwi = data; + const char *oldhost, *newhost; + + /* This shouldn't happen, but just in case */ + if (ast_sockaddr_isnull(newaddr)) { + ast_debug(1, "Empty sockaddr change...ignoring!\n"); + return; + } + + oldhost = ast_strdupa(ast_sockaddr_stringify(oldaddr)); + newhost = ast_strdupa(ast_sockaddr_stringify(newaddr)); + + ast_debug(1, "Changing mwi %s from %s to %s\n", mwi->hostname, oldhost, newhost); + ast_sockaddr_copy(&mwi->us, newaddr); +} + +/* 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]; + + mwi->us.ss.ss_family = sip_transport_family(mwi->transport); /* Filter address family */ + ao2_t_ref(mwi, +1, "dnsmgr reference to mwi"); + + snprintf(transport, sizeof(transport), "_%s._%s", sip_srv_service(mwi->transport), sip_srv_protocol(mwi->transport)); + ast_dnsmgr_lookup_cb(mwi->hostname, &mwi->us, &mwi->dnsmgr, sip_config.srvlookup ? transport : NULL, sip_mwi_subscription_dnsmgr_lookup, mwi); + + if (!mwi->dnsmgr) { + ao2_t_ref(mwi, -1, "dnsmgr disabled, remove reference"); + } + } + + /* If we already have a subscription up simply send a resubscription */ + if (mwi->pvt) { + sip_send_invite(mwi->pvt, SIP_SUBSCRIBE, FALSE, SIP_INIT_NONE, NULL); + return 0; + } + + /* Create a dialog that we will use for the subscription */ + if (!(mwi->pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_SUBSCRIBE, NULL, 0))) { + return -1; + } + + sip_proxy_ref(mwi->pvt, sip_proxy_get(mwi->pvt, NULL)); + + if (!ast_sockaddr_port(&mwi->us) && mwi->port) { + ast_sockaddr_set_port(&mwi->us, mwi->port); + } + + /* Setup the destination of our subscription */ + if (sip_pvt_build(mwi->pvt, mwi->hostname, &mwi->us, FALSE)) { + sip_pvt_unlink(mwi->pvt); + ao2_t_cleanup(mwi->pvt, "unref dialog after unlink_all"); + + mwi->pvt = NULL; + return 0; + } + + mwi->pvt->expiry = sip_config.mwi_expiry; + + if (!mwi->dnsmgr && mwi->port) { + ast_sockaddr_set_port(&mwi->pvt->sa, mwi->port); + ast_sockaddr_set_port(&mwi->pvt->recv, mwi->port); + } else { + mwi->port = ast_sockaddr_port(&mwi->pvt->sa); + } + + /* Set various other information */ + if (!ast_strlen_zero(mwi->authuser)) { + ast_string_field_set(mwi->pvt, peername, mwi->authuser); + ast_string_field_set(mwi->pvt, authname, mwi->authuser); + ast_string_field_set(mwi->pvt, fromuser, mwi->authuser); + } else { + ast_string_field_set(mwi->pvt, peername, mwi->username); + ast_string_field_set(mwi->pvt, authname, mwi->username); + ast_string_field_set(mwi->pvt, fromuser, mwi->username); + } + + ast_string_field_set(mwi->pvt, username, mwi->username); + + if (!ast_strlen_zero(mwi->secret)) { + ast_string_field_set(mwi->pvt, peersecret, mwi->secret); + } + + sip_socket_set_transport(&mwi->pvt->socket, mwi->transport); + sip_pvt_set_ouraddrfor(mwi->pvt, &mwi->pvt->sa, &mwi->pvt->ourip); + sip_pvt_build_via(mwi->pvt); + + /* Change the dialog callid. */ + sip_pvt_change_callid(mwi->pvt, NULL); + ast_set_flag(&mwi->pvt->flags[0], SIP_OUTGOING); + + /* Associate the call with us */ + mwi->pvt->mwi = ao2_t_bump(mwi, "Reference mwi from it's call"); + mwi->pvt->subscribed = SIP_SUBSCRIBED_MESSAGE_SUMMARY; + + /* Actually send the packet */ + sip_send_invite(mwi->pvt, SIP_SUBSCRIBE, FALSE, SIP_INIT_REQUEST, NULL); + + return 0; +} + +/* Send a subscription or resubscription for MWI */ +int sip_mwi_subscription_send(const void *data) +{ + struct sip_mwi_subscription *mwi = (struct sip_mwi_subscription *) data; + + mwi->resub = -1; + __sip_mwi_subscription_send(mwi); + ao2_t_ref(mwi, -1, "Scheduled mwi resub complete"); + + 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, "sip_mwi_subscription_send_all iter"))) { + sip_mwi_subscription_start(mwi, 1); + ao2_t_ref(mwi, -1, "sip_mwi_subscription_send_all iter"); + } + + ao2_iterator_destroy(&iter); +} + +/* Run by the sched thread. */ +static int __sip_mwi_subscription_start(const void *data) +{ + struct sip_sched_data *sched_data = (void *) data; + struct sip_mwi_subscription *mwi = sched_data->mwi; + int ms = sched_data->ms; + + ast_free(sched_data); + + AST_SCHED_DEL_UNREF(sip_sched_context, mwi->resub, ao2_t_ref(mwi, -1, "Stop scheduled mwi resub")); + + ao2_t_ref(mwi, +1, "Schedule mwi resub"); + mwi->resub = ast_sched_add(sip_sched_context, ms, sip_mwi_subscription_send, mwi); + + if (mwi->resub < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(mwi, -1, "Failed to schedule mwi resub"); + } + + ao2_t_ref(mwi, -1, "Start MWI subscription action"); + + return 0; +} + +void sip_mwi_subscription_start(struct sip_mwi_subscription *mwi, int ms) +{ + 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->ms = ms; + + ao2_t_ref(mwi, +1, "Start MWI subscription action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_mwi_subscription_start, sched_data) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(mwi, -1, "Failed to schedule start MWI subscription action"); + ast_free(sched_data); + } +} + +/* Run by the sched thread. */ +static int __sip_mwi_subscription_stop(const void *data) +{ + struct sip_mwi_subscription *mwi = (void *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, mwi->resub, ao2_t_ref(mwi, -1, "Stop scheduled mwi resub")); + + if (mwi->dnsmgr) { + ast_dnsmgr_release(mwi->dnsmgr); + mwi->dnsmgr = NULL; + ao2_t_ref(mwi, -1, "dnsmgr release"); + } + + ao2_t_ref(mwi, -1, "Shutdown MWI subscription action"); + + return 0; +} + +void sip_mwi_subscription_stop(struct sip_mwi_subscription *mwi) +{ + ao2_t_ref(mwi, +1, "Shutdown MWI subscription action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_mwi_subscription_stop, mwi) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(mwi, -1, "Failed to schedule shutdown MWI subscription action"); + } +} diff -durN asterisk-22.2.0.orig/channels/sip/netsock.c asterisk-22.2.0/channels/sip/netsock.c --- asterisk-22.2.0.orig/channels/sip/netsock.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/netsock.c 2025-02-18 17:14:46.815902458 +1300 @@ -0,0 +1,1290 @@ +/* + * 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/pvt.h" +#include "include/handlers.h" + +#define SIP_DEFAULT_TIMER_A 1000 /* How frequently to retransmit Default: 2 * 500 ms in RFC 3261 */ + +static void sip_tcptls_thread_destroy(void *obj); +static void sip_packet_free(void *data); +static int sip_socket_prepare(struct sip_pvt *pvt); + +/* 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 */ +static int sip_unauth_count = 0; /* Unauthenticated TCP sessions */ + +/* 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_request req; + struct ast_sockaddr addr; + int res; + static char buf[65535]; + + memset(&req, 0, sizeof(req)); + res = ast_recvfrom(fd, buf, sizeof(buf) - 1, 0, &addr); + + if (res < 0) { +#if !defined(__FreeBSD__) + if (errno == EAGAIN) { + ast_log(LOG_NOTICE, "SIP: Received packet with bad UDP checksum\n"); + } +#endif + if (errno != ECONNREFUSED) { + ast_log(LOG_WARNING, "Recv error: %s\n", strerror(errno)); + } + + return 1; + } + + buf[res] = '\0'; + + if (!(req.data = ast_str_create(SIP_MIN_PACKET_SIZE))) { + return 1; + } + + if (ast_str_set(&req.data, 0, "%s", buf) == AST_DYNSTR_BUILD_FAILED) { + return -1; + } + + req.socket.fd = sip_socket_fd; + sip_socket_set_transport(&req.socket, AST_TRANSPORT_UDP); + req.socket.tcptls_session = NULL; + + sip_handle_incoming(&req, &addr); + sip_request_free(&req); + + return 1; +} + +void sip_socket_set_transport(struct sip_socket *socket, int transport) +{ + /* if the transport type changes, clear all socket data */ + if (socket->type != transport) { + socket->fd = -1; + socket->type = transport; + + if (socket->tcptls_session) { + ao2_ref(socket->tcptls_session, -1); + socket->tcptls_session = NULL; + } + } +} + +void sip_socket_copy_data(struct sip_socket *to_sock, const struct sip_socket *from_sock) +{ + if (to_sock->tcptls_session) { + ao2_ref(to_sock->tcptls_session, -1); + to_sock->tcptls_session = NULL; + } + + if (from_sock->tcptls_session) { + ao2_ref(from_sock->tcptls_session, +1); + } + + *to_sock = *from_sock; +} + +/* Get socket for dialog, prepare if needed, and return file handle */ +static int sip_socket_prepare(struct sip_pvt *pvt) +{ + struct sip_socket *sock = &pvt->socket; + static const char *name = "SIP socket"; + struct sip_tcptls_thread *thread = NULL; + struct ast_tcptls_session_instance *tcptls_session; + struct ast_tcptls_session_args *args; + struct ast_sockaddr addr; + pthread_t threadid; + + /* check to see if a socket is already active */ + if (sock->fd != -1 && sock->type == AST_TRANSPORT_UDP) { + return sock->fd; + } + + if ((sock->type & (AST_TRANSPORT_TCP | AST_TRANSPORT_TLS)) && sock->tcptls_session && sock->tcptls_session->stream) { + return ast_iostream_get_fd(sock->tcptls_session->stream); + } + + if (pvt->outboundproxy && pvt->outboundproxy->transport) { + sock->type = pvt->outboundproxy->transport; + } + + if (sock->type == AST_TRANSPORT_UDP) { + sock->fd = sip_socket_fd; + return sock->fd; + } + + /* 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(&addr, sip_pvt_real_dst(pvt)); + + if ((tcptls_session = sip_tcptls_session_find(&addr))) { + sock->fd = ast_iostream_get_fd(tcptls_session->stream); + + if (sock->tcptls_session) { + ao2_ref(sock->tcptls_session, -1); + sock->tcptls_session = NULL; + } + + sock->tcptls_session = tcptls_session; + return sock->fd; + /* 2. Thread not found, if tcptls_session already exists, it once had a thread and is now terminated */ + } else if (sock->tcptls_session) { + return sock->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 (!(args = ao2_alloc(sizeof(*args), sip_tcptls_session_args_free)) || !(args->name = ast_strdup(name))) { + goto cleanup; + } + + args->accept_fd = -1; + ast_sockaddr_copy(&args->remote_address, sip_pvt_real_dst(pvt)); + + /* if type is TLS, we need to create a tls cfg for this session arg */ + if (sock->type == AST_TRANSPORT_TLS) { + if (!(args->tls_cfg = ast_calloc(1, sizeof(*args->tls_cfg)))) { + goto cleanup; + } + + memcpy(args->tls_cfg, &sip_tls_config, sizeof(*args->tls_cfg)); + + if (!(args->tls_cfg->certfile = ast_strdup(sip_tls_config.certfile)) || + !(args->tls_cfg->pvtfile = ast_strdup(sip_tls_config.pvtfile)) || + !(args->tls_cfg->cipher = ast_strdup(sip_tls_config.cipher)) || + !(args->tls_cfg->cafile = ast_strdup(sip_tls_config.cafile)) || + !(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(pvt->tohost)) { + ast_copy_string(args->hostname, pvt->tohost, sizeof(args->hostname)); + } + } + + /* If a bind address has been specified, use it */ + if ((sock->type == AST_TRANSPORT_TLS) && !ast_sockaddr_isnull(&sip_tls_session.local_address)) { + args->local_address = sip_tls_session.local_address; + } else if ((sock->type == AST_TRANSPORT_TCP) && !ast_sockaddr_isnull(&sip_tcp_session.local_address)) { + 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(&args->local_address)) { + ast_sockaddr_set_port(&args->local_address, 0); + } + + /* Create a client connection for address, this does not start the connection, just sets it up. */ + if (!(sock->tcptls_session = ast_tcptls_client_create(args))) { + goto cleanup; + } + + sock->fd = ast_iostream_get_fd(sock->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(sock->tcptls_session, sock->type))) { + goto cleanup; + } + + /* Give the new thread a reference to the tcptls_session */ + ao2_ref(sock->tcptls_session, +1); + + if (ast_pthread_create_detached_background(&threadid, NULL, sip_tcptls_session_thread, sock->tcptls_session)) { + ast_debug(1, "Unable to launch '%s'.", args->name); + ao2_ref(sock->tcptls_session, -1); /* take away the thread ref we just gave it */ + + goto cleanup; + } + + ast_set_qos(sock->fd, sip_config.tos_sip, sip_config.cos_sip, "SIP"); + + return sock->fd; + +cleanup: + if (args) { + ao2_t_ref(args, -1, "failed to create client, getting rid of client tcptls_session arguments"); + } + + if (sock->tcptls_session) { + ast_tcptls_close_session_file(sock->tcptls_session); + sock->fd = -1; + + ao2_ref(sock->tcptls_session, -1); + sock->tcptls_session = NULL; + } + + if (thread) { + ao2_t_unlink(sip_tcptls_threads, thread, "Removing tcptls thread info object, thread failed to open"); + } + + return -1; +} + +int sip_tcptls_thread_hash(const void *obj, const int flags) +{ + const struct sip_tcptls_thread *thread = obj; + + return ast_sockaddr_hash(&thread->tcptls_session->remote_address); +} + +int sip_tcptls_thread_cmp(void *obj, void *arg, int flags) +{ + struct sip_tcptls_thread *thread1 = obj, *thread2 = arg; + + return thread1->tcptls_session == thread2->tcptls_session ? CMP_MATCH | CMP_STOP : 0; +} + +int sip_tcptls_thread_cmp_addr(void *data, void *arg, int flags) +{ + struct sip_tcptls_thread *thread = data; + struct ast_sockaddr *addr = arg; + + return !ast_sockaddr_cmp(addr, &thread->tcptls_session->remote_address) ? CMP_MATCH | CMP_STOP : 0; +} + +/* creates a sip_tcptls_thread object and links it into the sip_tcptls_threads table. */ +struct sip_tcptls_thread *sip_tcptls_thread_alloc(struct ast_tcptls_session_instance *tcptls_session, int transport) +{ + struct sip_tcptls_thread *thread; + + if (!tcptls_session || !(thread = ao2_alloc(sizeof(*thread), sip_tcptls_thread_destroy))) { + return NULL; + } + + thread->alert_pipe[0] = thread->alert_pipe[1] = -1; + + if (pipe(thread->alert_pipe) == -1) { + ao2_t_ref(thread, -1, "Failed to open alert pipe on sip_tcptls_thread"); + ast_log(LOG_ERROR, "Could not create sip alert pipe in tcptls thread, error %s\n", strerror(errno)); + + return NULL; + } + + ao2_t_ref(tcptls_session, +1, "tcptls_session ref for sip_tcptls_thread object"); + + thread->tcptls_session = tcptls_session; + thread->type = transport ? transport : (ast_iostream_get_ssl(tcptls_session->stream) ? AST_TRANSPORT_TLS: AST_TRANSPORT_TCP); + + ao2_t_link(sip_tcptls_threads, thread, "Adding new tcptls helper thread"); + ao2_t_ref(thread, -1, "Decrementing threadinfo ref from alloc, only table ref remains"); + + return thread; +} + +static void sip_tcptls_thread_destroy(void *obj) +{ + struct sip_tcptls_thread *thread = obj; + struct sip_tcptls_packet *packet; + + if (thread->alert_pipe[0] > -1) { + close(thread->alert_pipe[0]); + } + + if (thread->alert_pipe[1] > -1) { + close(thread->alert_pipe[1]); + } + + thread->alert_pipe[0] = thread->alert_pipe[1] = -1; + + while ((packet = AST_LIST_REMOVE_HEAD(&thread->packet_queue, next))) { + ao2_t_ref(packet, -1, "thread destruction, removing packet from frame queue"); + } + + if (thread->tcptls_session) { + ao2_t_ref(thread->tcptls_session, -1, "remove tcptls_session for sip_tcptls_thread object"); + } +} + +static void sip_tcptls_packet_free(void *obj) +{ + struct sip_tcptls_packet *packet = obj; + + ast_free(packet->data); +} + +void sip_tcptls_session_args_free(void *obj) +{ + struct ast_tcptls_session_args *args = obj; + + if (args->tls_cfg) { + ast_free(args->tls_cfg->certfile); + ast_free(args->tls_cfg->pvtfile); + ast_free(args->tls_cfg->cipher); + ast_free(args->tls_cfg->cafile); + ast_free(args->tls_cfg->capath); + + ast_ssl_teardown(args->tls_cfg); + } + + ast_free(args->tls_cfg); + ast_free((char *) args->name); +} + +/* Find thread for TCP/TLS session (based on IP/Port */ +struct ast_tcptls_session_instance *sip_tcptls_session_find(struct ast_sockaddr *addr) +{ + struct sip_tcptls_thread *thread; + struct ast_tcptls_session_instance *tcptls_session = NULL; + + if ((thread = ao2_callback(sip_tcptls_threads, 0, sip_tcptls_thread_cmp_addr, addr))) { + tcptls_session = (ao2_ref(thread->tcptls_session, +1), thread->tcptls_session); + ao2_t_ref(thread, -1, "decrement ref from callback"); + } + + 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 void *buf, size_t len) +{ + int res = len; + struct sip_tcptls_thread *thread = NULL; + struct sip_tcptls_packet *packet = NULL; + struct sip_tcptls_thread tmp_thread = {.tcptls_session = tcptls_session}; + int alert = TRUE; + + if (!tcptls_session) { + return -1; + } + + ao2_lock(tcptls_session); + + if (!tcptls_session->stream || + !(packet = ao2_alloc(sizeof(*packet), sip_tcptls_packet_free)) || + !(packet->data = ast_str_create(len))) { + goto error; + } + + if (!(thread = ao2_t_find(sip_tcptls_threads, &tmp_thread, OBJ_POINTER, "ao2_find, getting sip_tcptls_thread in tcp helper 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", (char *) buf); + packet->len = len; + + /* 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); + + 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, "could not write to alert pipe, remove 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); + } + + ao2_unlock(thread); + ao2_unlock(tcptls_session); + ao2_t_ref(thread, -1, "In sip_tcptls_session_write, unref threadinfo object after finding it"); + + return res; + +error: + if (thread) { + ao2_t_ref(thread, -1, "In sip_tcptls_session_write, unref threadinfo obj, could not create packet"); + } + + if (packet) { + ao2_t_ref(packet, -1, "could not allocate packet's data"); + } + + ao2_unlock(tcptls_session); + + return -1; +} + +/* Check that a message received over TCP is complete */ +static int sip_tcptls_session_read_done(struct ast_str **data, struct ast_str **overflow_buf) +{ + int datalen = ast_str_strlen(*data); + struct ast_str *headers; + char *body, *header; + int headerslen, bodylen, contentlen = -1; + + /* Important pieces to search for in a SIP request are \r\n\r\n. This marks either The division between the headers and body or + * the end of the SIP request */ + if (!(body = strstr(ast_str_buffer(*data), "\r\n\r\n"))) { + /* This is clearly a partial message since we haven't reached an end yet. */ + return FALSE; + } + + body += 4; + + headerslen = body - ast_str_buffer(*data); + bodylen = datalen - (body - ast_str_buffer(*data)); + + /* Using a ast_str because sip_compress_whitespace takes one of those */ + if (!(headers = ast_str_create(headerslen + 1))) { + return FALSE; + } + + ast_str_append_substr(&headers, 0, ast_str_buffer(*data), headerslen); + + if (sip_config.pedanticsipchecking) { + sip_compress_whitespace(headers); + } + + /* 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. */ + if ((header = strcasestr(ast_str_buffer(headers), "\nContent-Length:"))) { + header += 16; + + /* Check that this is a complete header */ + if (!strchr(header, '\n') || sscanf(header, "%30d", &contentlen) != 1) { + contentlen = -1; + } + } + + ast_free(headers); + + if (contentlen < 0) { + return FALSE; + } else if (contentlen == 0) { + /* We've definitely received an entire message. We need to check if there's also a fragment of another message in addition. */ + if (bodylen > 0) { + ast_str_append(overflow_buf, 0, "%s", body); + ast_str_truncate(*data, datalen - bodylen); + } + + return TRUE; + } + + /* Positive content length. Let's see what sort of message body we're dealing with. */ + if (bodylen < contentlen) { + /* We don't have the full message body yet */ + return FALSE; + } + + if (bodylen > contentlen) { + /* We have the full message plus a fragment of a further message */ + ast_str_append(overflow_buf, 0, "%s", body + contentlen); + ast_str_truncate(*data, datalen - (bodylen - contentlen)); + + return TRUE; + } + + return TRUE; +} + +/* Read SIP request or response from a TCP/TLS connection */ +int sip_tcptls_session_read(struct sip_request *req, struct ast_tcptls_session_instance *tcptls_session, int authenticated, time_t start) +{ + do { + size_t datalen; + + if (ast_str_strlen(tcptls_session->overflow_buf) == 0) { + char data[4097]; + int res, timeout; + + if (!tcptls_session->client && !authenticated) { + time_t now = time(NULL); + + timeout = (sip_config.tcpauthtimeout - (now - start)) * 1000; + + if (timeout < 0) { + ast_debug(2, "SIP TCP/TLS server timed out\n"); + return -1; + } + } else { + timeout = -1; + } + + res = ast_wait_for_input(ast_iostream_get_fd(tcptls_session->stream), timeout); + + if (res < 0) { + ast_debug(2, "SIP TCP/TLS server ast_wait_for_input returned %d\n", res); + return -1; + } else if (res == 0) { + ast_debug(2, "SIP 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, "SIP TCP/TLS server error when receiving data\n"); + return -1; + } else if (res == 0) { + ast_debug(2, "SIP TCP/TLS server has shut down\n"); + return -1; + } + + data[res] = '\0'; + ast_str_append(&req->data, 0, "%s", data); + } else { + ast_str_append(&req->data, 0, "%s", ast_str_buffer(tcptls_session->overflow_buf)); + ast_str_reset(tcptls_session->overflow_buf); + } + + datalen = ast_str_strlen(req->data); + + if (datalen > 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), datalen); + return -1; + } + } while (!sip_tcptls_session_read_done(&req->data, &tcptls_session->overflow_buf)); + + return 0; +} + +/* SIP TCP connection handler */ +void *sip_tcptls_session_thread(void *data) +{ + struct ast_tcptls_session_instance *tcptls_session = data; + int res, timeout = -1, authenticated = FALSE, keepalive; + time_t start; + struct sip_request req = {0}; + struct sip_tcptls_thread *thread = NULL; + struct pollfd fds[2] = {{0}, {0}}; + struct ast_tcptls_session_args *args = NULL; + + /* 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) { + if (ast_atomic_fetchadd_int(&sip_unauth_count, +1) >= sip_config.tcpauthlimit) { + /* sip_unauth_count is decremented in the cleanup code */ + goto cleanup; + } + + ast_iostream_nonblock(tcptls_session->stream); + + if (!(thread = sip_tcptls_thread_alloc(tcptls_session, ast_iostream_get_ssl(tcptls_session->stream) ? AST_TRANSPORT_TLS : AST_TRANSPORT_TCP))) { + goto cleanup; + } + + thread->threadid = pthread_self(); + ao2_t_ref(thread, +1, "Adding threadinfo ref for sip_tcptls_worker"); + } else { + struct sip_tcptls_thread tmp_thread = {.tcptls_session = tcptls_session}; + + if ((!(args = tcptls_session->parent)) || + (!(thread = ao2_t_find(sip_tcptls_threads, &tmp_thread, OBJ_POINTER, "ao2_find, getting sip_tcptls_thread in tcp helper thread")))) { + goto cleanup; + } + + thread->threadid = pthread_self(); + + if (!(tcptls_session = ast_tcptls_client_start(tcptls_session))) { + goto cleanup; + } + } + + 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-alives on sip socket: %s\n", strerror(errno)); + goto cleanup; + } + + 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[1].fd = thread->alert_pipe[0]; + fds[0].events = fds[1].events = POLLIN | POLLPRI; + + if (!(req.data = ast_str_create(SIP_MIN_PACKET_SIZE))) { + goto cleanup; + } + + if (time(&start) == -1) { + ast_log(LOG_ERROR, "error executing time(): %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, 0); + ast_iostream_set_timeout_sequence(tcptls_session->stream, ast_tvnow(), tcptls_session->client ? -1 : (sip_config.tcpauthtimeout * 1000)); + + for (;;) { + if (!tcptls_session->client && req.authenticated && !authenticated) { + authenticated = TRUE; + ast_iostream_set_timeout_disable(tcptls_session->stream); + ast_atomic_fetchadd_int(&sip_unauth_count, -1); + } + + /* calculate the timeout for unauthenticated server sessions */ + if (!tcptls_session->client && !authenticated) { + time_t now = time(NULL); + + timeout = (sip_config.tcpauthtimeout - (now - 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) == 0) { + res = ast_poll(fds, 2, timeout); /* polls for both socket and alert_pipe */ + + if (res < 0) { + ast_debug(2, "SIP %s server ast_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 */ + struct ast_str *req_data; + + fds[0].revents = 0; + + /* clear request structure */ + req_data = req.data; + memset(&req, 0, sizeof(req)); + req.data = req_data; + ast_str_reset(req.data); + + if (ast_iostream_get_ssl(tcptls_session->stream)) { + sip_socket_set_transport(&req.socket, AST_TRANSPORT_TLS); + } else { + sip_socket_set_transport(&req.socket, AST_TRANSPORT_TCP); + } + + req.socket.fd = ast_iostream_get_fd(tcptls_session->stream); + res = sip_tcptls_session_read(&req, tcptls_session, authenticated, start); + + if (res < 0) { + goto cleanup; + } + + req.socket.tcptls_session = tcptls_session; + sip_handle_incoming(&req, &tcptls_session->remote_address); + } + + if (fds[1].revents) { /* alert_pipe indicates there is data in the send queue to be sent */ + int alert; + struct sip_tcptls_packet *packet; + + fds[1].revents = 0; + + if (read(thread->alert_pipe[0], &alert, sizeof(alert)) == -1) { + ast_log(LOG_ERROR, "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, "TCPTLS thread alert_pipe indicated packet should be sent, but packet list is empty\n"); + } + + ao2_unlock(thread); + + if (packet) { + if (ast_iostream_write(tcptls_session->stream, ast_str_buffer(packet->data), packet->len) == -1) { + ast_log(LOG_WARNING, "Failure to write to tcp/tls socket\n"); + } + + ao2_t_ref(packet, -1, "tcptls packet sent, this is no longer needed"); + } else { + goto cleanup; + } + } + } + + 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(&sip_unauth_count, -1); + } + + if (thread) { + ao2_t_unlink(sip_tcptls_threads, thread, "Removing tcptls helper thread, thread is closing"); + ao2_t_ref(thread, -1, "Removing sip_tcptls_worker threadinfo ref"); + } + + sip_request_free(&req); + + /* if client, we own the parent session arguments and must decrement ref */ + if (args) { + ao2_t_ref(args, -1, "closing tcptls thread, getting rid of client tcptls_session arguments"); + } + + 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_pvt *pvt, struct ast_str *data) +{ + int res = 0; + const struct ast_sockaddr *dst = sip_pvt_real_dst(pvt); + + ast_debug(2, "Trying to put '%.32s...' onto %s socket destined for %s\n", + ast_str_buffer(data), sip_pvt_get_transport(pvt), ast_sockaddr_stringify(dst)); + + if (sip_socket_prepare(pvt) < 0) { + return -1; + } + + if (pvt->socket.type == AST_TRANSPORT_UDP) { + res = ast_sendto(pvt->socket.fd, ast_str_buffer(data), ast_str_strlen(data), 0, dst); + } else if (pvt->socket.tcptls_session) { + res = sip_tcptls_session_write(pvt->socket.tcptls_session, ast_str_buffer(data), ast_str_strlen(data)); + + if (res < -1) { + return res; + } + } else { + ast_debug(2, "Socket type is TCP but no tcptls_session is present to write to\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_log(LOG_WARNING, "sip_xmit of %p (len %zu) to %s returned %d: %s\n", + data, ast_str_strlen(data), ast_sockaddr_stringify(dst), res, strerror(errno)); + } + + return res; +} + +/* Transmit packet with retransmits */ +int sip_packet_send_reliable(struct sip_pvt *pvt, uint32_t seqno, int is_resp, struct ast_str *data, int is_fatal, int method) +{ + struct sip_packet *packet = NULL; + int timer_a = SIP_DEFAULT_TIMER_A; + + if (method == SIP_INVITE) { + /* Note this is a pending invite */ + pvt->pendinginvite = seqno; + } + + if (!(packet = ao2_alloc_options(sizeof(*packet), sip_packet_free, 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, "Failed to initialize"); + return -1; + } + + ast_str_set(&packet->data, 0, "%s%s", ast_str_buffer(data), "\0"); + + /* copy other parameters from the caller */ + packet->method = method; + packet->seqno = seqno; + packet->is_resp = is_resp; + packet->is_fatal = is_fatal; + packet->owner = ao2_t_bump(pvt, "sip_packet_send_reliable: setting packet->owner"); + + /* The retransmission list owns a packet ref */ + AST_LIST_INSERT_HEAD(&pvt->packet_queue, packet, next); + + if (is_resp) { + int resp; + + /* Parse out the response code */ + if (sscanf(ast_str_buffer(packet->data), "SIP/2.0 %30u", &resp) == 1) { + packet->response_code = resp; + } + } + + packet->timer_t1 = pvt->timer_t1; /* Set SIP timer T1 */ + + if (packet->timer_t1) { + timer_a = packet->timer_t1; + } + + packet->time_sent = ast_tvnow(); /* time packet was sent */ + packet->retrans_stop_time = 64 * (packet->timer_t1 ? packet->timer_t1 : sip_config.timer_t1); /* time in ms after packet->time_sent to stop retransmission */ + + if (!(pvt->socket.type & AST_TRANSPORT_UDP)) { + /* TCP does not need retransmits as that's built in, but with retrans_stop set, we must give it the full timer_H treatment */ + packet->retrans_stop = TRUE; + timer_a = packet->retrans_stop_time; + } + + /* Schedule retransmission */ + ao2_t_ref(packet, +1, "Schedule packet retransmission"); + packet->retransid = ast_sched_add_variable(sip_sched_context, timer_a, sip_packet_resend, packet, 1); + + if (packet->retransid < 0) { + ao2_t_ref(packet, -1, "Failed to schedule packet retransmission"); + } + + if (sip_debug) { + ast_debug(4, "SIP TIMER: Initializing retransmit timer on packet: Id #%d\n", packet->retransid); + } + + if (sip_packet_send(packet->owner, packet->data) == -1) { /* Send packet */ + /* Serious network trouble, no need to try again */ + sip_history_append(packet->owner, "XmitErr", "%s", packet->is_fatal ? "(Critical)" : "(Non-critical)"); + ast_log(LOG_ERROR, "Serious Network Trouble; sip_packet_send returns error for packet data\n"); + + /* Unlink and destroy the packet object. */ + AST_LIST_REMOVE_HEAD(&pvt->packet_queue, next); + sip_packet_cancel_resend(packet); + ao2_t_ref(packet, -1, "Packet retransmission list"); + + return -1; + } else { + /* This is odd, but since the retrans 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 sip_packet *) data; + struct sip_packet *owner_packet; + struct ast_channel *owner_chan; + int timer_a = SIP_DEFAULT_TIMER_A; + int send_res = 0; + /* how many ms until retrans timeout is reached */ + int64_t diff = packet->retrans_stop_time - ast_tvdiff_ms(ast_tvnow(), packet->time_sent); + + /* 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. */ + if (diff <= 0 || diff > packet->retrans_stop_time) { + packet->retrans_stop = TRUE; + } + + /* Lock channel PVT */ + ao2_lock(packet->owner); + + if (!packet->retrans_stop) { + packet->retrans++; + + if (!packet->timer_t1) { /* Re-schedule using timer_a and timer_t1 */ + if (sip_debug) { + ast_debug(4, "SIP TIMER: Not rescheduling id #%d:%s (Method %d) No timer T1\n", + packet->retransid, sip_methods[packet->method].name, packet->method); + } + } else { + if (sip_debug) { + ast_debug(4, "SIP TIMER: Rescheduling retransmission #%d (%d) %s - %d\n", + packet->retransid, packet->retrans, sip_methods[packet->method].name, packet->method); + } + + 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_INVITE && timer_a > 4000) { + timer_a = 4000; + } + + /* Reschedule re-transmit */ + ast_debug(4, "SIP timers: Rescheduling retransmission %d to %d ms (t1 %d ms (Retrans id #%d))\n", + packet->retrans + 1, timer_a, packet->timer_t1, packet->retransid); + } + + if (sip_debug_test_pvt(packet->owner)) { + const struct ast_sockaddr *dst = sip_pvt_real_dst(packet->owner); + + ast_verb(3, "Retransmitting #%d (%s) to %s:\n%s\n---\n", + packet->retrans, sip_nat_mode2str(&packet->owner->flags[0]), ast_sockaddr_stringify(dst), ast_str_buffer(packet->data)); + } + + sip_history_append(packet->owner, "ReTx", "%d %s", timer_a, ast_str_buffer(packet->data)); + send_res = sip_packet_send(packet->owner, 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 (send_res != -1) { + if (timer_a >= diff) { + packet->retrans_stop = TRUE; + timer_a = diff; + } + + ao2_unlock(packet->owner); + + 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->retransid = -1; /* Kill this scheduler item */ + + if (packet->method != SIP_OPTIONS && send_res == 0) { + if (packet->is_fatal || sip_debug) { /* Tell us if it's critical or if we're debugging */ + ast_log(LOG_WARNING, "Retransmission timeout reached on transmission %s for seqno %u (%s %s) -- See https://wiki.asterisk.org/wiki/display/AST/SIP+Retransmissions\nPacket timed out after %dms with no response\n", + packet->owner->callid, + packet->seqno, + packet->is_fatal ? "Critical" : "Non-critical", + packet->is_resp ? "Response" : "Request", + (int) ast_tvdiff_ms(ast_tvnow(), packet->time_sent)); + } + } else if (packet->method == SIP_OPTIONS && sip_debug) { + ast_log(LOG_WARNING, "Cancelling retransmit of OPTIONs (call id %s) -- See https://wiki.asterisk.org/wiki/display/AST/SIP+Retransmissions\n", packet->owner->callid); + } + + if (send_res == -1) { + ast_log(LOG_WARNING, "Transmit error: Cancelling transmission on Call ID %s\n", packet->owner->callid); + sip_history_append(packet->owner, "XmitErr", "%s", packet->is_fatal ? "(Critical)" : "(Non-critical)"); + } else { + sip_history_append(packet->owner, "MaxRetries", "%s", packet->is_fatal ? "(Critical)" : "(Non-critical)"); + } + + ao2_unlock(packet->owner); /* SIP_PVT, not channel */ + owner_chan = sip_pvt_lock_full(packet->owner); + + if (packet->is_fatal) { + if (owner_chan) { + ast_log(LOG_WARNING, "Hanging up call %s - no reply to our critical packet (see https://wiki.asterisk.org/wiki/display/AST/SIP+Retransmissions).\n", packet->owner->callid); + + if (packet->is_resp && (packet->response_code >= 200) && (packet->response_code < 300) && + packet->owner->pendinginvite && ast_test_flag(&packet->owner->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 "alreadygone") + * 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->owner->invitestate = SIP_INVITE_TERMINATED; + packet->owner->pendinginvite = 0; + } else { + /* there is nothing left to do, mark the dialog as gone */ + sip_pvt_set_already_gone(packet->owner); + } + + if (!ast_channel_hangupcause(owner_chan)) { + ast_channel_hangupcause_set(owner_chan, AST_CAUSE_NO_USER_RESPONSE); + } + + ast_queue_hangup_with_cause(owner_chan, 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_OPTIONS && packet->method != SIP_REGISTER) { + sip_pvt_set_need_destroy(packet->owner, "no response to critical packet"); + sip_pvt_set_already_gone(packet->owner); + + sip_history_append(packet->owner, "DialogKill", "Killing this failed dialog immediately"); + } + } + } else if (packet->owner->pendinginvite == packet->seqno) { + ast_log(LOG_WARNING, "Timeout on %s on non-critical invite transaction.\n", packet->owner->callid); + + packet->owner->invitestate = SIP_INVITE_TERMINATED; + packet->owner->pendinginvite = 0; + + sip_pvt_check_pendings(packet->owner); + } + + if (owner_chan) { + ast_channel_unlock(owner_chan); + ast_channel_unref(owner_chan); + } + + if (packet->method == SIP_BYE) { + /* We're not getting answers on SIP BYE's. Tear down the call anyway. */ + sip_pvt_set_already_gone(packet->owner); + sip_pvt_set_need_destroy(packet->owner, "no response to BYE"); + + sip_history_append(packet->owner, "ByeFailure", "Remote peer doesn't respond to bye. Destroying call anyway."); + } + + /* Unlink and destoroy the packet object. */ + AST_LIST_TRAVERSE_SAFE_BEGIN(&packet->owner->packet_queue, owner_packet, next) { + if (owner_packet == packet) { + /* Unlink the node from the list. */ + AST_LIST_REMOVE_CURRENT(next); + ao2_t_ref(owner_packet, -1, "Packet retransmission list (retransmission complete)"); + + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + /* 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->owner); + ao2_t_ref(packet, -1, "Scheduled packet retransmission complete"); + + return 0; +} + +/* Run by the sched thread. */ +static int __sip_packet_cancel_resend(const void *data) +{ + struct sip_packet *packet = (void *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, packet->retransid, ao2_t_ref(packet, -1, "Stop scheduled packet retransmission")); + ao2_t_ref(packet, -1, "Stop packet retransmission action"); + + return 0; +} + +void sip_packet_cancel_resend(struct sip_packet *packet) +{ + ao2_t_ref(packet, +1, "Stop packet retransmission action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_packet_cancel_resend, packet) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(packet, -1, "Failed to schedule stop packet retransmission action"); + } +} + +static void sip_packet_free(void *data) +{ + struct sip_packet *packet = (void *) data; + + if (packet->owner) { + ao2_t_cleanup(packet->owner, "Retransmission packet is being destroyed"); + } + + ast_free(packet->data); +} + +/* Acknowledges receipt of a packet and stops retransmission, called with pvt locked */ +int sip_packet_ack(struct sip_pvt *pvt, uint32_t seqno, int is_resp, int method) +{ + struct sip_packet *packet; + const char *msg = "Not Found"; /* used only for debugging */ + int res = FALSE; + + /* 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 (pvt->outboundproxy && !pvt->outboundproxy->force) { + sip_proxy_ref(pvt, NULL); + } + + AST_LIST_TRAVERSE_SAFE_BEGIN(&pvt->packet_queue, packet, next) { + if (packet->seqno != seqno || packet->is_resp != is_resp) { + continue; + } + + if (packet->is_resp || packet->method == method) { + res = TRUE; + msg = "Found"; + + if (!is_resp && seqno == pvt->pendinginvite) { + ast_debug(1, "Acked pending invite %u\n", pvt->pendinginvite); + pvt->pendinginvite = 0; + } + + if (packet->retransid > -1) { + if (sip_debug) { + ast_debug(4, "SIP TIMER: Cancelling retransmit of packet (reply received) Retransid #%d\n", packet->retransid); + } + } + + /* Unlink and destroy the packet object. */ + AST_LIST_REMOVE_CURRENT(next); + sip_packet_cancel_resend(packet); + + ao2_t_ref(packet, -1, "Packet retransmission list"); + + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + ast_debug(1, "Stopping retransmission on '%s' of %s %u: Match %s\n", pvt->callid, is_resp ? "Response" : "Request", seqno, msg); + + return res; +} + +/* Pretend to ack all packets, called with pvt locked */ +void sip_packet_pretend_ack(struct sip_pvt *pvt) +{ + struct sip_packet *packet = NULL; + + while (!AST_LIST_EMPTY(&pvt->packet_queue)) { + int method; + + if (packet == AST_LIST_FIRST(&pvt->packet_queue)) { + ast_log(LOG_WARNING, "Have a packet that doesn't want to give up! %s\n", sip_methods[packet->method].name); + return; + } + + packet = AST_LIST_FIRST(&pvt->packet_queue); + method = packet->method ? packet->method : sip_method_find(ast_str_buffer(packet->data)); + + sip_packet_ack(pvt, packet->seqno, packet->is_resp, method); + } +} + +/* Acks receipt of packet, keep it around (used for provisional responses) */ +int sip_packet_semi_ack(struct sip_pvt *pvt, uint32_t seqno, int is_resp, int method) +{ + struct sip_packet *packet; + int res = FALSE; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&pvt->packet_queue, packet, next) { + if (packet->seqno == seqno && packet->is_resp == is_resp && + (packet->is_resp || sip_method_cmp(method, ast_str_buffer(packet->data)))) { + /* this is our baby */ + if (packet->retransid > -1) { + if (sip_debug) { + ast_debug(4, "SIP TIMER: Cancelling retransmission #%d - %s (got response)\n", + packet->retransid, sip_methods[method].name); + } + } + + sip_packet_cancel_resend(packet); + res = TRUE; + + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + ast_debug(1, "(Provisional) Stopping retransmission (but retaining packet) on '%s' %s %u: %s\n", + pvt->callid, is_resp ? "Response" : "Request", seqno, res == -1 ? "Not Found" : "Found"); + + return res; +} diff -durN asterisk-22.2.0.orig/channels/sip/parking.c asterisk-22.2.0/channels/sip/parking.c --- asterisk-22.2.0.orig/channels/sip/parking.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/parking.c 2025-02-18 17:14:46.816902431 +1300 @@ -0,0 +1,340 @@ +/* + * 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/pvt.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_pvt *pvt; + struct ast_channel *chan; + int monitor:1; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(context); + AST_STRING_FIELD(callid); + AST_STRING_FIELD(tag); + AST_STRING_FIELD(theirtag); + 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 parkingspace, long unsigned int timeout); + +/* Handle remotecc park requests */ +int sip_remotecc_park(struct sip_pvt *pvt, struct sip_request *req, 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, 128))) { + return -1; + } + + ao2_t_bump(pvt, "copying dialog pvt into park_data struct"); + park_data->pvt = pvt; + + sip_request_copy(&pvt->initreq, req); + park_data->monitor = !strcmp(remotecc_data->softkeyevent, "ParkMonitor"); + + ast_string_field_set(park_data, context, peer->context); + ast_string_field_set(park_data, callid, remotecc_data->dialogid.callid); + ast_string_field_set(park_data, tag, remotecc_data->dialogid.remotetag); + ast_string_field_set(park_data, theirtag, remotecc_data->dialogid.localtag); + + if (ast_pthread_create_detached_background(&threadid, NULL, sip_park_thread, park_data)) { + ao2_t_cleanup(park_data->pvt, "thread creation failed"); + + 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_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct sip_remotecc_data *remotecc_data) +{ + return sip_remotecc_park(pvt, req, peer, remotecc_data); +} + +/* park call */ +static void *sip_park_thread(void *data) +{ + struct sip_park_data *park_data = data; + struct sip_pvt *pvt; + struct ast_channel *chan, *bridged; + struct stasis_subscription *sub = NULL; + struct ast_exten *exten; + struct pbx_find_info find_info = {.stacklen = 0}; + int res = -1; + + if (!(pvt = sip_pvt_find(park_data->callid, park_data->tag, park_data->theirtag))) { + ast_debug(1, "call leg does not exist\n"); + goto cleanup; + } + + ao2_lock(pvt); + + if (!(chan = pvt->owner)) { + ast_debug(1, "no owner channel\n"); + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "drop pvt"); + + goto cleanup; + } + + ast_channel_ref(chan); + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "drop pvt"); + + if (!(bridged = ast_channel_bridge_peer(chan))) { + ast_debug(1, "no bridge channel"); + ast_channel_unref(chan); + + goto cleanup; + } + + /* needed so that comebacktoorigin will work */ + pbx_builtin_setvar_helper(bridged, "BLINDTRANSFER", ast_channel_name(chan)); + pbx_builtin_setvar_helper(bridged, "PARKINGLOT", ast_channel_parkinglot(chan)); + + park_data->chan = chan; + ast_channel_ref(chan); + ast_string_field_set(park_data, uniqueid, ast_channel_uniqueid(bridged)); + + sub = 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(chan), + ast_channel_uniqueid(bridged), ast_channel_uniqueid(chan), exten ? ast_get_extension_app_data(exten) : NULL); + + ast_channel_unref(chan); + ast_channel_unref(bridged); + + sip_send_response(park_data->pvt, "202 Accepted", &park_data->pvt->initreq); + res = 0; + +cleanup: + if (res) { + sip_send_response(park_data->pvt, "503 Service Unavailable", &park_data->pvt->initreq); + sip_park_send_notify(park_data, PARKED_CALL_FAILED, 0, 0); + + if (sub) { + stasis_unsubscribe(sub); + } + + if (park_data->chan) { + ast_channel_unref(park_data->chan); + } + + ao2_t_cleanup(park_data->pvt, "drop park_data->pvt"); + + ast_string_field_free_memory(park_data); + ast_free(park_data); + } + + return NULL; +} + +static void sip_park_event(void *data, struct stasis_subscription *sub, struct stasis_message *msg) +{ + struct sip_park_data *park_data = data; + struct ast_parked_call_payload *payload; + + if (stasis_message_type(msg) != ast_parked_call_type()) { + return; + } + + if (!stasis_subscription_final_message(sub, msg)) { + payload = stasis_message_data(msg); + + 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->chan, AST_SOFTHANGUP_EXPLICIT); + + ast_channel_unref(park_data->chan); + park_data->chan = NULL; + } + + if (park_data->monitor && (payload->event_type == PARKED_CALL || payload->event_type == PARKED_CALL_REMINDER)) { + return; + } + } + + stasis_unsubscribe(sub); + ast_channel_cleanup(park_data->chan); + + ao2_t_cleanup(park_data->pvt, "drop park_data->pvt"); + + 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 parkingspace, long unsigned int timeout) +{ + struct ast_str *content = ast_str_alloca(8192); + + if (park_data->monitor) { + struct sip_request req; + const char *fromdomain; + char *parkevent; + + if (event_type == PARKED_CALL) { + parkevent = "parked"; + } else if (event_type == PARKED_CALL_REMINDER) { + parkevent = "reminder"; + } else if (event_type == PARKED_CALL_UNPARKED) { + parkevent = "retrieved"; + } else if (event_type == PARKED_CALL_TIMEOUT) { + parkevent = "forwarded"; + } else if (event_type == PARKED_CALL_GIVEUP) { + parkevent = "abandoned"; + } else if (event_type == PARKED_CALL_FAILED) { + parkevent = "error"; + } else { + return; + } + + ao2_lock(park_data->pvt); + + if (!ast_test_flag(&park_data->pvt->flags[1], SIP_DIALOG_ESTABLISHED)) { + ast_set_flag(&park_data->pvt->flags[0], SIP_OUTGOING); + ast_set_flag(&park_data->pvt->flags[1], SIP_DIALOG_ESTABLISHED); + + park_data->pvt->subscribed = SIP_SUBSCRIBED_DIALOG_INFO_XML; + park_data->pvt->expiry = timeout; + + sip_request_init(&req, park_data->pvt, SIP_NOTIFY, NULL); + } else { + sip_request_prepare(&req, park_data->pvt, SIP_NOTIFY, 0, TRUE); + } + + park_data->pvt->dialogver++; + sip_request_add_header(&req, "Event", "refer"); + + if (event_type == PARKED_CALL || event_type == PARKED_CALL_REMINDER) { + sip_request_build_header(&req, "Subscription-State", "active;expires=%d", park_data->pvt->expiry); + } else { + sip_request_add_header(&req, "Subscription-State", "terminated;reason=noresource"); + } + + sip_request_add_header(&req, "Content-Type", "application/dialog-info+xml"); + fromdomain = S_OR(park_data->pvt->fromdomain, ast_sockaddr_stringify_host_remote(&park_data->pvt->ourip)); + + ast_str_append(&content, 0, "\n"); + /* "parmams" is a typo in the the Cisco API, duh. */ + ast_str_append(&content, 0, "\n", park_data->pvt->dialogver, parkingspace, fromdomain); + ast_str_append(&content, 0, "\n", parkingspace); + + 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", parkevent); + ast_str_append(&content, 0, "sip:%d@%s\n", parkingspace, fromdomain); + ast_str_append(&content, 0, "sip:%d@%s\n", parkingspace, fromdomain); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + sip_request_add_content(&req, ast_str_buffer(content)); + sip_request_send(park_data->pvt, &req, SIP_SEND_RELIABLE, park_data->pvt->ocseq++); + + ao2_unlock(park_data->pvt); + } else { + struct sip_pvt *pvt; + + if (!(pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + return; + } + + sip_pvt_copy_data(pvt, park_data->pvt); + + 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->callid); + ast_str_append(&content, 0, "%s\n", park_data->theirtag); + ast_str_append(&content, 0, "%s\n", park_data->tag); + ast_str_append(&content, 0, "\n"); + + if (event_type == PARKED_CALL) { + ast_str_append(&content, 0, "\200! %d\n", parkingspace); + } else { + ast_str_append(&content, 0, "\200^\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"); + + ao2_lock(pvt); + sip_send_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_unlock(pvt); + + ao2_t_cleanup(pvt, "bump down the count of pvt since we're done with it."); + } +} diff -durN asterisk-22.2.0.orig/channels/sip/peers.c asterisk-22.2.0/channels/sip/peers.c --- asterisk-22.2.0.orig/channels/sip/peers.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/peers.c 2025-02-18 17:14:46.818902378 +1300 @@ -0,0 +1,2987 @@ +/* + * 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.h" +#include "include/peers.h" +#include "include/realtime.h" +#include "include/registry.h" +#include "include/mwi_subscriptions.h" +#include "include/pvt.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_unlink_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_remove_from_db(struct sip_peer *peer); +static void sip_peer_register_from_db(struct sip_peer *peer); + +static void sip_peer_set_defaults(struct sip_peer *peer); +static int sip_peer_keepalive(const void *data); + +static struct ast_variable *sip_variable_build(const char *buf, struct ast_variable *list); + +static void sip_mailbox_build(struct sip_peer *peer, const char *value); +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 *value, int *lineindex); +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 *value); +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_addr; + +/* 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 *obj, const int flags) +{ + const struct sip_peer *peer = obj; + + return ast_str_case_hash(peer->name); +} + +/* The only member of the peer used here is the name field */ +int sip_peer_cmp(void *obj, void *arg, int flags) +{ + struct sip_peer *peer = obj, *peer2 = arg; + + return !strcasecmp(peer->name, peer2->name) ? CMP_MATCH | CMP_STOP : 0; +} + +/* Hash function based on the peer's ip address. For IPv6, we use the end of the address. */ +int sip_peer_hash_addr(const void *obj, const int flags) +{ + const struct sip_peer *peer = obj; + int ret = 0; + + if (ast_sockaddr_isnull(&peer->addr)) { + ast_log(LOG_ERROR, "Empty address\n"); + } + + ret = ast_sockaddr_hash(&peer->addr); + + if (ret < 0) { + ret = -ret; + } + + return ret; +} + +/* 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_addr(void *obj, void *arg, int flags) +{ + struct sip_peer *peer1 = obj, *peer2 = arg; + + if (!(peer1->transports & peer2->transports)) { + /* transport setting doesn't match */ + return 0; + } + + /* At this point, we match the callback extension if we need to. Carry on. */ + return !ast_sockaddr_cmp(&peer1->addr, &peer2->addr) ? (CMP_MATCH | CMP_STOP) : 0; +} + +struct sip_peer *sip_peer_find(const char *name, struct ast_sockaddr *addr, int realtime, int devstate_only, int transport) +{ + struct sip_peer *peer = NULL; + + if (name) { + struct sip_peer tmp_peer = {.name = name}; + + peer = ao2_t_find(sip_peers, &tmp_peer, OBJ_POINTER, "ao2_find in peers table"); + } else if (addr) { /* search by addr? */ + struct sip_peer tmp_peer = {.addr = *addr, .transports = transport}; + + peer = ao2_t_find(sip_peers_by_addr, &tmp_peer, OBJ_POINTER, "ao2_find in sip_peers_by_addr table"); + } + + if (!peer && (realtime || devstate_only)) { + peer = sip_realtime_load(name, addr, devstate_only); + } + + return peer; +} + +int sip_peer_set_removed(void *data, void *arg, int flags) +{ + struct sip_peer *peer = data; + + peer->removed = TRUE; + + return 0; +} + +static void sip_peer_unlink_sched(struct sip_peer *peer) +{ + if (peer->qualifyexpire != -1) { + AST_SCHED_DEL_UNREF(sip_sched_context, peer->qualifyexpire, ao2_t_cleanup(peer, "removing qualify peer ref")); + } + + if (peer->expire != -1) { + AST_SCHED_DEL_UNREF(sip_sched_context, peer->expire, ao2_t_cleanup(peer, "remove register expire ref")); + } + + if (peer->keepalivesend != -1) { + AST_SCHED_DEL_UNREF(sip_sched_context, peer->keepalivesend, ao2_t_cleanup(peer, "remove keepalive peer ref")); + } +} + +/* 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 = data; + int removed_only = *(int *) arg; + + if (!removed_only || peer->removed) { + sip_peer_unlink_sched(peer); + + if (peer->dnsmgr) { + ast_dnsmgr_release(peer->dnsmgr); + peer->dnsmgr = NULL; + ao2_t_cleanup(peer, "Release peer from dnsmgr"); + } + + 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, + "initiating callback to remove marked peers"); + ao2_t_callback(sip_peers_by_addr, OBJ_UNLINK | OBJ_MULTIPLE | OBJ_NODATA, + sip_peer_unlink, &marked_only, "initiating callback to remove marked sip_peers_by_addr"); +} + +/* Destroy peer object from memory */ +static void sip_peer_destroy(void *data) +{ + struct sip_peer *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->outboundproxy) { + ao2_ref(peer->outboundproxy, -1); + peer->outboundproxy = NULL; + } + + /* Delete it, it needs to disappear */ + if (peer->qualifypvt) { + sip_pvt_unlink(peer->qualifypvt); + ao2_t_cleanup(peer->qualifypvt, "peer->qualifypvt is being unset"); + + peer->qualifypvt = NULL; + } + + if (peer->mwipvt) { /* We have an active subscription, delete it */ + sip_pvt_unlink(peer->mwipvt); + ao2_t_cleanup(peer->mwipvt, "unreffing peer->mwipvt"); + + peer->mwipvt = NULL; + } + + if (peer->fepvt) { /* We have an active subscription, delete it */ + sip_pvt_unlink(peer->fepvt); + ao2_t_cleanup(peer->fepvt, "unreffing peer->fepvt"); + + peer->fepvt = NULL; + } + + if (peer->callback) { + sip_callback_destroy(peer); + peer->callback = NULL; + } + + if (peer->chanvars) { + ast_variables_destroy(peer->chanvars); + peer->chanvars = NULL; + } + + sip_route_clear(&peer->path); + sip_peer_register_exten(peer, FALSE); + + ast_free_acl_list(peer->acl); + ast_free_acl_list(peer->contactacl); + ast_free_acl_list(peer->directmediaacl); + + if (!ast_test_flag(&sip_config.flags[1], SIP_RTCACHEFRIENDS) && peer->is_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) { + ao2_t_ref(peer->auth, -1, "Removing peer authentication"); + peer->auth = 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->caps); + + 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.peer_rtupdate && (peer->is_realtime || ast_test_flag(&peer->flags[1], SIP_RTCACHEFRIENDS))) { + sip_realtime_update(peer, expiry); + } +} + +/* Automatically add peer extension to dial plan */ +void sip_peer_register_exten(struct sip_peer *peer, int add) +{ + char *regexten, *exten, *context; + struct pbx_find_info find_info = {.stacklen = 0}; + + /* XXX note that sip_config.regcontext is both a global 'enable' flag and the name of the global regexten context, if not + * specified individually. */ + if (ast_strlen_zero(sip_config.regcontext)) { + return; + } + + regexten = ast_strdup(S_OR(peer->regexten, peer->name)); + + while ((exten = strsep(®exten, "&"))) { + if ((context = strchr(exten, '@'))) { + *context++ = '\0'; /* split exten@context */ + + if (!ast_context_find(context)) { + ast_log(LOG_WARNING, "Context %s must exist in regcontext= in sip.conf!\n", context); + continue; + } + } else { + context = sip_config.regcontext; + } + + if (add) { + if (!ast_exists_extension(NULL, context, exten, 1, NULL)) { + ast_add_extension(context, 1, exten, 1, NULL, NULL, "Noop", ast_strdup(peer->name), ast_free_ptr, "SIP"); + } + } else if (pbx_find_extension(NULL, NULL, &find_info, context, exten, 1, NULL, "", E_MATCH)) { + ast_context_remove_extension(context, exten, 1, NULL); + } + } +} + +/* Get cached MWI info */ +int sip_peer_get_cached_mwi(struct sip_peer *peer, int *newmsgs, int *oldmsgs) +{ + struct sip_mailbox *mailbox; + int in_cache = FALSE; + + AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, next) { + RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); + struct ast_mwi_state *mwi_state; + + msg = stasis_cache_get(ast_mwi_state_cache(), ast_mwi_state_type(), mailbox->name); + + if (!msg) { + continue; + } + + mwi_state = stasis_message_data(msg); + + *newmsgs += mwi_state->new_msgs; + *oldmsgs += 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 = 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_pvt 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_pvt *pvt; + int newmsgs = 0, oldmsgs = 0; + const char *vmexten = NULL; + + ao2_lock(peer); + + if (peer->vmexten) { + vmexten = ast_strdupa(peer->vmexten); + } + + if (ast_test_flag((&peer->flags[1]), SIP_SUBSCRIBEMWIONLY) && !peer->mwipvt) { + sip_peer_set_lastmsgs(peer, -1, 0, TRUE); + ao2_unlock(peer); + + return -1; + } + + /* Do we have an IP address? If not, skip this peer */ + if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { + sip_peer_set_lastmsgs(peer, -1, 0, TRUE); + ao2_unlock(peer); + + return -1; + } + + /* Attempt to use cached mwi to get message counts. */ + if (!sip_peer_get_cached_mwi(peer, &newmsgs, &oldmsgs) && !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_lastmsgs(peer, -1, 0, FALSE); + return 0; + } + + ast_app_inboxcount(ast_str_buffer(mailboxes), &newmsgs, &oldmsgs); + ao2_lock(peer); + } + + if (peer->mwipvt) { + /* Base message on subscription */ + pvt = ao2_t_bump(peer->mwipvt, "sip_peer_send_mwi: Setting dialog ptr pvt from peer->mwipvt"); + ao2_unlock(peer); + } else { + ao2_unlock(peer); + + /* Build temporary dialog for this message */ + if (!(pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_NOTIFY, NULL, 0))) { + sip_peer_set_lastmsgs(peer, -1, 0, FALSE); + return -1; + } + + /* If we don't set the socket type to 0, then sip_pvt_build_from_peer will fail immediately if the peer + * uses any transport other than UDP. We set the type to 0 here and then let sip_pvt_build_from_peer copy + * the peer's socket information to the sip_pvt we just allocated. */ + sip_socket_set_transport(&pvt->socket, 0); + + if (sip_pvt_build_from_peer(pvt, peer)) { + /* Maybe they're not registered, etc. */ + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "unref dialog pvt just created via sip_pvt_alloc"); + + sip_peer_set_lastmsgs(peer, -1, 0, FALSE); + return -1; + } + + /* Recalculate our side, and recalculate Call ID */ + sip_pvt_set_ouraddrfor(pvt, &pvt->sa, &pvt->ourip); + sip_pvt_build_via(pvt); + + ao2_lock(peer); + + if (!ast_strlen_zero(peer->mwi_from)) { + ast_string_field_set(pvt, mwi_from, peer->mwi_from); + } else if (!ast_strlen_zero(sip_config.mwi_from)) { + ast_string_field_set(pvt, mwi_from, sip_config.mwi_from); + } + + ao2_unlock(peer); + + /* Change the dialog callid. */ + sip_pvt_change_callid(pvt, NULL); + /* Destroy this session after 32 secs */ + sip_pvt_sched_destroy(pvt, 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(pvt); + + /* Send MWI */ + ast_set_flag(&pvt->flags[0], SIP_OUTGOING); + /* the following will decrement the refcount on pvt as it finishes */ + sip_send_notify_with_mwi(pvt, newmsgs, oldmsgs, vmexten); + + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "unref pvt just before it goes out of scope at the end of sip_peer_send_mwi."); + + sip_peer_set_lastmsgs(peer, newmsgs, oldmsgs, 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 '%s'\n", alias->name); + alias->peer->lastms = 0; + memset(&alias->peer->addr, 0, sizeof(alias->peer->addr)); + + 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, fullcontact, ""); + 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); + sip_peer_register_exten(alias->peer, FALSE); + + ao2_t_cleanup(alias->peer, "unref after sip_peer_find"); + alias->peer = NULL; + } +} + +/* Update bulk-register aliases */ +void sip_peer_register_aliases(struct sip_peer *peer) +{ + struct sip_alias *alias; + char *scheme, *hostport; + + if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { + return; + } + + scheme = ast_strdupa(peer->fullcontact); + + if ((hostport = strchr(scheme, ':')) != NULL) { + *hostport++ = '\0'; + + if ((hostport = strchr(hostport, '@')) != NULL) { + *hostport++ = '\0'; + } + } + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (!alias->peer) { + if (!(alias->peer = sip_peer_find(alias->name, NULL, TRUE, FALSE, 0))) { + ast_log(LOG_WARNING, "No such register peer '%s'\n", alias->name); + continue; + } + + /* remove any schedules that may have been created */ + sip_peer_unlink_sched(alias->peer); + } + + /* These settings could have been overwritten by a reload */ + alias->peer->cisco_lineindex = alias->lineindex; + ast_string_field_set(alias->peer, username, alias->name); + + ast_string_field_set(alias->peer, cisco_authname, 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, regcallid, peer->regcallid); + ast_string_field_set(alias->peer, cisco_devicename, peer->cisco_devicename); + ast_string_field_set(alias->peer, useragent, peer->useragent); + + alias->peer->donotdisturb = peer->donotdisturb; + alias->peer->huntgroup = peer->huntgroup; + + /* Peer hasn't changed */ + if (!ast_sockaddr_cmp(&peer->addr, &alias->peer->addr)) { + continue; + } + + alias->peer->portinuri = peer->portinuri; + alias->peer->fromdomainport = peer->fromdomainport; + alias->peer->sipoptions = peer->sipoptions; + + ast_string_field_build(alias->peer, fullcontact, "%s:%s@%s", scheme, alias->peer->name, hostport); + + alias->peer->addr = peer->addr; + sip_socket_copy_data(&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->addr)); + 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); + sip_peer_register_exten(alias->peer, TRUE); + + ast_verb(3, "Registered SIP '%s' at %s\n", alias->peer->name, ast_sockaddr_stringify(&alias->peer->addr)); + alias->peer->offhook = 0; + + sip_peer_update(alias->peer, sip_config.max_expiry); + sip_peer_set_lastmsgs(alias->peer, -1, 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, "iterate thru peers table"))) { + ao2_lock(peer); + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + if (alias->peer) { + alias->peer->addr = peer->addr; + } + } + + sip_peer_register_aliases(peer); + + ao2_unlock(peer); + ao2_t_cleanup(peer, "toss iterator peer ptr"); + } + + ao2_iterator_destroy(&iter); +} + +/* Send donotdisturb, call forward and huntgroup in one bulk update */ +int sip_peer_send_bulkupdate(struct sip_peer *peer) +{ + if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { + return 0; + } + + if (ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER)) { + struct sip_pvt *pvt; + struct ast_str *content; + int newmsgs = 0, oldmsgs = 0; + struct sip_alias *alias; + + if (!(pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + return -1; + } + + if (!(content = ast_str_create(8192))) { + ao2_t_cleanup(pvt, "drop pvt"); + return -1; + } + + /* Don't use sip_pvt_build_from_peer here as it may fail due to the peer not having responded to an OPTIONS request yet */ + pvt->sa = peer->addr; + pvt->recv = peer->addr; + + sip_socket_copy_data(&pvt->socket, &peer->socket); + + ast_copy_flags(&pvt->flags[0], &peer->flags[0], SIP_FLAGS0_TO_COPY); + ast_copy_flags(&pvt->flags[1], &peer->flags[1], SIP_FLAGS1_TO_COPY); + ast_copy_flags(&pvt->flags[2], &peer->flags[2], SIP_FLAGS2_TO_COPY); + + /* Recalculate our side, and recalculate Call ID */ + sip_pvt_set_ouraddrfor(pvt, &pvt->sa, &pvt->ourip); + sip_pvt_change_callid(pvt, NULL); + + if (!ast_strlen_zero(peer->tohost)) { + ast_string_field_set(pvt, tohost, peer->tohost); + } else { + ast_string_field_set(pvt, tohost, ast_sockaddr_stringify_host_remote(&peer->addr)); + } + + if (!pvt->portinuri) { + pvt->portinuri = peer->portinuri; + } + + if (peer->fromdomainport) { + pvt->fromdomainport = peer->fromdomainport; + } + + ast_string_field_set(pvt, fullcontact, peer->fullcontact); + ast_string_field_set(pvt, 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->donotdisturb ? "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->huntgroup ? "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"); + + if (!sip_peer_get_cached_mwi(peer, &newmsgs, &oldmsgs)) { + 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), &newmsgs, &oldmsgs); + sip_peer_set_lastmsgs(peer, newmsgs, oldmsgs, FALSE); + } else { + sip_peer_set_lastmsgs(peer, -1, 0, FALSE); + } + } + + ast_str_append(&content, 0, "\n", peer->cisco_lineindex); + ast_str_append(&content, 0, "%s\n", newmsgs ? "yes" : "no"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n", newmsgs, oldmsgs); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", peer->callforward); + ast_str_append(&content, 0, "%s\n", !ast_strlen_zero(peer->vmexten) && !strcmp(peer->callforward, peer->vmexten) ? "on" : "off"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + AST_LIST_TRAVERSE(&peer->aliases, alias, next) { + int newmsgs = 0, oldmsgs = 0; + + if (!alias->peer) { + continue; + } + + if (!sip_peer_get_cached_mwi(alias->peer, &newmsgs, &oldmsgs)) { + 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), &newmsgs, &oldmsgs); + sip_peer_set_lastmsgs(peer, newmsgs, oldmsgs, FALSE); + } else { + sip_peer_set_lastmsgs(peer, -1, 0, FALSE); + } + } + + ast_str_append(&content, 0, "\n", alias->peer->cisco_lineindex); + ast_str_append(&content, 0, "%s\n", newmsgs ? "yes" : "no"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n", newmsgs, oldmsgs); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", alias->peer->callforward); + ast_str_append(&content, 0, "%s\n", !ast_strlen_zero(alias->peer->vmexten) && !strcmp(alias->peer->callforward, alias->peer->vmexten) ? "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(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(pvt, "bump down the count of pvt since we're done with it."); + + ast_free(content); + } else if (peer->fepvt) { + struct sip_request req; + struct sip_pvt *pvt = peer->fepvt; + char content[SIP_BUFFER_SIZE], boundary[32]; + + ast_set_flag(&pvt->flags[0], SIP_OUTGOING); + sip_request_prepare(&req, pvt, SIP_NOTIFY, 0, TRUE); + + snprintf(boundary, sizeof(boundary), "%08lx%08lx%08lx", ast_random(), ast_random(), ast_random()); + + sip_request_add_header(&req, "Event", "as-feature-event"); + sip_request_add_header(&req, "Subscription-State", pvt->expiry ? "active" : "terminated;reason=timeout"); + sip_request_build_header(&req, "Content-Type", "multipart/mixed; boundary=%s", boundary); + + snprintf(content, sizeof(content), "--%s\r\n", boundary); + sip_request_add_content(&req, content); + sip_request_add_content(&req, "Content-Type: application/x-as-feature-event+xml\r\n"); + sip_request_add_content(&req, "\r\n"); + sip_request_add_content(&req, "\n"); + sip_request_add_content(&req, "\n"); + snprintf(content, sizeof(content), "\n%s\n", peer->donotdisturb ? "true" : "false"); + sip_request_add_content(&req, content); + sip_request_add_content(&req, "\n"); + sip_request_add_content(&req, "\r\n"); + + snprintf(content, sizeof(content), "--%s\r\n", boundary); + sip_request_add_content(&req, content); + sip_request_add_content(&req, "Content-Type: application/x-as-feature-event+xml\r\n"); + sip_request_add_content(&req, "\r\n"); + sip_request_add_content(&req, "\n"); + sip_request_add_content(&req, "\n"); + sip_request_add_content(&req, "\nforwardImmediate\n"); + snprintf(content, sizeof(content), "%s\n%s\n", !ast_strlen_zero(peer->callforward) ? "true" : "false", peer->callforward); + sip_request_add_content(&req, content); + sip_request_add_content(&req, "\n"); + sip_request_add_content(&req, "\r\n"); + snprintf(content, sizeof(content), "--%s--\r\n", boundary); + + sip_request_add_content(&req, content); + sip_request_send(pvt, &req, SIP_SEND_RELIABLE, pvt->ocseq); + } + + return 0; +} + +/* Notify peer that the do not disturb status has changed */ +int sip_peer_send_donotdisturb(struct sip_peer *peer) +{ + if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { + return 0; + } + + if (ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER)) { + struct sip_pvt *pvt; + struct ast_str *content = ast_str_alloca(8192); + + if (!(pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + return -1; + } + + sip_socket_set_transport(&pvt->socket, 0); + + if (sip_pvt_build_from_peer(pvt, peer)) { + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "sip_pvt_build_from_peer failed in sip_peer_send_donotdisturb. Unref dialog"); + + return -1; + } + + 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->donotdisturb ? "enable" : "disable"); + ast_str_append(&content, 0, "\n", ast_test_flag(&pvt->flags[2], SIP_DND_BUSY) ? "callreject" : "ringeroff"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + sip_send_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_t_cleanup(pvt, "bump down the count of pvt since we're done with it."); + } else if (peer->fepvt) { + struct sip_request req; + struct sip_pvt *pvt = peer->fepvt; + char tmp[512]; + + sip_request_prepare(&req, pvt, SIP_NOTIFY, 0, TRUE); + + sip_request_add_header(&req, "Event", "as-feature-event"); + sip_request_add_header(&req, "Subscription-State", pvt->expiry ? "active" : "terminated;reason=timeout"); + sip_request_add_header(&req, "Content-Type", "application/x-as-feature-event+xml"); + + sip_request_add_content(&req, "\n"); + sip_request_add_content(&req, "\n"); + snprintf(tmp, sizeof(tmp), "\n%s\n", peer->donotdisturb ? "true" : "false"); + sip_request_add_content(&req, tmp); + sip_request_add_content(&req, "\n"); + + sip_request_send(pvt, &req, SIP_SEND_RELIABLE, pvt->ocseq); + } + + return 0; +} + +/* Notify peer that the huntgroup login state has changed */ +int sip_peer_send_huntgroup(struct sip_peer *peer) +{ + if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { + return 0; + } + + if (ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER)) { + struct sip_pvt *pvt; + struct ast_str *content = ast_str_alloca(8192); + + if (!(pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + return -1; + } + + sip_socket_set_transport(&pvt->socket, 0); + + if (sip_pvt_build_from_peer(pvt, peer)) { + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "sip_pvt_build_from_peer failed in sip_peer_send_huntgroup. Unref dialog"); + + return -1; + } + + 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->huntgroup ? "on" : "off"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + sip_send_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_t_cleanup(pvt, "bump down the count of pvt since we're done with it."); + } + + return 0; +} + +/* Notify peer that the call forwarding extension has changed */ +int sip_peer_send_callforward(struct sip_peer *peer) +{ + if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { + return 0; + } + + if (ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER)) { + struct sip_pvt *pvt; + struct ast_str *content = ast_str_alloca(8192); + + if (!(pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0))) { + return -1; + } + + sip_socket_set_transport(&pvt->socket, 0); + + if (sip_pvt_build_from_peer(pvt, peer)) { + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "sip_pvt_build_from_peer failed in sip_peer_send_callforward. Unref dialog"); + + return -1; + } + + 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_lineindex); + ast_str_append(&content, 0, "%s\n", peer->callforward); + ast_str_append(&content, 0, "%s\n", !ast_strlen_zero(peer->vmexten) && !strcmp(peer->callforward, peer->vmexten) ? "on" : "off"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + sip_send_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + ao2_t_cleanup(pvt, "bump down the count of pvt since we're done with it."); + } else if (peer->fepvt) { + struct sip_request req; + struct sip_pvt *pvt = peer->fepvt; + char tmp[512]; + + sip_request_prepare(&req, pvt, SIP_NOTIFY, 0, TRUE); + + sip_request_add_header(&req, "Event", "as-feature-event"); + sip_request_add_header(&req, "Subscription-State", pvt->expiry ? "active" : "terminated;reason=timeout"); + sip_request_add_header(&req, "Content-Type", "application/x-as-feature-event+xml"); + + sip_request_add_content(&req, "\n"); + sip_request_add_content(&req, "\n"); + sip_request_add_content(&req, "\nforwardImmediate\n"); + snprintf(tmp, sizeof(tmp), "%s\n%s\n", !ast_strlen_zero(peer->callforward) ? "true" : "false", peer->callforward); + sip_request_add_content(&req, tmp); + sip_request_add_content(&req, "\n"); + + sip_request_send(pvt, &req, SIP_SEND_RELIABLE, pvt->ocseq); + } + + return 0; +} + +void sip_peer_send_qrt_url(struct sip_peer *peer) +{ + struct sip_pvt *pvt; + struct ast_str *content = ast_str_alloca(8192); + struct ast_str *url = ast_str_alloca(1024); + + if (ast_strlen_zero(peer->cisco_qrt_url)) { + return; + } + + ast_str_set(&url, 0, "%s", peer->cisco_qrt_url); + ast_str_append(&url, 0, "%sname=%s", strchr(ast_str_buffer(url), '?') ? "&" : "?", peer->cisco_devicename); + + if (!((pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0)))) { + return; + } + + sip_socket_set_transport(&pvt->socket, 0); + + if (sip_pvt_build_from_peer(pvt, peer)) { + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "sip_pvt_build_from_peer failed"); + + return; + } + + 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(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(pvt, "bump down the count of pvt since we're done with it."); +} + +/* Remove registration data from realtime database or AST/DB when registration expires */ +static void sip_peer_remove_from_db(struct sip_peer *peer) +{ + int realtimeregs = ast_check_realtime("sipregs"); + char *tablename = realtimeregs ? "sipregs" : "sippeers"; + + if (!sip_config.ignore_regexpire) { + if (peer->rt_fromcontact && sip_config.peer_rtupdate) { + ast_update_realtime(tablename, "name", peer->name, + "fullcontact", "", "ipaddr", "", "port", "0", "regseconds", "0", "regserver", "", "useragent", "", "lastms", "0", SENTINEL); + } else { + ast_db_del("SIP/Registry", peer->name); + ast_db_del("SIP/RegistryPath", peer->name); + ast_db_del("SIP/PeerMethods", peer->name); + } + } +} + +/* Expire registration of SIP peer */ +int sip_peer_expire_register(const void *data) +{ + struct sip_peer *peer = (struct sip_peer *) data; + struct sip_subscription *subscription; + + if (!peer) { /* Hmmm. We have no peer. Weird. */ + return 0; + } + + peer->expire = -1; + peer->portinuri = FALSE; + ast_string_field_set(peer, regcallid, ""); + + sip_peer_remove_from_db(peer); /* remove registration data from storage */ + sip_socket_set_transport(&peer->socket, peer->default_outbound_transport); + + if (peer->socket.tcptls_session) { + ao2_ref(peer->socket.tcptls_session, -1); + peer->socket.tcptls_session = NULL; + } + + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, next) { + if (subscription->pvt) { + sip_pvt_unlink(subscription->pvt); + ao2_t_cleanup(subscription->pvt, "destroying subscription"); + + subscription->pvt = 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); + } + + sip_peer_register_exten(peer, FALSE); /* Remove regexten */ + 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->is_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_RTAUTOCLEAR)) { + ao2_t_unlink(sip_peers, peer, "ao2_unlink of peer from peers table"); + } + + if (!ast_sockaddr_isnull(&peer->addr)) { + /* We still need to unlink the peer from the sip_peers_by_addr 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_addr, peer, "ao2_unlink of peer from sip_peers_by_addr table"); + } + + /* Only clear the addr after we check for destruction. The addr must remain + * in order to unlink from the sip_peers_by_addr container correctly */ + memset(&peer->addr, 0, sizeof(peer->addr)); + sip_peer_expire_aliases(peer); + + ao2_t_cleanup(peer, "removing peer ref for sip_peer_expire_register"); + + return 0; +} + +/* Get registration details from Asterisk DB */ +static void sip_peer_register_from_db(struct sip_peer *peer) +{ + char data[256]; + char path[SIP_BUFFER_SIZE * 2]; + struct ast_sockaddr sa; + int expire; + char full_addr[128]; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(addr); + AST_APP_ARG(port); + AST_APP_ARG(expiry_str); + AST_APP_ARG(username); + AST_APP_ARG(contact); + ); + + /* If read-only RT backend, then refresh from local DB cache */ + if (peer->rt_fromcontact && sip_config.peer_rtupdate) { + return; + } + + if (ast_db_get("SIP/Registry", peer->name, data, sizeof(data))) { + return; + } + + AST_NONSTANDARD_RAW_ARGS(args, data, ':'); + + snprintf(full_addr, sizeof(full_addr), "%s:%s", args.addr, args.port); + + if (!ast_sockaddr_parse(&sa, full_addr, 0)) { + return; + } + + if (args.expiry_str) { + expire = atoi(args.expiry_str); + } else { + return; + } + + if (args.username) { + ast_string_field_set(peer, username, args.username); + } + + if (args.contact) { + ast_string_field_set(peer, fullcontact, args.contact); + } + + ast_debug(2, "SIP Seeding peer from astdb: '%s' at %s@%s for %d\n", + peer->name, peer->username, ast_sockaddr_stringify_host(&sa), expire); + + ast_sockaddr_copy(&peer->addr, &sa); + + if (peer->maxms) { + /* Don't qualify peer immediately, just schedule it within qualifyfreq */ + AST_SCHED_REPLACE_UNREF(peer->qualifyexpire, sip_sched_context, + ast_random() % (peer->qualifyfreq ? peer->qualifyfreq : sip_config.qualify_freq) + 1, __sip_peer_qualify, peer, + ao2_t_cleanup(_data, "removing qualify peer ref"), + ao2_t_cleanup(peer, "removing qualify peer ref"), + ao2_t_bump(peer, "adding qualify peer ref")); + } + + AST_SCHED_REPLACE_UNREF(peer->expire, sip_sched_context, (expire + 10) * 1000, sip_peer_expire_register, peer, + ao2_t_cleanup(_data, "remove registration ref"), + ao2_t_cleanup(peer, "remove registration ref"), + ao2_t_bump(peer, "add registration ref")); + + sip_peer_register_exten(peer, TRUE); + + if (!ast_db_get("SIP/RegistryPath", peer->name, path, sizeof(path))) { + sip_parse_path(NULL, peer, NULL, path); + } +} + +/* Send initial subscription state updates to peer */ +void sip_peer_extension_state_subscriptions(struct sip_peer *peer) +{ + struct sip_subscription *subscription; + + if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { + return; + } + + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, next) { + struct sip_request req; + struct ao2_container *device_state_info = NULL; + struct sip_extension_state_data state_data = {0}; + char *subtype = NULL, *message = NULL; + + if (subscription->pvt) { + /* Peer hasn't changed, keep original pvt */ + if (!ast_sockaddr_cmp(&peer->addr, &subscription->pvt->sa)) { + continue; + } + + sip_pvt_unlink(subscription->pvt); + ao2_t_cleanup(subscription->pvt, "drop pvt"); + subscription->pvt = NULL; + } + + if (!subscription->pvt) { + if (!(subscription->pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_NOTIFY, NULL, 0))) { + return; + } + } + + /* Don't use sip_pvt_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->tohost)) { + ast_string_field_set(subscription->pvt, tohost, peer->tohost); + } else { + ast_string_field_set(subscription->pvt, tohost, ast_sockaddr_stringify_host_remote(&peer->addr)); + } + + subscription->pvt->portinuri = peer->portinuri; + subscription->pvt->fromdomainport = peer->fromdomainport; + + ast_string_field_set(subscription->pvt, fullcontact, peer->fullcontact); + ast_string_field_set(subscription->pvt, username, peer->username); + ast_string_field_set(subscription->pvt, fromuser, subscription->exten); + ast_string_field_set(subscription->pvt, fromname, ""); + + ast_string_field_set(subscription->pvt, context, subscription->context); + ast_string_field_set(subscription->pvt, exten, subscription->exten); + ast_string_field_build(subscription->pvt, subscribeuri, "%s@%s", subscription->exten, subscription->context); + + ast_copy_flags(&subscription->pvt->flags[0], &peer->flags[0], SIP_FLAGS0_TO_COPY); + ast_copy_flags(&subscription->pvt->flags[1], &peer->flags[1], SIP_FLAGS1_TO_COPY); + ast_copy_flags(&subscription->pvt->flags[2], &peer->flags[2], SIP_FLAGS2_TO_COPY); + + /* Notify is outgoing call */ + ast_set_flag(&subscription->pvt->flags[0], SIP_OUTGOING); + ast_set_flag(&subscription->pvt->flags[1], SIP_DIALOG_ESTABLISHED); + ast_set_flag(&subscription->pvt->flags[2], SIP_SUBSCRIPTIONSTATE_ACTIVE); + + subscription->pvt->subscribed = SIP_SUBSCRIBED_PIDF_XML; /* Needs to be configurable */ + subscription->pvt->expiry = 0; + + subscription->pvt->sa = peer->addr; + subscription->pvt->recv = peer->addr; + sip_socket_copy_data(&subscription->pvt->socket, &peer->socket); + + /* Recalculate our side, and recalculate Call ID */ + sip_pvt_set_ouraddrfor(subscription->pvt, &subscription->pvt->sa, &subscription->pvt->ourip); + sip_pvt_change_callid(subscription->pvt, NULL); + + sip_request_init(&req, subscription->pvt, SIP_NOTIFY, NULL); + sip_pvt_initial_request(subscription->pvt, &req); + + /* Because we only use this req to initialize the pvt's initreq we have to manually deallocate it */ + sip_request_free(&req); + + subscription->pvt->stateid = ast_extension_state_add_extended(subscription->pvt->context, subscription->pvt->exten, + sip_pvt_extension_state_event, subscription->pvt); + + if (subscription->pvt->stateid == -1) { + sip_pvt_unlink(subscription->pvt); + ao2_t_cleanup(subscription->pvt, "drop pvt"); + subscription->pvt = NULL; + + continue; + } + + ast_debug(1, "Adding subscription for %s@%s (%s)\n", subscription->exten, subscription->context, subscription->pvt->callid); + + state_data.exten_state = ast_extension_state_extended(NULL, subscription->context, subscription->exten, &device_state_info); + + if (state_data.exten_state < 0) { + ao2_cleanup(device_state_info); + continue; + } + + state_data.presence_state = ast_hint_presence_state(NULL, subscription->context, subscription->exten, &subtype, &message); + state_data.presence_subtype = subtype; + state_data.presence_message = message; + state_data.device_state_info = device_state_info; + + if (state_data.exten_state & AST_EXTENSION_RINGING) { + struct ast_channel *ringing = sip_find_ringing_channel(state_data.device_state_info); + + if (ringing) { + subscription->pvt->last_ringing_channel_time = ast_channel_creationtime(ringing); + ao2_ref(ringing, -1); + } + } + + sip_pvt_extension_state_update(subscription->pvt, &state_data, TRUE); + + ao2_cleanup(device_state_info); + ast_free(subtype); + ast_free(message); + } +} + +/* 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_mailbox_subscriptions(struct sip_peer *peer) +{ + struct sip_mailbox *mailbox; + + AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, next) { + if (mailbox->event_sub) { + continue; + } + + mailbox->event_sub = ast_mwi_subscribe_pool(mailbox->name, sip_peer_mwi_event, peer); + + if (mailbox->event_sub) { + stasis_subscription_accept_message_type(ast_mwi_subscriber_subscription(mailbox->event_sub), + stasis_subscription_change_type()); + } + } +} + +/* Helper function to update a peer's newmsgs and oldmsgs values */ +void sip_peer_set_lastmsgs(struct sip_peer *peer, int newmsgs, int oldmsgs, int locked) +{ + if (!locked) { + ao2_lock(peer); + } + + peer->newmsgs = newmsgs; + peer->oldmsgs = oldmsgs; + + if (!locked) { + ao2_unlock(peer); + } +} + +/* Report Peer status in character string */ +int sip_peer_get_status(struct sip_peer *peer, char *status, int statuslen) +{ + int res = 0; + + if (peer->maxms) { + if (peer->lastms < 0) { + ast_copy_string(status, "UNREACHABLE", statuslen); + } else if (peer->lastms > peer->maxms) { + snprintf(status, statuslen, "LAGGED (%d ms)", peer->lastms); + res = 1; + } else if (peer->lastms) { + snprintf(status, statuslen, "OK (%d ms)", peer->lastms); + res = 1; + } else { + ast_copy_string(status, "UNKNOWN", statuslen); + } + } else { + ast_copy_string(status, "Unmonitored", statuslen); + /* Checking if port is 0 */ + res = -1; + } + + 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, "allocate a peer struct"))) { + return NULL; + } + + if (ast_string_field_init(peer, 512)) { + ao2_t_ref(peer, -1, "failed to string_field_init, drop peer"); + return NULL; + } + + if (!(peer->caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + ao2_t_ref(peer, -1, "failed to allocate format capabilities, drop peer"); + return NULL; + } + + ast_string_field_set(peer, name, name); + sip_peer_set_defaults(peer); + + peer->host_dynamic = TRUE; + peer->expire = -1; + peer->qualifyexpire = -1; + peer->keepalivesend = -1; + + return peer; +} + +/* Set peer defaults before configuring specific configurations */ +static void sip_peer_set_defaults(struct sip_peer *peer) +{ + if (peer->expire < 0) { + /* Don't reset expire or port time during reload if we have an active registration */ + sip_peer_unlink_sched(peer); + sip_socket_set_transport(&peer->socket, AST_TRANSPORT_UDP); + } + + ast_copy_flags(&peer->flags[0], &sip_config.flags[0], SIP_FLAGS0_TO_COPY); + ast_copy_flags(&peer->flags[1], &sip_config.flags[1], SIP_FLAGS1_TO_COPY); + ast_copy_flags(&peer->flags[2], &sip_config.flags[2], SIP_FLAGS2_TO_COPY); + + ast_string_field_set(peer, context, sip_config.context); + ast_string_field_set(peer, messagecontext, sip_config.messagecontext); + ast_string_field_set(peer, subscribecontext, sip_config.subscribecontext); + ast_string_field_set(peer, language, sip_config.language); + ast_string_field_set(peer, mohinterpret, sip_config.mohinterpret); + ast_string_field_set(peer, mohsuggest, sip_config.mohsuggest); + ast_string_field_set(peer, engine, sip_config.engine); + + ast_sockaddr_setnull(&peer->addr); + ast_sockaddr_setnull(&peer->defaddr); + + ast_format_cap_append_from_cap(peer->caps, sip_config.caps, AST_MEDIA_TYPE_UNKNOWN); + peer->maxcallbitrate = sip_config.maxcallbitrate; + + peer->rtptimeout = sip_config.rtptimeout; + peer->rtpholdtimeout = sip_config.rtpholdtimeout; + peer->rtpkeepalive = sip_config.rtpkeepalive; + + peer->allowtransfer = sip_config.allowtransfer; + peer->autoframing = sip_config.autoframing; + + peer->t38_maxdatagram = sip_config.t38_maxdatagram; + peer->qualifyfreq = sip_config.qualify_freq; + + if (sip_config.callcounter) { + peer->call_limit = INT_MAX; + } + + ast_string_field_set(peer, description, ""); + ast_string_field_set(peer, vmexten, sip_config.vmexten); + + ast_string_field_set(peer, secret, ""); + ast_string_field_set(peer, remotesecret, ""); + ast_string_field_set(peer, md5secret, ""); + + ast_string_field_set(peer, cid_num, ""); + ast_string_field_set(peer, cid_name, ""); + ast_string_field_set(peer, cid_tag, ""); + + ast_string_field_set(peer, fromdomain, ""); + ast_string_field_set(peer, fromuser, ""); + ast_string_field_set(peer, regexten, ""); + + peer->callgroup = 0; + peer->pickupgroup = 0; + + peer->maxms = sip_config.qualify_maxms; + 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.min_se; + peer->stimer.max_se = sip_config.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_outbound_transport = sip_config.primary_transport; + + if (peer->outboundproxy) { + ao2_ref(peer->outboundproxy, -1); + peer->outboundproxy = NULL; + } + + peer->cisco_lineindex = 1; + peer->cisco_pickupnotify_timer = 5; +} + +/* Build peer from configuration (file or realtime static/dynamic) */ +struct sip_peer *sip_peer_build(const char *name, struct ast_variable *vars, struct ast_variable *alt_vars, int realtime, int devstate_only) +{ + struct ast_variable *var; + struct sip_peer *peer = NULL; + struct ast_acl_list *oldacl = NULL; + struct ast_acl_list *oldcontactacl = NULL; + struct ast_acl_list *olddirectmediaacl = NULL; + int found = 0; + int first_pass = TRUE; + uint16_t port = 0; + int timerb_set = FALSE, timert1_set = FALSE; + time_t regseconds = 0; + struct ast_flags peerflags[3] = {{(0)}}; + struct ast_flags mask[3] = {{(0)}}; + const char *srvlookup = NULL; + static int deprecated_username = TRUE; + int headercount = 0; + int alt_fullcontact = !!alt_vars; + struct ast_str *fullcontact = ast_str_alloca(512); + int acl_change_sub_needed = FALSE; + int lineindex = 2; + + if (!realtime || ast_test_flag(&sip_config.flags[1], SIP_RTCACHEFRIENDS)) { + struct sip_peer tmp_peer = {.name = name}; + + /* 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, &tmp_peer, OBJ_POINTER | OBJ_UNLINK, "find and unlink peer from peers table"); + } + + if (peer) { + /* Already in the list, remove it and it will be added back (or FREE'd) */ + found++; + + /* we've unlinked the peer from the peers container but not unlinked from the sip_peers_by_addr container yet this leads + * to a wrong refcounter and the peer object is never destroyed */ + if (!ast_sockaddr_isnull(&peer->addr)) { + ao2_t_unlink(sip_peers_by_addr, peer, "ao2_unlink peer from sip_peers_by_addr table"); + } + + if (!peer->removed) { + first_pass = FALSE; + } else { + ast_format_cap_remove_by_type(peer->caps, AST_MEDIA_TYPE_UNKNOWN); + } + } else { + if (!(peer = ao2_t_alloc(sizeof(*peer), sip_peer_destroy, "allocate a peer struct"))) { + return NULL; + } + + if (!(peer->endpoint = ast_endpoint_create("SIP", name))) { + ao2_t_ref(peer, -1, "failed to allocate endpoint, drop peer"); + return NULL; + } + + if (!(peer->caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + ao2_t_ref(peer, -1, "failed to allocate format capabilities, drop peer"); + return NULL; + } + + if (ast_string_field_init(peer, 512)) { + ao2_t_ref(peer, -1, "failed to string_field_init, drop peer"); + return NULL; + } + + if (realtime && !ast_test_flag(&sip_config.flags[1], SIP_RTCACHEFRIENDS)) { + 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->expire = -1; + peer->qualifyexpire = -1; + peer->keepalivesend = -1; + } + + /* Note that our peer HAS had its reference count increased */ + if (first_pass) { + oldacl = peer->acl; + peer->acl = NULL; + + oldcontactacl = peer->contactacl; + peer->contactacl = NULL; + + olddirectmediaacl = peer->directmediaacl; + peer->directmediaacl = 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->chanvars) { + ast_variables_destroy(peer->chanvars); + peer->chanvars = NULL; + /* XXX should unregister ? */ + } + + if (found) { + peer->portinuri = FALSE; + } + + /* If we have realm authentication information, remove them (reload) */ + ao2_lock(peer); + + if (peer->auth) { + ao2_t_ref(peer->auth, -1, "Removing old peer authentication"); + peer->auth = NULL; + } + + ao2_unlock(peer); + + /* clear the transport information. We will detect if a default value is required after parsing the config */ + peer->default_outbound_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); + + for (var = vars; var || ((var = alt_vars) && !(alt_vars = NULL)); var = var->next) { + if (!devstate_only) { + if (sip_config_parse_common(&peerflags[0], &mask[0], var)) { + continue; + } + + if (sip_config_parse_t38(&peerflags[0], &mask[0], var, &peer->t38_maxdatagram)) { + continue; + } + + if (!strcasecmp(var->name, "transport")) { + char *transport = ast_strdupa(var->value); + char *option; + + peer->transports = peer->default_outbound_transport = 0; + + 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, "'%.3s' is not a valid transport type when %.3senable=no. If no other is specified, the defaults from general will be used.\n", option, option); + } else { + ast_log(LOG_NOTICE, "'%s' is not a valid transport type. if no other is specified, the defaults from general will be used.\n", option); + } + + if (!peer->default_outbound_transport) { /* The first transport listed should be default outbound */ + peer->default_outbound_transport = peer->transports; + } + } + } else if (realtime && !strcasecmp(var->name, "regseconds")) { + ast_get_time_t(var->value, ®seconds, 0, NULL); + } else if (realtime && !strcasecmp(var->name, "name")) { + ast_string_field_set(peer, name, var->value); + } else if (realtime && !strcasecmp(var->name, "useragent")) { + ast_string_field_set(peer, useragent, var->value); + } else if (!strcasecmp(var->name, "type")) { + /* skip */ + } else if (!strcasecmp(var->name, "remotesecret")) { + ast_string_field_set(peer, remotesecret, var->value); + } else if (!strcasecmp(var->name, "secret")) { + ast_string_field_set(peer, secret, var->value); + } else if (!strcasecmp(var->name, "description")) { + ast_string_field_set(peer, description, var->value); + } else if (!strcasecmp(var->name, "md5secret")) { + ast_string_field_set(peer, md5secret, var->value); + } else if (!strcasecmp(var->name, "auth")) { + sip_auth_build(&peer->auth, var->value, var->lineno); + } else if (!strcasecmp(var->name, "callerid")) { + char cid_name[80], cid_num[80]; + + ast_callerid_split(var->value, cid_name, sizeof(cid_name), cid_num, sizeof(cid_num)); + ast_string_field_set(peer, cid_name, cid_name); + ast_string_field_set(peer, cid_num, cid_num); + } else if (!strcasecmp(var->name, "mwi_from")) { + ast_string_field_set(peer, mwi_from, var->value); + } else if (!strcasecmp(var->name, "cid_name")) { + ast_string_field_set(peer, cid_name, var->value); + } else if (!strcasecmp(var->name, "cid_number")) { + ast_string_field_set(peer, cid_num, var->value); + } else if (!strcasecmp(var->name, "cid_tag")) { + ast_string_field_set(peer, cid_tag, var->value); + } else if (!strcasecmp(var->name, "context")) { + ast_string_field_set(peer, context, var->value); + ast_set_flag(&peer->flags[1], SIP_HAVEPEERCONTEXT); + } else if (!strcasecmp(var->name, "outofcall_message_context")) { + ast_string_field_set(peer, messagecontext, var->value); + } else if (!strcasecmp(var->name, "subscribecontext")) { + ast_string_field_set(peer, subscribecontext, var->value); + } else if (!strcasecmp(var->name, "fromdomain")) { + char *fromdomainport; + + ast_string_field_set(peer, fromdomain, var->value); + + if ((fromdomainport = strchr(peer->fromdomain, ':'))) { + *fromdomainport++ = '\0'; + + if (!(peer->fromdomainport = sip_str2port(fromdomainport, 0))) { + ast_log(LOG_NOTICE, "'%s' is not a valid port number for fromdomain.\n", fromdomainport); + } + } else { + peer->fromdomainport = SIP_STANDARD_PORT; + } + } else if (!strcasecmp(var->name, "usereqphone")) { + ast_set2_flag(&peer->flags[0], ast_true(var->value), SIP_USEREQPHONE); + } else if (!strcasecmp(var->name, "fromuser")) { + ast_string_field_set(peer, fromuser, var->value); + } else if (!strcasecmp(var->name, "outboundproxy")) { + struct sip_proxy *proxy; + + if (ast_strlen_zero(var->value)) { + ast_log(LOG_WARNING, "no value given for outbound proxy on line %d of sip.conf\n", var->lineno); + continue; + } + + if (!(proxy = sip_proxy_build(var->value, var->lineno, peer->outboundproxy))) { + ast_log(LOG_WARNING, "failure parsing the outbound proxy on line %d of sip.conf.\n", var->lineno); + continue; + } + + peer->outboundproxy = proxy; + } else if (!strcasecmp(var->name, "host")) { + if (!strcasecmp(var->value, "dynamic")) { + /* They'll register with us */ + if ((!found && !ast_test_flag(&sip_config.flags[1], SIP_RTCACHEFRIENDS)) || !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, tohost, NULL); + ast_sockaddr_setnull(&peer->addr); + } + + 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->expire, ao2_t_cleanup(peer, "removing register expire ref")); + + peer->host_dynamic = FALSE; + srvlookup = var->value; + } + } else if (!strcasecmp(var->name, "defaultip")) { + peer->defaddr.ss.ss_family = AST_AF_UNSPEC; + + if (!ast_strlen_zero(var->value) && ast_get_ip(&peer->defaddr, var->value)) { + ao2_t_cleanup(peer, "ao2_t_cleanup: from sip_peer_build defaultip"); + return NULL; + } + } else if (!strcasecmp(var->name, "permit") || !strcasecmp(var->name, "deny") || !strcasecmp(var->name, "acl")) { + int ha_error = 0; + + if (!ast_strlen_zero(var->value)) { + ast_append_acl(var->name, var->value, &peer->acl, &ha_error, &acl_change_sub_needed); + } + + if (ha_error) { + ast_log(LOG_ERROR, "Bad ACL entry in configuration line %d : %s. Deleting peer\n", var->lineno, var->value); + ao2_t_cleanup(peer, "Removing peer due to bad ACL configuration"); + + return NULL; + } + } else if (!strcasecmp(var->name, "contactpermit") || !strcasecmp(var->name, "contactdeny") || !strcasecmp(var->name, "contactacl")) { + int ha_error = 0; + + if (!ast_strlen_zero(var->value)) { + ast_append_acl(var->name + 7, var->value, &peer->contactacl, &ha_error, &acl_change_sub_needed); + } + + if (ha_error) { + ast_log(LOG_ERROR, "Bad ACL entry in configuration line %d : %s. Deleting peer\n", var->lineno, var->value); + ao2_t_cleanup(peer, "Removing peer due to bad contact ACL configuration"); + + return NULL; + } + } else if (!strcasecmp(var->name, "directmediapermit") || !strcasecmp(var->name, "directmediadeny") || !strcasecmp(var->name, "directmediaacl")) { + int ha_error = 0; + + ast_append_acl(var->name + 11, var->value, &peer->directmediaacl, &ha_error, &acl_change_sub_needed); + + if (ha_error) { + ast_log(LOG_ERROR, "Bad directmedia ACL entry in configuration line %d : %s. Deleting peer\n", var->lineno, var->value); + ao2_t_cleanup(peer, "Removing peer due to bad direct media ACL configuration"); + + return NULL; + } + } else if (!strcasecmp(var->name, "port")) { + peer->portinuri = TRUE; + + if (!(port = sip_str2port(var->value, 0))) { + if (realtime) { + /* If stored as integer, could be 0 for some DBs (notably MySQL) */ + peer->portinuri = FALSE; + } else { + ast_log(LOG_WARNING, "Invalid peer port configuration at line %d : %s\n", var->lineno, var->value); + } + } + } else if (!strcasecmp(var->name, "callingpres")) { + peer->callingpres = ast_parse_caller_presentation(var->value); + + if (peer->callingpres == -1) { + peer->callingpres = atoi(var->value); + } + } else if (!strcasecmp(var->name, "username") || !strcasecmp(var->name, "defaultuser")) { /* "username" is deprecated */ + ast_string_field_set(peer, username, var->value); + + if (!strcasecmp(var->name, "username")) { + if (deprecated_username) { + ast_log(LOG_NOTICE, "The 'username' field for sip peers has been deprecated in favor of the term 'defaultuser'\n"); + deprecated_username = FALSE; + } + + peer->deprecated_username = TRUE; + } + } else if (!strcasecmp(var->name, "tonezone")) { + struct ast_tone_zone *zone; + + if (!(zone = ast_get_indication_zone(var->value))) { + ast_log(LOG_ERROR, "Unknown country code '%s' for tonezone in device [%s] at line %d. Check indications.conf for available country codes.\n", var->value, peer->name, var->lineno); + } else { + ast_tone_zone_unref(zone); + ast_string_field_set(peer, zone, var->value); + } + } else if (!strcasecmp(var->name, "language")) { + ast_string_field_set(peer, language, var->value); + } else if (!strcasecmp(var->name, "regexten")) { + ast_string_field_set(peer, regexten, var->value); + } else if (!strcasecmp(var->name, "callbackextension")) { + ast_log(LOG_NOTICE, "'%s' is no longer supported (always empty)\n", var->name); + } else if (!strcasecmp(var->name, "amaflags")) { + if ((peer->amaflags = ast_channel_string2amaflag(var->value)) < 0) { + ast_log(LOG_WARNING, "Invalid AMA Flags for peer: %s at line %d\n", var->value, var->lineno); + peer->amaflags = 0; + } + } else if (!strcasecmp(var->name, "maxforwards")) { + if (sscanf(var->value, "%30d", &peer->maxforwards) != 1 || peer->maxforwards < 1 || 255 < peer->maxforwards) { + ast_log(LOG_WARNING, "'%s' is not a valid maxforwards value at line %d. Using default.\n", var->value, var->lineno); + peer->maxforwards = sip_config.max_forwards; + } + } else if (!strcasecmp(var->name, "accountcode")) { + ast_string_field_set(peer, accountcode, var->value); + } else if (!strcasecmp(var->name, "mohinterpret")) { + ast_string_field_set(peer, mohinterpret, var->value); + } else if (!strcasecmp(var->name, "mohsuggest")) { + ast_string_field_set(peer, mohsuggest, var->value); + } else if (!strcasecmp(var->name, "parkinglot")) { + ast_string_field_set(peer, parkinglot, var->value); + } else if (!strcasecmp(var->name, "rtp_engine")) { + ast_string_field_set(peer, engine, var->value); + } else if (!strcasecmp(var->name, "mailbox")) { + sip_mailbox_build(peer, var->value); + } else if (!strcasecmp(var->name, "subscribemwi")) { + ast_set2_flag(&peer->flags[1], ast_true(var->value), SIP_SUBSCRIBEMWIONLY); + } else if (!strcasecmp(var->name, "vmexten")) { + ast_string_field_set(peer, vmexten, var->value); + } else if (!strcasecmp(var->name, "callgroup")) { + peer->callgroup = ast_get_group(var->value); + } else if (!strcasecmp(var->name, "allowtransfer")) { + peer->allowtransfer = ast_true(var->value); + } else if (!strcasecmp(var->name, "pickupgroup")) { + peer->pickupgroup = ast_get_group(var->value); + } else if (!strcasecmp(var->name, "namedcallgroup")) { + peer->named_callgroups = ast_get_namedgroups(var->value); + } else if (!strcasecmp(var->name, "namedpickupgroup")) { + peer->named_pickupgroups = ast_get_namedgroups(var->value); + } else if (!strcasecmp(var->name, "allow")) { + if (ast_format_cap_update_by_allow_disallow(peer->caps, var->value, TRUE)) { + ast_log(LOG_WARNING, "Codec configuration errors found at line %d: %s=%s\n", var->lineno, var->name, var->value); + } + } else if (!strcasecmp(var->name, "disallow")) { + if (ast_format_cap_update_by_allow_disallow(peer->caps, var->value, FALSE)) { + ast_log(LOG_WARNING, "Codec configuration errors found in line %d: %s=%s\n", var->lineno, var->name, var->value); + } + } else if (!strcasecmp(var->name, "preferred_codec_only")) { + ast_set2_flag(&peer->flags[1], ast_true(var->value), SIP_PREFERRED_CODEC); + } else if (!strcasecmp(var->name, "autoframing")) { + peer->autoframing = ast_true(var->value); + } else if (!strcasecmp(var->name, "rtptimeout")) { + if (sscanf(var->value, "%30d", &peer->rtptimeout) != 1 || peer->rtptimeout < 0) { + ast_log(LOG_WARNING, "'%s' is not a valid RTP hold time at line %d. Using default.\n", var->value, var->lineno); + peer->rtptimeout = sip_config.rtptimeout; + } + } else if (!strcasecmp(var->name, "rtpholdtimeout")) { + if (sscanf(var->value, "%30d", &peer->rtpholdtimeout) != 1 || peer->rtpholdtimeout < 0) { + ast_log(LOG_WARNING, "'%s' is not a valid RTP hold time at line %d. Using default.\n", var->value, var->lineno); + peer->rtpholdtimeout = sip_config.rtpholdtimeout; + } + } else if (!strcasecmp(var->name, "rtpkeepalive")) { + if (sscanf(var->value, "%30d", &peer->rtpkeepalive) != 1 || peer->rtpkeepalive < 0) { + ast_log(LOG_WARNING, "'%s' is not a valid RTP keepalive time at line %d. Using default.\n", var->value, var->lineno); + peer->rtpkeepalive = sip_config.rtpkeepalive; + } + } else if (!strcasecmp(var->name, "timert1")) { + if (sscanf(var->value, "%30d", &peer->timer_t1) != 1 || peer->timer_t1 < 200 || peer->timer_t1 < sip_config.t1min) { + ast_log(LOG_WARNING, "'%s' is not a valid T1 time at line %d. Using default.\n", var->value, var->lineno); + peer->timer_t1 = sip_config.t1min; + } + + timert1_set = TRUE; + } else if (!strcasecmp(var->name, "timerb")) { + if (sscanf(var->value, "%30d", &peer->timer_b) != 1 || peer->timer_b < 200) { + ast_log(LOG_WARNING, "'%s' is not a valid Timer B time at line %d. Using default.\n", var->value, var->lineno); + peer->timer_b = sip_config.timer_b; + } + + timerb_set = TRUE; + } else if (!strcasecmp(var->name, "setvar")) { + peer->chanvars = sip_variable_build(var->value, peer->chanvars); + } else if (!strcasecmp(var->name, "header")) { + char header[SIP_BUFFER_SIZE * 5]; + + snprintf(header, sizeof(header), "__SIPADDHEADERpre%2d=%s", ++headercount, var->value); + peer->chanvars = sip_variable_build(header, peer->chanvars); + } else if (!strcasecmp(var->name, "qualifyfreq")) { + if (sscanf(var->value, "%30d", &peer->qualifyfreq) == 1) { + peer->qualifyfreq = peer->qualifyfreq * 1000; + } else { + ast_log(LOG_WARNING, "Invalid qualifyfreq number '%s' at line %d\n", var->value, var->lineno); + peer->qualifyfreq = sip_config.qualify_freq; + } + } else if (!strcasecmp(var->name, "maxcallbitrate")) { + peer->maxcallbitrate = atoi(var->value); + + if (peer->maxcallbitrate < 0) { + peer->maxcallbitrate = sip_config.maxcallbitrate; + } + } else if (!strcasecmp(var->name, "session-timers")) { + if (!strcasecmp(var->value, "accept")) { + peer->stimer.mode = SIP_STIMER_MODE_ACCEPT; + } else if (!strcasecmp(var->value, "refuse")) { + peer->stimer.mode = SIP_STIMER_MODE_REFUSE; + } else if (!strcasecmp(var->value, "originate")) { + peer->stimer.mode = SIP_STIMER_MODE_ORIGINATE; + } else { + ast_log(LOG_WARNING, "Invalid session-timers '%s' at line %d\n", var->value, var->lineno); + peer->stimer.mode = sip_config.stimer_mode; + } + } else if (!strcasecmp(var->name, "session-expires")) { + if (sscanf(var->value, "%30d", &peer->stimer.max_se) != 1) { + ast_log(LOG_WARNING, "Invalid session-expires '%s' at line %d\n", var->value, var->lineno); + peer->stimer.max_se = sip_config.max_se; + } + } else if (!strcasecmp(var->name, "session-minse")) { + if (sscanf(var->value, "%30d", &peer->stimer.min_se) != 1) { + ast_log(LOG_WARNING, "Invalid session-minse '%s' at line %d\n", var->value, var->lineno); + peer->stimer.min_se = sip_config.min_se; + } + + if (peer->stimer.min_se < sip_config.min_se) { + ast_log(LOG_WARNING, "session-minse '%s' at line %d is not allowed to be < %d secs\n", var->value, var->lineno, sip_config.min_se); + peer->stimer.min_se = sip_config.min_se; + } + } else if (!strcasecmp(var->name, "session-refresher")) { + if (!strcasecmp(var->value, "uac")) { + peer->stimer.refresher = SIP_STIMER_REFRESHER_US; + } else if (!strcasecmp(var->value, "uas")) { + peer->stimer.refresher = SIP_STIMER_REFRESHER_THEM; + } else { + ast_log(LOG_WARNING, "Invalid session-refresher '%s' at line %d\n", var->value, var->lineno); + peer->stimer.refresher = sip_config.stimer_refresher; + } + } else if (!strcasecmp(var->name, "disallowed_methods")) { + sip_method_parse(&peer->disallowed_methods, var->value); + } else if (!strcasecmp(var->name, "unsolicited_mailbox")) { + ast_string_field_set(peer, unsolicited_mailbox, var->value); + } else if (!strcasecmp(var->name, "use_q850_reason")) { + ast_set2_flag(&peer->flags[1], ast_true(var->value), SIP_Q850_REASON); + } else if (!strcasecmp(var->name, "encryption")) { + ast_set2_flag(&peer->flags[0], ast_true(var->value), SIP_USE_SRTP); + } else if (!strcasecmp(var->name, "encryption_taglen")) { + ast_set2_flag(&peer->flags[2], !strcasecmp(var->value, "32"), SIP_SRTP_TAG_32); + } else if (!strcasecmp(var->name, "avpf")) { + ast_set2_flag(&peer->flags[2], ast_true(var->value), SIP_USE_AVPF); + } else if (!strcasecmp(var->name, "icesupport")) { + ast_set2_flag(&peer->flags[2], ast_true(var->value), SIP_ICE_SUPPORT); + } else if (!strcasecmp(var->name, "ignore_requested_pref")) { + ast_set2_flag(&peer->flags[2], ast_true(var->value), SIP_IGNORE_PREFCAPS); + } else if (!strcasecmp(var->name, "discard_remote_hold_retrieval")) { + ast_set2_flag(&peer->flags[2], ast_true(var->value), SIP_DISCARD_REMOTE_HOLD_RETRIEVAL); + } else if (!strcasecmp(var->name, "force_avp")) { + ast_set2_flag(&peer->flags[2], ast_true(var->value), SIP_FORCE_AVP); + } else if (!strcasecmp(var->name, "register")) { + if (!strcasecmp(peer->name, var->value)) { + ast_log(LOG_WARNING, "Invalid register '%s', same name as peer at line %d\n", var->value, var->lineno); + } else { + sip_alias_build(peer, var->value, &lineindex); + } + } else if (!strcasecmp(var->name, "subscribe")) { + sip_subscription_build(peer, var->value); + } else if (!strcasecmp(var->name, "cisco_pickupnotify_alert")) { + char *alert = ast_strdupa(var->value); + char *option; + + ast_clear_flag(&peer->flags[2], SIP_CISCO_PICKUPNOTIFY_FROM | SIP_CISCO_PICKUPNOTIFY_TO | SIP_CISCO_PICKUPNOTIFY_BEEP); + + while ((option = strsep(&alert, ","))) { + if (!strcasecmp(option, "none")) { + ast_clear_flag(&peer->flags[2], SIP_CISCO_PICKUPNOTIFY_FROM | SIP_CISCO_PICKUPNOTIFY_TO | SIP_CISCO_PICKUPNOTIFY_BEEP); + } else if (!strcasecmp(option, "from")) { + ast_set_flag(&peer->flags[2], SIP_CISCO_PICKUPNOTIFY_FROM); + } else if (!strcasecmp(option, "to")) { + ast_set_flag(&peer->flags[2], SIP_CISCO_PICKUPNOTIFY_TO); + } else if (!strcasecmp(option, "beep")) { + ast_set_flag(&peer->flags[2], SIP_CISCO_PICKUPNOTIFY_BEEP); + } else { + ast_log(LOG_WARNING, "Invalid cisco_pickupnotify_alert '%s' at line %d\n", option, var->lineno); + } + } + } else if (!strcasecmp(var->name, "cisco_pickupnotify_timer")) { + if (sscanf(var->value, "%030d", &peer->cisco_pickupnotify_timer) != 1 || peer->cisco_pickupnotify_timer < 0 || peer->cisco_pickupnotify_timer > 60) { + ast_log(LOG_WARNING, "Invalid cisco_pickupnotify_timer '%s' at line %d\n", var->value, var->lineno); + peer->cisco_pickupnotify_timer = 5; + } + } else if (!strcasecmp(var->name, "cisco_qrt_url")) { + ast_string_field_set(peer, cisco_qrt_url, var->value); + } else if (realtime && !strcasecmp(var->name, "donotdisturb")) { + peer->donotdisturb = ast_true(var->value); + } else if (realtime && !strcasecmp(var->name, "callforward")) { + ast_string_field_set(peer, callforward, var->value); + } else if (realtime && !strcasecmp(var->name, "huntgroup")) { + peer->huntgroup = ast_true(var->value); + } + } + + /* These apply to devstate lookups */ + if (realtime && !strcasecmp(var->name, "lastms")) { + sscanf(var->value, "%30d", &peer->lastms); + } else if (realtime && !strcasecmp(var->name, "ipaddr") && !ast_strlen_zero(var->value)) { + ast_sockaddr_parse(&peer->addr, var->value, PARSE_PORT_FORBID); + } else if (realtime && !strcasecmp(var->name, "fullcontact")) { + if (alt_fullcontact && !alt_vars) { + /* Reset, because the alternate also has a fullcontact and we do NOT want the field value to be doubled. It + * might be tempting to skip this, but the first table might not have fullcontact and since we're here, we know + * that the alternate absolutely does. */ + alt_fullcontact = FALSE; + ast_str_reset(fullcontact); + } + /* Reconstruct field, because realtime separates our value at the ';' */ + if (ast_str_strlen(fullcontact) > 0) { + ast_str_append(&fullcontact, 0, ";%s", var->value); + } else { + ast_str_set(&fullcontact, 0, "%s", var->value); + } + } else if (!strcasecmp(var->name, "qualify")) { + if (!strcasecmp(var->value, "no")) { + peer->maxms = 0; + } else if (!strcasecmp(var->value, "yes")) { + peer->maxms = sip_config.qualify_maxms; + } else if (sscanf(var->value, "%30d", &peer->maxms) != 1) { + ast_log(LOG_WARNING, "Qualification of peer '%s' should be 'yes', 'no', or a number of milliseconds at line %d of sip.conf\n", peer->name, var->lineno); + peer->maxms = 0; + } + + if (realtime && !ast_test_flag(&sip_config.flags[1], SIP_RTCACHEFRIENDS) && peer->maxms > 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, "Qualify is incompatible with dynamic uncached realtime. Please either turn rtcachefriends on or turn qualify off on peer '%s'\n", peer->name); + peer->maxms = 0; + } + } else if (!strcasecmp(var->name, "keepalive")) { + if (!strcasecmp(var->value, "no")) { + peer->keepalive = 0; + } else if (!strcasecmp(var->value, "yes")) { + peer->keepalive = sip_config.keepalive; + } else if (sscanf(var->value, "%30d", &peer->keepalive) != 1) { + ast_log(LOG_WARNING, "Keep alive of peer '%s' should be 'yes', 'no', or a number of milliseconds at line %d of sip.conf\n", peer->name, var->lineno); + peer->keepalive = 0; + } + } else if (!strcasecmp(var->name, "callcounter")) { + peer->call_limit = ast_true(var->value) ? INT_MAX : 0; + } else if (!strcasecmp(var->name, "call-limit")) { + peer->call_limit = atoi(var->value); + + if (peer->call_limit < 0) { + peer->call_limit = 0; + } + } else if (!strcasecmp(var->name, "busylevel")) { + peer->busy_level = atoi(var->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 * Timer-T1=%d)\n", + peer->name, peer->timer_b, peer->timer_t1); + } else if (timerb_set) { + if ((peer->timer_t1 = peer->timer_b / 64) < sip_config.t1min) { + ast_log(LOG_WARNING, "Timer B has been set lower than recommended (%d < 64 * timert1=%d). (RFC 3261, 17.1.1.2)\n", + peer->timer_b, peer->timer_t1); + + peer->timer_t1 = sip_config.t1min; + 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_outbound_transport) { + /* Set default set of transports */ + peer->transports = sip_config.transports; + /* Set default primary transport */ + peer->default_outbound_transport = sip_config.primary_transport; + } + + /* The default transport type set during sip_peer_build should only replace the socket.type when... + * 1. Registration is not present and the socket.type and default transport types are different. + * 2. The socket.type is not an acceptable transport type after rebuilding peer. + * 3. The socket.type is not set yet. */ + if ((peer->socket.type != peer->default_outbound_transport && peer->expire == -1) || + !(peer->socket.type & peer->transports) || !peer->socket.type) { + sip_socket_set_transport(&peer->socket, peer->default_outbound_transport); + } + + ast_copy_flags(&peer->flags[0], &peerflags[0], mask[0].flags); + ast_copy_flags(&peer->flags[1], &peerflags[1], mask[1].flags); + ast_copy_flags(&peer->flags[2], &peerflags[2], mask[2].flags); + + if (ast_str_strlen(fullcontact)) { + ast_string_field_set(peer, fullcontact, ast_str_buffer(fullcontact)); + peer->rt_fromcontact = TRUE; + + /* We have a hostname in the fullcontact, 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->addr)) { + sip_parse_contact(ast_str_buffer(fullcontact), &peer->addr); + } + } + + if (srvlookup && peer->dnsmgr == NULL) { + char transport[MAXHOSTNAMELEN]; + char host[MAXHOSTNAMELEN]; + char *params; + + peer->addr.ss.ss_family = sip_transport_family(peer->socket.type); /* Filter address family */ + ast_copy_string(host, srvlookup, sizeof(host)); + + if ((params = strchr(host, ';'))) { + *params++ = '\0'; + } + + snprintf(transport, sizeof(transport), "_%s._%s", sip_srv_service(peer->socket.type), sip_srv_protocol(peer->socket.type)); + + if (ast_dnsmgr_lookup_cb(host, &peer->addr, &peer->dnsmgr, sip_config.srvlookup && !peer->portinuri ? transport : NULL, + sip_peer_dnsmgr_lookup, ao2_t_bump(peer, "Store peer on dnsmgr"))) { + ast_log(LOG_ERROR, "srvlookup failed for host: %s, on peer %s, removing peer\n", host, peer->name); + + ao2_t_cleanup(peer, "dnsmgr lookup failed, getting rid of peer dnsmgr ref"); + ao2_t_cleanup(peer, "getting rid of a peer pointer"); + + return NULL; + } + + if (!peer->dnsmgr) { + /* dnsmgr refresh disabeld, release reference */ + ao2_t_cleanup(peer, "dnsmgr disabled, unref peer"); + } + + ast_string_field_set(peer, tohost, srvlookup); + + if (sip_config.dynamic_exclude_static && !ast_sockaddr_isnull(&peer->addr)) { + int ha_error = 0; + + ast_append_acl("deny", ast_sockaddr_stringify_addr(&peer->addr), &sip_config.contact_acl, &ha_error, NULL); + + if (ha_error) { + ast_log(LOG_ERROR, "Bad 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->defaddr, port); + } else if (port) { + ast_sockaddr_set_port(&peer->addr, port); + } + + if (ast_sockaddr_port(&peer->addr) == 0) { + ast_sockaddr_set_port(&peer->addr, (peer->socket.type & AST_TRANSPORT_TLS) ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + } + if (ast_sockaddr_port(&peer->defaddr) == 0) { + ast_sockaddr_set_port(&peer->defaddr, (peer->socket.type & AST_TRANSPORT_TLS) ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + } + + if (realtime) { + int enable_qualify = TRUE; + + if (!sip_config.ignore_regexpire && peer->host_dynamic) { + time_t now = time(NULL); + + if ((now - regseconds) > 0) { + sip_peer_remove_from_db(peer); + memset(&peer->addr, 0, sizeof(peer->addr)); + + peer->lastms = -1; + enable_qualify = FALSE; + + ast_debug(1, "Bah, we're expired (%d/%d/%d)!\n", (int) (now - regseconds), (int) regseconds, (int) now); + } + } + + /* Startup regular qualifications */ + if (!devstate_only && enable_qualify) { + /* 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->qualifyexpire, sip_sched_context, 0, /* Poke the peer ASAP */ + sip_peer_qualify_now, peer, + ao2_t_cleanup(_data, "removing qualify peer ref"), + ao2_t_cleanup(peer, "removing qualify peer ref"), + ao2_t_bump(peer, "adding qualify peer ref")); + } + } + + if (ast_test_flag(&peer->flags[1], SIP_ALLOWSUBSCRIBE)) { + sip_config.allowsubscribe = TRUE; /* No global ban any more */ + } + + /* If read-only RT backend, then refresh from local DB cache */ + if (peer->host_dynamic && (!peer->is_realtime || !sip_config.peer_rtupdate)) { + sip_peer_register_from_db(peer); + } + + if (!realtime) { + char data[128]; + + if (!ast_db_get("SIP/DoNotDisturb", peer->name, data, sizeof(data))) { + peer->donotdisturb = ast_true(data); + } + + if (!ast_db_get("SIP/CallForward", peer->name, data, sizeof(data))) { + ast_string_field_set(peer, callforward, data); + } + + if (!ast_db_get("SIP/HuntGroup", peer->name, data, sizeof(data))) { + peer->huntgroup = ast_true(data); + } else { + peer->huntgroup = !!ast_test_flag(&peer->flags[2], SIP_HUNTGROUP_DEFAULT); + } + } + + /* 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_SUBSCRIBEMWIONLY) && !AST_LIST_EMPTY(&peer->mailboxes)) { + sip_peer_mailbox_subscriptions(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_extension_state_subscriptions(peer); + } + + peer->removed = FALSE; + + ast_free_acl_list(oldacl); + ast_free_acl_list(oldcontactacl); + ast_free_acl_list(olddirectmediaacl); + + /* If an ACL change subscription is needed and doesn't exist, we need one. */ + if (acl_change_sub_needed) { + 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 type) +{ + if (peer->socket.type == type) { + return 0; + } + + if (!(peer->transports & type)) { + ast_log(LOG_ERROR, "'%s' is not a valid transport for '%s'. we only use '%s'! ending call.\n", + ast_transport2str(type), peer->name, sip_transports2str(peer->transports)); + } else if (peer->socket.type & AST_TRANSPORT_TLS) { + ast_log(LOG_WARNING, "peer '%s' has not used TLS in favor of '%s' (but this was allowed in sip.conf)!\n", + peer->name, ast_transport2str(type)); + } else { + ast_debug(1, "peer '%s' has contacted us over %s even though we prefer %s.\n", + peer->name, ast_transport2str(type), ast_transport2str(peer->socket.type)); + } + + return -1; +} + +void sip_peer_dnsmgr_lookup(struct ast_sockaddr *oldaddr, struct ast_sockaddr *newaddr, void *data) +{ + struct sip_peer *peer = data; + const char *oldhost, *newhost; + + /* This shouldn't happen, but just in case */ + if (ast_sockaddr_isnull(newaddr)) { + ast_debug(1, "Empty sockaddr change...ignoring!\n"); + return; + } + + if (!ast_sockaddr_isnull(&peer->addr)) { + ao2_unlink(sip_peers_by_addr, peer); + } + + if (!ast_sockaddr_port(newaddr)) { + ast_sockaddr_set_port(newaddr, peer->socket.type == AST_TRANSPORT_TLS ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + } + + oldhost = ast_strdupa(ast_sockaddr_stringify(oldaddr)); + newhost = ast_strdupa(ast_sockaddr_stringify(newaddr)); + + ast_debug(1, "Changing peer %s address from %s to %s\n", peer->name, oldhost, newhost); + + ao2_lock(peer); + ast_sockaddr_copy(&peer->addr, newaddr); + ao2_unlock(peer); + + ao2_link(sip_peers_by_addr, peer); +} + +/* Send keep alive packet to peer */ +static int sip_peer_keepalive(const void *data) +{ + struct sip_peer *peer = (struct sip_peer *) data; + int res = 0; + const char *keepalive = "\r\n"; + size_t count = sizeof(keepalive) - 1; + + peer->keepalivesend = -1; + + if (!peer->keepalive || ast_sockaddr_isnull(&peer->addr)) { + ao2_t_cleanup(peer, "release keepalive peer ref"); + return 0; + } + + /* Send the packet out using the proper method for this peer */ + if ((peer->socket.fd != -1) && (peer->socket.type == AST_TRANSPORT_UDP)) { + res = ast_sendto(peer->socket.fd, keepalive, count, 0, &peer->addr); + } else if ((peer->socket.type & (AST_TRANSPORT_TCP | AST_TRANSPORT_TLS)) && peer->socket.tcptls_session) { + res = sip_tcptls_session_write(peer->socket.tcptls_session, keepalive, count); + + if (res < -1) { + return 0; + } + } else if (peer->socket.type == AST_TRANSPORT_UDP) { + res = ast_sendto(sip_socket_fd, keepalive, count, 0, &peer->addr); + } + + 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 != count) { + ast_log(LOG_WARNING, "sip_peer_keepalive to %s returned %d: %s\n", ast_sockaddr_stringify(&peer->addr), res, strerror(errno)); + } + + AST_SCHED_REPLACE_UNREF(peer->keepalivesend, sip_sched_context, peer->keepalive * 1000, sip_peer_keepalive, peer, + ao2_t_cleanup(_data, "removing keepalive peer ref"), + ao2_t_cleanup(peer, "removing keepalive peer ref"), + ao2_t_bump(peer, "adding keepalive peer ref")); + + ao2_t_cleanup(peer, "release keepalive peer ref"); + + 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, "iterate thru peers table"))) { + ao2_lock(peer); + + AST_SCHED_REPLACE_UNREF(peer->keepalivesend, sip_sched_context, 0, sip_peer_keepalive, peer, + ao2_t_cleanup(_data, "removing keepalive peer ref"), + ao2_t_cleanup(peer, "removing keepalive peer ref"), + ao2_t_bump(peer, "adding keepalive peer ref")); + + ao2_unlock(peer); + ao2_t_cleanup(peer, "toss iterator peer ptr"); + } + + ao2_iterator_destroy(&iter); +} + +/* 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 pvt lock while calling this function. + * This function calls sip_pvt_alloc, which can cause a deadlock if another sip_pvt is held. */ +int sip_peer_qualify(struct sip_peer *peer, int force) +{ + struct sip_pvt *pvt; + int send_res = 0; + + if ((!peer->maxms && !force) || ast_sockaddr_isnull(&peer->addr)) { + /* 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->qualifyexpire, ao2_t_cleanup(peer, "removing qualify peer ref")); + + peer->lastms = 0; + + if (peer->qualifypvt) { + ao2_t_cleanup(peer->qualifypvt, "unref dialog peer->call"); + peer->qualifypvt = NULL; + } + + return 0; + } + + if (peer->qualifypvt) { + if (sip_debug) { + ast_log(LOG_NOTICE, "Still have a QUALIFY dialog active, deleting\n"); + } + + sip_pvt_unlink(peer->qualifypvt); + ao2_t_cleanup(peer->qualifypvt, "unref dialog peer->qualifypvt"); + + peer->qualifypvt = NULL; + } + + if (!(pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_OPTIONS, NULL, 0))) { + return -1; + } + + peer->qualifypvt = ao2_t_bump(pvt, "copy sip alloc from pvt to peer->call"); + + pvt->sa = peer->addr; + pvt->recv = peer->addr; + + sip_socket_copy_data(&pvt->socket, &peer->socket); + + ast_copy_flags(&pvt->flags[0], &peer->flags[0], SIP_FLAGS0_TO_COPY); + ast_copy_flags(&pvt->flags[1], &peer->flags[1], SIP_FLAGS1_TO_COPY); + ast_copy_flags(&pvt->flags[2], &peer->flags[2], SIP_FLAGS2_TO_COPY); + sip_route_copy(&pvt->route, &peer->path); + + if (!sip_route_empty(&pvt->route)) { + /* Parse SIP URI of first route-set hop and use it as target address */ + sip_parse_contact(sip_route_first_uri(&pvt->route), &pvt->sa); + } + + /* Get the outbound proxy information */ + sip_proxy_ref(pvt, sip_proxy_get(pvt, peer)); + + /* Send OPTIONs to peer's fullcontact */ + if (!ast_strlen_zero(peer->fullcontact)) { + ast_string_field_set(pvt, fullcontact, peer->fullcontact); + } + + if (!ast_strlen_zero(peer->fromuser)) { + ast_string_field_set(pvt, fromuser, peer->fromuser); + } + + if (!ast_strlen_zero(peer->tohost)) { + ast_string_field_set(pvt, tohost, peer->tohost); + } else { + ast_string_field_set(pvt, tohost, ast_sockaddr_stringify_host_remote(&peer->addr)); + } + + /* Recalculate our side, and recalculate Call ID */ + sip_pvt_set_ouraddrfor(pvt, &pvt->sa, &pvt->ourip); + sip_pvt_build_via(pvt); + + /* Change the dialog callid. */ + sip_pvt_change_callid(pvt, NULL); + + AST_SCHED_DEL_UNREF(sip_sched_context, peer->qualifyexpire, ao2_t_cleanup(peer, "removing qualify peer ref")); + + if (pvt->relatedpeer) { + ao2_t_cleanup(pvt->relatedpeer,"unsetting the relatedpeer field in the dialog, before it is set to something else."); + } + + pvt->relatedpeer = ao2_t_bump(peer, "setting the relatedpeer field in the dialog"); + + ast_set_flag(&pvt->flags[0], SIP_OUTGOING); + send_res = sip_send_invite(pvt, SIP_OPTIONS, FALSE, SIP_INIT_REQUEST, NULL); /* sinks the pvt refcount */ + + peer->qualifystart = ast_tvnow(); + + if (send_res == -1) { + /* Immediately unreachable, network problems */ + sip_peer_qualify_noanswer(ao2_t_bump(peer, "add ref for peerexpire (fake, for sip_peer_qualify_noanswer to remove)")); + } else if (!force) { + AST_SCHED_REPLACE_UNREF(peer->qualifyexpire, sip_sched_context, peer->maxms * 2, sip_peer_qualify_noanswer, peer, + ao2_t_cleanup(_data, "removing qualify peer ref"), + ao2_t_cleanup(peer, "removing qualify peer ref"), + ao2_t_bump(peer, "adding qualify peer ref")); + } + + ao2_t_cleanup(pvt, "unref dialog at end of sip_peer_qualify, obtained from sip_pvt_alloc, just before it goes out of scope"); + + return 0; +} + +/* React to lack of answer to Qualify qualify */ +int sip_peer_qualify_noanswer(const void *data) +{ + struct sip_peer *peer = (struct sip_peer *) data; + + peer->qualifyexpire = -1; + + if (peer->lastms > -1) { + ast_verb(3, "Peer '%s' is now Unreachable. Last qualify: %dms\n", peer->name, peer->lastms); + + if (sip_config.peer_rtupdate) { + ast_update_realtime(ast_check_realtime("sipregs") ? "sipregs" : "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 (sip_config.regextenonqualify) { + sip_peer_register_exten(peer, FALSE); + } + } + + if (peer->qualifypvt) { + sip_pvt_unlink(peer->qualifypvt); + ao2_t_cleanup(peer->qualifypvt, "unref dialog peer->qualifypvt"); + + peer->qualifypvt = NULL; + } + + /* Don't send a devstate change if nothing changed. */ + if (peer->lastms > -1) { + peer->lastms = -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->lastms == -1) { + continue; + } + + ast_log(LOG_NOTICE, "Peer '%s' is now UNREACHABLE! Last qualify: %d\n", alias->peer->name, alias->peer->lastms); + + 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->lastms = -1; + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); + + if (sip_config.regextenonqualify) { + sip_peer_register_exten(alias->peer, FALSE); + } + } + } + + /* Try again quickly */ + AST_SCHED_REPLACE_UNREF(peer->qualifyexpire, sip_sched_context, SIP_QUALIFY_FREQ_NOTOK, __sip_peer_qualify, peer, + ao2_t_cleanup(_data, "removing qualify peer ref"), + ao2_t_cleanup(peer, "removing qualify peer ref"), + ao2_t_bump(peer, "adding qualify peer ref")); + + /* Release the ref held by the running scheduler entry */ + ao2_t_cleanup(peer, "release peer qualify noanswer ref"); + + return 0; +} + +/* Poke peer (send qualify to check if peer is alive and well) */ +int __sip_peer_qualify(const void *data) +{ + struct sip_peer *peer = (struct sip_peer *) data; + struct sip_peer *qualify_peer; + + peer->qualifyexpire = -1; + + if (!(qualify_peer = ao2_find(sip_peers, peer, OBJ_POINTER))) { + ao2_t_cleanup(peer, "removing qualify peer ref"); + return 0; + } + + if (qualify_peer->name != peer->name) { + ao2_t_cleanup(qualify_peer, "removing above peer ref"); + ao2_t_cleanup(peer, "removing qualify peer ref"); + + return 0; + } + + ao2_t_cleanup(qualify_peer, "removing above peer ref"); + sip_peer_qualify(peer, FALSE); + ao2_t_cleanup(peer, "removing qualify peer ref"); + + return 0; +} + +/* Send a qualify to all known peers */ +void sip_peer_qualify_all(void) +{ + int ms = 0, num = 0; + 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, "iterate thru peers table"))) { + ao2_lock(peer); + + /* Only qualify the primary line */ + if (peer->cisco_lineindex > 1) { + ao2_unlock(peer); + continue; + } + + /* Don't schedule poking on a peer without qualify */ + if (peer->maxms) { + if (num == sip_config.qualify_peers) { + ms += sip_config.qualify_gap; + num = 0; + } else { + num++; + } + + AST_SCHED_REPLACE_UNREF(peer->qualifyexpire, sip_sched_context, ms, __sip_peer_qualify, peer, + ao2_t_cleanup(_data, "removing qualify peer ref"), + ao2_t_cleanup(peer, "removing qualify peer ref"), + ao2_t_bump(peer, "adding qualify peer ref")); + } + + ao2_unlock(peer); + ao2_t_cleanup(peer, "toss iterator peer ptr"); + } + + ao2_iterator_destroy(&iter); +} + +int sip_peer_qualify_now(const void *data) +{ + struct sip_peer *peer = (struct sip_peer *) data; + + peer->qualifyexpire = -1; + sip_peer_qualify(peer, FALSE); + ao2_t_cleanup(peer, "removing qualify peer ref"); + + return 0; +} + +/* Implement the setvar config line */ +static struct ast_variable *sip_variable_build(const char *buf, struct ast_variable *list) +{ + struct ast_variable *var = NULL; + char *name = ast_strdupa(buf), *value = NULL; + + if ((value = strchr(name, '='))) { + *value++ = '\0'; + + if ((var = ast_variable_new(name, value, ""))) { + if (ast_variable_list_replace(&list, var)) { + var->next = list; + list = var; + } + } + } + + return list; +} + +/* Add a mailbox to a peer */ +static void sip_mailbox_build(struct sip_peer *peer, const char *value) +{ + char *next, *name; + struct sip_mailbox *mailbox; + + next = ast_strdupa(value); + + while ((name = strsep(&next, ","))) { + /* remove leading/trailing whitespace from mailbox string */ + name = ast_strip(name); + + if (ast_strlen_zero(name)) { + continue; + } + + /* Check whether the mailbox is already in the list */ + AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, next) { + if (!strcmp(mailbox->name, name)) { + break; + } + } + + if (mailbox) { + mailbox->removed = FALSE; + } else { + if (!(mailbox = ast_calloc(1, sizeof(*mailbox) + strlen(name) + 1))) { + return; + } + + strcpy(mailbox->name, name); /* SAFE */ + mailbox->peer = peer; + + AST_LIST_INSERT_TAIL(&peer->mailboxes, mailbox, next); + } + } +} + +/* Destroy mailbox subscriptions */ +static void sip_mailbox_destroy(struct sip_mailbox *mailbox) +{ + if (mailbox->event_sub) { + mailbox->event_sub = ast_mwi_unsubscribe_and_join(mailbox->event_sub); + } + + 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 *value, int *lineindex) +{ + char *next, *name; + struct sip_alias *alias; + + next = ast_strdupa(value); + + while ((name = strsep(&next, ","))) { + 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->lineindex = (*lineindex)++; + } +} + +static void sip_alias_destroy(struct sip_alias *alias) +{ + if (alias->peer) { + alias->peer->lastms = 0; + + if (alias->peer->socket.tcptls_session) { + ao2_ref(alias->peer->socket.tcptls_session, -1); + } + + ast_string_field_set(alias->peer, fullcontact, ""); + ast_string_field_set(alias->peer, username, ""); + ast_string_field_set(alias->peer, useragent, ""); + + if (!ast_sockaddr_isnull(&alias->peer->addr)) { + 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); + sip_peer_register_exten(alias->peer, FALSE); + + memset(&alias->peer->addr, 0, sizeof(alias->peer->addr)); + } + + ao2_t_cleanup(alias->peer, "sip_alias_destroy removing peer ref"); + } + + 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 *value) +{ + char *next, *exten, *context; + struct sip_subscription *subscription; + + next = ast_strdupa(value); + + while ((exten = strsep(&next, ","))) { + if ((context = strchr(exten, '@'))) { + *context++ = '\0'; + context = ast_strip(context); + } else { + context = ast_strdupa(S_OR(peer->subscribecontext, 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, 32))) { + 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->pvt) { + sip_pvt_unlink(subscription->pvt); + ao2_t_cleanup(subscription->pvt, "destroying subscription"); + } + + 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.2.0.orig/channels/sip/pickup.c asterisk-22.2.0/channels/sip/pickup.c --- asterisk-22.2.0.orig/channels/sip/pickup.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/pickup.c 2025-02-18 17:14:46.819902352 +1300 @@ -0,0 +1,330 @@ +/* + * 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/pvt.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_sub; /* 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 *chan = data; + + ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL_CLEARING); + + if (ast_pickup_call(chan)) { + ast_channel_hangupcause_set(chan, AST_CAUSE_CALL_REJECTED); + } + + ast_hangup(chan); + ast_channel_unref(chan); + + 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 *chan) +{ + pthread_t threadid; + + ast_channel_ref(chan); + + if (ast_pthread_create_detached_background(&threadid, NULL, sip_pickup_thread, chan)) { + ast_debug(1, "Unable to start Group pickup thread on channel %s\n", ast_channel_name(chan)); + ast_channel_unref(chan); + return -1; + } + + ast_debug(1, "Started Group pickup thread on channel %s\n", ast_channel_name(chan)); + + return 0; +} + +/* No channel or pvt locks should be held while calling this function. */ +int sip_pickup_exten(struct ast_channel *chan, const char *exten, const char *context) +{ + struct ast_str *str = ast_str_alloca(AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2); + struct ast_app *pickup = pbx_findapp("Pickup"); + + if (!pickup) { + ast_log(LOG_ERROR, "Unable to perform pickup: Application 'Pickup' not loaded (app_directed_pickup.so).\n"); + return -1; + } + + ast_str_set(&str, 0, "%s@%s", exten, sip_config.notifycid == SIP_NOTIFYCID_IGNORE_CONTEXT ? "PICKUPMARK" : context); + ast_debug(2, "About to call Pickup(%s)\n", ast_str_buffer(str)); + + /* 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(chan, pickup, ast_str_buffer(str)); + + return 0; +} + +static int sip_pickup_notify_peer(void *data, void *arg, int flags) +{ + struct sip_peer *peer = data; + struct sip_pickup_notify_args *args = arg; + + ao2_lock(peer); + + if (!ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER) || + !ast_test_flag(&peer->flags[2], SIP_CISCO_PICKUPNOTIFY_FROM | SIP_CISCO_PICKUPNOTIFY_TO | SIP_CISCO_PICKUPNOTIFY_BEEP)) { + ao2_unlock(peer); + return 0; + } + + if (ast_sockaddr_isnull(&peer->addr) || peer->ringing || peer->inuse || peer->donotdisturb) { + ao2_unlock(peer); + return 0; + } + + if ((peer->pickupgroup & args->callgroup) || ast_namedgroups_intersect(peer->named_pickupgroups, args->named_callgroups)) { + if (args->now - peer->cisco_pickupnotify_sent > peer->cisco_pickupnotify_timer) { + peer->cisco_pickupnotify_sent = args->now; + ao2_unlock(peer); + + return CMP_MATCH; + } + } + + ao2_unlock(peer); + + return 0; +} + +static void *sip_pickup_notify_thread(void *data) +{ + char *device = data; + char match[AST_CHANNEL_NAME]; + struct ast_channel_iterator *chaniter; + struct ast_channel *pickupchan = NULL, *chan; + struct timeval creationtime = { 0, }; + struct sip_pickup_notify_args args; + char *cid_num, *lid_num; + struct ao2_iterator *peeriter; + struct sip_peer *peer; + struct sip_pvt *pvt; + struct ast_str *content = ast_str_alloca(8192); + + snprintf(match, sizeof(match), "%s-", device); + chaniter = ast_channel_iterator_by_name_new(match, strlen(match)); + ast_free(device); + + while ((chan = ast_channel_iterator_next(chaniter))) { + ast_channel_lock(chan); + + /* Pick the youngest ringing channel */ + if (ast_channel_state(chan) == AST_STATE_RINGING && ast_tvcmp(ast_channel_creationtime(chan), creationtime) > 0) { + if (pickupchan) { + ast_channel_unref(pickupchan); + } + + pickupchan = ast_channel_ref(chan); + creationtime = ast_channel_creationtime(chan); + } + + ast_channel_unlock(chan); + ast_channel_unref(chan); + } + + ast_channel_iterator_destroy(chaniter); + + if (!pickupchan) { + return NULL; + } + + ast_channel_lock(pickupchan); + + args.callgroup = ast_channel_callgroup(pickupchan); + args.named_callgroups = ast_ref_namedgroups(ast_channel_named_callgroups(pickupchan)); + + cid_num = ast_strdupa(S_COR(ast_channel_caller(pickupchan)->id.number.valid, ast_channel_caller(pickupchan)->id.number.str, "")); + lid_num = ast_strdupa(S_COR(ast_channel_connected(pickupchan)->id.number.valid, ast_channel_connected(pickupchan)->id.number.str, "")); + + ast_channel_unlock(pickupchan); + ast_channel_unref(pickupchan); + + if (!args.callgroup && !args.named_callgroups) { + return NULL; + } + + args.now = time(NULL); + + /* We use ao2_callback here so that we don't hold the lock on the peers container while sending the notify dialogs */ + if (!(peeriter = ao2_callback(sip_peers, OBJ_MULTIPLE, sip_pickup_notify_peer, &args))) { + ast_log(LOG_ERROR, "Unable to create iterator for peers container in sip_pickup_notify_thread\n"); + return NULL; + } + + ast_unref_namedgroups(args.named_callgroups); + + while ((peer = ao2_iterator_next(peeriter))) { + if ((!(pvt = sip_pvt_alloc(NULL, NULL, FALSE, SIP_REFER, NULL, 0)))) { + ao2_t_cleanup(peer, "remove iterator ref"); + continue; + } + + sip_socket_set_transport(&pvt->socket, 0); + + if (sip_pvt_build_from_peer(pvt, peer)) { + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "sip_pvt_build_from_peer failed in sip_pickup_notify_thread. Unref dialog"); + ao2_t_cleanup(peer, "remove iterator ref"); + continue; + } + + ast_str_reset(content); + + if (ast_test_flag(&peer->flags[2], SIP_CISCO_PICKUPNOTIFY_FROM | SIP_CISCO_PICKUPNOTIFY_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_PICKUPNOTIFY_FROM)) { + ast_str_append(&content, 0, "From %s", lid_num); + } + + if (ast_test_flag(&peer->flags[2], SIP_CISCO_PICKUPNOTIFY_TO)) { + ast_str_append(&content, 0, "%s %s", + ast_test_flag(&peer->flags[2], SIP_CISCO_PICKUPNOTIFY_FROM) ? " to" : "To", cid_num); + } + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%d\n", peer->cisco_pickupnotify_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_PICKUPNOTIFY_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(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + ao2_t_cleanup(pvt, "bump down the count of pvt since we're done with it."); + + ao2_t_cleanup(peer, "remove iterator ref"); + } + + ao2_iterator_destroy(peeriter); + + return NULL; +} + +void sip_pickup_notify_subscribe(void) +{ + if (!sip_pickup_notify_sub) { + sip_pickup_notify_sub = stasis_subscribe(ast_device_state_topic_all(), sip_pickup_notify_event, NULL); + } +} + +void sip_pickup_notify_unsubscribe(void) +{ + if (sip_pickup_notify_sub) { + sip_pickup_notify_sub = stasis_unsubscribe(sip_pickup_notify_sub); + } +} + +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)) == NULL) { + return; + } + + if (ast_pthread_create_detached_background(&threadid, NULL, sip_pickup_notify_thread, device)) { + ast_free(device); + } +} diff -durN asterisk-22.2.0.orig/channels/sip/proxy.c asterisk-22.2.0/channels/sip/proxy.c --- asterisk-22.2.0.orig/channels/sip/proxy.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/proxy.c 2025-02-18 17:14:46.819902352 +1300 @@ -0,0 +1,197 @@ +/* + * 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/pvt.h" +#include "include/utils.h" +#include "include/config.h" + +/* Resolve DNS srv name or host name in a sip_proxy structure */ +int sip_proxy_update(struct sip_proxy *proxy) +{ + /* If it's actually an IP address and not a name, there's no need for a managed lookup */ + if (!ast_sockaddr_parse(&proxy->addr, 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->addr.ss.ss_family = sip_transport_family(AST_TRANSPORT_UDP); /* Filter address family */ + + if (ast_get_ip_or_srv(&proxy->addr, proxy->host, sip_config.srvlookup ? "_sip._udp" : NULL) < 0) { + ast_log(LOG_WARNING, "Unable to locate host '%s'\n", proxy->host); + return FALSE; + } + } + + ast_sockaddr_set_port(&proxy->addr, proxy->port); + proxy->last_dnsupdate = time(NULL); + + return TRUE; +} + +/* Parse proxy string and return an ao2_alloc'd proxy. If dest is non-NULL, no allocation is performed and dest is used instead. */ +struct sip_proxy *sip_proxy_build(const char *config, int lineno, struct sip_proxy *proxy) +{ + char *sep, *host, *port; + int allocated = FALSE; + + if (!proxy) { + if (!(proxy = ao2_alloc(sizeof(*proxy), NULL))) { + ast_log(LOG_WARNING, "Unable to allocate config storage for proxy\n"); + return NULL; + } + + allocated = TRUE; + } + + /* Format is: [transport://]host[:port][,force] */ + host = ast_skip_blanks(config); + + if ((sep = strchr(host, ','))) { + *sep++ = '\0'; + proxy->force = !strncasecmp(ast_skip_blanks(sep), "force", 5); + } else { + proxy->force = FALSE; + } + + if (!strncasecmp(host, "tcp://", 5)) { + proxy->transport = AST_TRANSPORT_TCP; + } else if (!strncasecmp(host, "tls://", 5)) { + proxy->transport = AST_TRANSPORT_TLS; + } else if (!strncasecmp(host, "udp://", 5)) { + proxy->transport = AST_TRANSPORT_UDP; + } else if ((sep = strstr(host, "://"))) { + ast_log(LOG_NOTICE, "'%.3s' is not a valid transport type on line %d of sip.conf. defaulting to udp.\n", config, lineno); + host = sep + 3; + } else { + proxy->transport = AST_TRANSPORT_UDP; + } + + if (!ast_sockaddr_split_hostport(host, &host, &port, 0)) { + ast_log(LOG_WARNING, "Cannot parse host '%s' on line %d of sip.conf.\n", config, lineno); + } else { + if (port && sscanf(port, "%5d", &proxy->port) != 1) { + ast_log(LOG_NOTICE, "'%s' is not a valid port number on line %d of sip.conf. using default.\n", port, lineno); + port = NULL; + } + } + + if (!port) { + if (proxy->transport & AST_TRANSPORT_TLS) { + proxy->port = SIP_STANDARD_TLS_PORT; + } else { + proxy->port = SIP_STANDARD_PORT; + } + } + + /* Check that there is a name at all */ + if (ast_strlen_zero(host)) { + if (allocated) { + ao2_ref(proxy, -1); + } else { + proxy->host[0] = '\0'; + } + + return NULL; + } + + ast_copy_string(proxy->host, host, sizeof(proxy->host)); + sip_proxy_update(proxy); /* Resolve host immediately */ + + return proxy; +} + +/* Maintain proper refcounts for a sip_pvt's outboundproxy + * This function sets pvt's outboundproxy pointer to the one referenced by the proxy parameter. Because proxy may be a refcounted + * object, and because pvt's old outboundproxy may also be a refcounted object, we need to maintain the proper refcounts. */ +void sip_proxy_ref(struct sip_pvt *pvt, struct sip_proxy *proxy) +{ + struct sip_proxy *oldproxy = pvt->outboundproxy; + + /* The sip_config.outboundproxy is statically allocated, and so we don't ever need to adjust refcounts for it */ + if (proxy && proxy != &sip_config.outboundproxy) { + ao2_ref(proxy, +1); + } + + pvt->outboundproxy = proxy; + + if (oldproxy && oldproxy != &sip_config.outboundproxy) { + ao2_ref(oldproxy, -1); + } +} + +/* Get default outbound proxy or global proxy */ +struct sip_proxy *sip_proxy_get(struct sip_pvt *pvt, struct sip_peer *peer) +{ + if (pvt && pvt->options && pvt->options->outboundproxy) { + if (sip_debug) { + ast_debug(1, "Applying dialplan set outbound proxy to this call\n"); + } + + sip_history_append(pvt, "OBproxy", "Using dialplan obproxy %s", pvt->options->outboundproxy->host); + + return pvt->options->outboundproxy; + } + + if (peer && peer->outboundproxy) { + if (sip_debug) { + ast_debug(1, "Applying peer outbound proxy to this call\n"); + } + + sip_history_append(pvt, "OBproxy", "Using peer obproxy %s", peer->outboundproxy->host); + + return peer->outboundproxy; + } + + if (sip_config.outboundproxy.host[0]) { + if (sip_debug) { + ast_debug(1, "Applying global outbound proxy to this call\n"); + } + + sip_history_append(pvt, "OBproxy", "Using global outbound proxy %s", sip_config.outboundproxy.host); + + return &sip_config.outboundproxy; + } + + if (sip_debug) { + ast_debug(1, "Not applying outbound proxy to this call\n"); + } + + return NULL; +} diff -durN asterisk-22.2.0.orig/channels/sip/pvt.c asterisk-22.2.0/channels/sip/pvt.c --- asterisk-22.2.0.orig/channels/sip/pvt.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/pvt.c 2025-02-18 17:14:46.822902271 +1300 @@ -0,0 +1,5314 @@ +/* + * 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/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.h" +#include "include/domain.h" +#include "include/peers.h" +#include "include/registry.h" +#include "include/mwi_subscriptions.h" +#include "include/pvt.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/chan_tech.h" +#include "include/manager.h" +#include "include/message.h" + +#define SIP_MAX_HISTORY_ENTRIES 50 /* Max entires in the history list for a sip_pvt */ + +static void sip_history_dump(struct sip_pvt *pvt); /* Dump history to debuglog at end of dialog, before destroying data */ + +static void sip_pvt_destroy(void *data); + +static void __sip_pvt_unlink_sched(struct sip_pvt *pvt); +static int sip_pvt_unlink_sched(const void *data); + +static int __sip_pvt_cancel_destroy(const void *data); +static int __sip_pvt_sched_destroy(const void *data); + +static int sip_pvt_rtp_check_timeout(struct sip_pvt *pvt, time_t now); +static int __sip_pvt_sched_check_pendings(const void *data); + +static int sip_pvt_setup_rtp(struct sip_pvt *pvt); +static void sip_pvt_cleanup_rtp(struct sip_pvt *pvt); +static void sip_pvt_build_callid(struct sip_pvt *pvt); + +static int sip_pvt_check_auth(struct sip_pvt *pvt, struct sip_request *req, const char *username, const char *secret, const char *md5secret, + const char *uri, int reliable); +static int __sip_pvt_check_peer_auth(struct sip_pvt *pvt, char *name, struct sip_request *req, char *uri, struct ast_sockaddr *addr, + struct sip_peer **authpeer, int reliable); + +static int __sip_pvt_stop_reinvite_retry(const void *data); +static int __sip_pvt_stop_reinvite(const void *data); + +static int sip_pvt_send_provisonal_keepalive_full(struct sip_pvt *pvt, int with_sdp); +static int sip_pvt_send_provisonal_keepalive(const void *data); +static int sip_pvt_send_provisonal_keepalive_with_sdp(const void *data); +static int __sip_pvt_update_provisional_keepalive_full(struct sip_pvt *pvt, int with_sdp); +static int __sip_pvt_update_provisional_keepalive(const void *data); +static int __sip_pvt_update_provisional_keepalive_with_sdp(const void *data); +static int __sip_pvt_cancel_provisional_keepalive(const void *data); + +static int sip_pvt_t38_abort(const void *data); +static int __sip_pvt_stop_t38_abort_timer(const void *data); +static void sip_pvt_stop_t38_abort_timer(struct sip_pvt *pvt); +static int __sip_pvt_start_t38_abort_timer(const void *data); +static void sip_pvt_set_t38_capabilities(struct sip_pvt *pvt); + +static void sip_history_dump(struct sip_pvt *pvt); + +static void sip_refer_free(struct sip_pvt *pvt); +static void sip_notify_free(struct sip_pvt *pvt); + +/* Here we implement the container for dialogs (sip_pvt), 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_pvts; + +/* Here we implement the container for dialogs which are in the sip_pvt_need_destroy state to iterate only through the dialogs unlink + * them instead of iterate through all dialogs */ +struct ao2_container *sip_pvts_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_pvts_rtp_check; + +/* The only member of the dialog used here callid string */ +int sip_pvt_hash(const void *obj, const int flags) +{ + const struct sip_pvt *pvt = obj; + + return ast_str_hash(pvt->callid); +} + +/* Compare callid and possibly tag and theirtag */ +int sip_pvt_cmp(void *obj, void *arg, int flags) +{ + struct sip_pvt *pvt1 = obj, *pvt2 = arg; + + if (strcmp(pvt1->callid, pvt2->callid)) { + return 0; + } + + if (!ast_strlen_zero(pvt2->tag) && strcmp(pvt1->tag, pvt2->tag)) { + return 0; + } + + if (!ast_strlen_zero(pvt2->theirtag) && strcmp(pvt1->tag, pvt2->theirtag)) { + return 0; + } + + return CMP_MATCH | ((flags & OBJ_MULTIPLE) ? 0 : CMP_STOP); +} + +/* Allocate sip_pvt structure, set defaults and link in the container. */ +struct sip_pvt *sip_pvt_alloc(ast_string_field callid, struct ast_sockaddr *addr, int useglobal_nat, const int method, + struct sip_request *req, ast_callid logger_callid) +{ + struct sip_pvt *pvt; + + if (!(pvt = ao2_t_alloc_options(sizeof(*pvt), sip_pvt_destroy, AO2_ALLOC_OPT_LOCK_MUTEX, "allocate a pvt struct"))) { + return NULL; + } + + if (ast_string_field_init(pvt, 512)) { + ao2_t_ref(pvt, -1, "failed to string_field_init, drop pvt"); + return NULL; + } + + if (logger_callid) { + pvt->logger_callid = logger_callid; + } + + pvt->caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + pvt->jointcaps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + pvt->peercaps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + pvt->redircaps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + pvt->prefcaps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + + if (!pvt->caps || !pvt->jointcaps || !pvt->peercaps || !pvt->redircaps || !pvt->prefcaps) { + ao2_cleanup(pvt->caps); + ao2_cleanup(pvt->jointcaps); + ao2_cleanup(pvt->peercaps); + ao2_cleanup(pvt->redircaps); + ao2_cleanup(pvt->prefcaps); + + ao2_t_ref(pvt, -1, "Failed to allocate format capabilities. Get rid of pvt"); + + 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 (req) { + struct sip_via *via; + const char *cseq = sip_request_get_header(req, "Cseq"); + uint32_t seqno; + + /* get branch parameter from initial Request that started this dialog */ + via = sip_via_parse(sip_request_get_header(req, "Via")); + + if (via) { + /* 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(via->branch) && !strncasecmp(via->branch, "z9hG4bK", 7)) { + ast_string_field_set(pvt, initviabranch, via->branch); + ast_string_field_set(pvt, initviasentby, via->sent_by); + } + + sip_via_free(via); + } + + /* Store initial incoming cseq. An error in sscanf here is ignored. There is no approperiate except not storing the + * number. CSeq validation must take place before dialog creation in sip_request_find_pvt */ + if (!ast_strlen_zero(cseq) && (sscanf(cseq, "%30u", &seqno) == 1)) { + pvt->init_icseq = seqno; + } + + /* Later in sip_pvt_set_ouraddrfor we need this to choose the right ip and port for the specific transport */ + sip_socket_set_transport(&pvt->socket, req->socket.type); + } else { + sip_socket_set_transport(&pvt->socket, AST_TRANSPORT_UDP); + } + + pvt->socket.fd = -1; + pvt->method = method; + + pvt->initid = -1; + pvt->waitid = -1; + pvt->reinviteid = -1; + pvt->autokillid = -1; + + pvt->request_queue_sched_id = -1; + pvt->provisional_keepalive_sched_id = -1; + + pvt->t38id = -1; + + pvt->subscribed = SIP_SUBSCRIBED_NONE; + pvt->stateid = -1; + + pvt->sessionversion_remote = -1; + pvt->session_modify = TRUE; + pvt->stimer = NULL; + + ast_copy_string(pvt->zone, sip_config.zone, sizeof(pvt->zone)); + pvt->maxforwards = sip_config.max_forwards; + + if (method != SIP_OPTIONS) { /* Qualify peers has it's own system */ + pvt->timer_t1 = sip_config.timer_t1; /* Default SIP retransmission timer T1 (RFC 3261) */ + pvt->timer_b = sip_config.timer_b; /* Default SIP transaction timer B (RFC 3261) */ + } + + if (!addr) { + pvt->ourip = sip_internip; + } else { + ast_sockaddr_copy(&pvt->sa, addr); + sip_pvt_set_ouraddrfor(pvt, &pvt->sa, &pvt->ourip); + } + + /* Copy global flags to this PVT at setup. */ + ast_copy_flags(&pvt->flags[0], &sip_config.flags[0], SIP_FLAGS0_TO_COPY); + ast_copy_flags(&pvt->flags[1], &sip_config.flags[1], SIP_FLAGS1_TO_COPY); + ast_copy_flags(&pvt->flags[2], &sip_config.flags[2], SIP_FLAGS2_TO_COPY); + + pvt->recordhistory = sip_config.recordhistory; + + pvt->branch = ast_random(); + sip_pvt_build_our_tag(pvt); + pvt->ocseq = SIP_INITIAL_CSEQ; + pvt->allowed_methods = UINT_MAX; + + if (sip_methods[method].need_rtp) { + pvt->maxcallbitrate = sip_config.maxcallbitrate; + pvt->autoframing = sip_config.autoframing; + } + + if (useglobal_nat && addr) { + /* Setup NAT structure according to global settings if we have an address */ + ast_sockaddr_copy(&pvt->recv, addr); + + sip_pvt_check_via(pvt, req); + sip_pvt_set_rtp_nat(pvt); + } + + if (pvt->method != SIP_REGISTER) { + ast_string_field_set(pvt, fromdomain, sip_config.fromdomain); + pvt->fromdomainport = sip_config.fromdomainport; + } + + sip_pvt_build_via(pvt); + + if (!callid) { + sip_pvt_build_callid(pvt); + } else { + ast_string_field_set(pvt, callid, callid); + } + + /* Assign default music on hold class */ + ast_string_field_set(pvt, mohinterpret, sip_config.mohinterpret); + ast_string_field_set(pvt, mohsuggest, sip_config.mohsuggest); + ast_format_cap_append_from_cap(pvt->caps, sip_config.caps, AST_MEDIA_TYPE_UNKNOWN); + + pvt->allowtransfer = sip_config.allowtransfer; + + if (ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833 || ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + pvt->noncodeccapability |= AST_RTP_DTMF; + } + + ast_string_field_set(pvt, context, sip_config.context); + ast_string_field_set(pvt, parkinglot, sip_config.parkinglot); + ast_string_field_set(pvt, engine, sip_config.engine); + + AST_LIST_HEAD_INIT_NOLOCK(&pvt->request_queue); + AST_LIST_HEAD_INIT_NOLOCK(&pvt->offered_media); + + /* Add to active dialog list */ + ao2_t_link(sip_pvts, pvt, "link pvt into dialogs table"); + + ast_debug(1, "Allocating new SIP dialog for %s - %s (%s)\n", + callid ? callid : pvt->callid, sip_methods[method].name, pvt->rtp ? "With RTP" : "No RTP"); + + return pvt; +} + +/* ao2 destructor for SIP dialog structure */ +static void sip_pvt_destroy(void *data) +{ + struct sip_pvt *pvt = data; + struct sip_request *req; + + ast_debug(3, "Destroying SIP dialog %s\n", pvt->callid); + + /* Destroy Session-Timers if allocated */ + ast_free(pvt->stimer); + pvt->stimer = NULL; + + if (sip_debug_test_pvt(pvt)) { + ast_verb(3, "Really destroying SIP dialog '%s' Method: %s\n", pvt->callid, sip_methods[pvt->method].name); + } + + if (ast_test_flag(&pvt->flags[0], SIP_INC_COUNT) || ast_test_flag(&pvt->flags[1], SIP_CALL_ONHOLD)) { + sip_pvt_update_call_counter(pvt, SIP_DEC_CALL_LIMIT); + ast_debug(2, "This call did not properly clean up call limits. Call ID %s\n", pvt->callid); + } + + /* Unlink us from the owner if we have one */ + if (pvt->owner) { + ast_channel_lock(pvt->owner); + + ast_debug(1, "Detaching from %s\n", ast_channel_name(pvt->owner)); + ast_channel_tech_pvt_set(pvt->owner, NULL); + + /* Make sure that the channel knows its backend is going away */ + ast_channel_softhangup_internal_flag_add(pvt->owner, AST_SOFTHANGUP_DEV); + ast_channel_unlock(pvt->owner); + + /* Give the channel a chance to react before deallocation */ + usleep(1); + } + + /* Remove link from peer to subscription of MWI */ + if (pvt->relatedpeer && pvt->relatedpeer->mwipvt == pvt) { + ao2_t_cleanup(pvt->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt"); + pvt->relatedpeer->mwipvt = NULL; + } + + /* Remove link from peer to subscription for Feature Events */ + if (pvt->relatedpeer && pvt->relatedpeer->fepvt) { + ao2_t_cleanup(pvt->relatedpeer->fepvt, "delete ->relatedpeer->fepvt"); + pvt->relatedpeer->fepvt = NULL; + } + + if (pvt->relatedpeer && pvt->relatedpeer->qualifypvt == pvt) { + ao2_t_cleanup(pvt->relatedpeer->qualifypvt, "unset the relatedpeer->qualifypvt field in tandem with relatedpeer field itself"); + pvt->relatedpeer->qualifypvt = NULL; + } + + if (pvt->relatedpeer) { + ao2_t_cleanup(pvt->relatedpeer,"unsetting a dialog relatedpeer field in sip_destroy"); + pvt->relatedpeer = NULL; + } + + if (pvt->registry) { + if (pvt->registry->pvt == pvt) { + ao2_t_cleanup(pvt->registry->pvt, "nulling out the registry's call dialog field in unlink_all"); + pvt->registry->pvt = NULL; + } + + ao2_t_replace(pvt->registry, NULL, "delete pvt->registry"); + } + + if (pvt->mwi) { + pvt->mwi->pvt = NULL; + pvt->mwi = NULL; + } + + if (sip_config.dumphistory) { + sip_history_dump(pvt); + } + + if (pvt->options) { + if (pvt->options->outboundproxy) { + ao2_ref(pvt->options->outboundproxy, -1); + } + + ast_free(pvt->options); + pvt->options = NULL; + } + + if (pvt->outboundproxy) { + sip_proxy_ref(pvt, NULL); + } + + /* Free RTP and SRTP instances */ + sip_pvt_cleanup_rtp(pvt); + + if (pvt->udptl) { + ast_udptl_destroy(pvt->udptl); + pvt->udptl = NULL; + } + + sip_refer_free(pvt); + sip_notify_free(pvt); + + sip_route_clear(&pvt->route); + sip_request_free(&pvt->initreq); + + /* Clear history */ + if (pvt->history) { + struct sip_history *history; + + while ((history = AST_LIST_REMOVE_HEAD(pvt->history, next))) { + ast_free(history); + } + + ast_free(pvt->history); + pvt->history = NULL; + pvt->history_entries = 0; + } + + while ((req = AST_LIST_REMOVE_HEAD(&pvt->request_queue, next))) { + ast_free(req); + } + + sip_pvt_free_offered_media(pvt); + + if (pvt->chanvars) { + ast_variables_destroy(pvt->chanvars); + pvt->chanvars = NULL; + } + + sip_msg_free_headers(pvt); + + if (pvt->vsrtp) { + ast_sdp_srtp_destroy(pvt->vsrtp); + pvt->vsrtp = NULL; + } + + if (pvt->directmediaacl) { + pvt->directmediaacl = ast_free_acl_list(pvt->directmediaacl); + } + + ast_string_field_free_memory(pvt); + + if (pvt->socket.tcptls_session) { + ao2_ref(pvt->socket.tcptls_session, -1); + pvt->socket.tcptls_session = NULL; + } + + if (pvt->peerauth) { + ao2_t_ref(pvt->peerauth, -1, "Removing active peer authentication"); + pvt->peerauth = NULL; + } + + pvt->named_callgroups = ast_unref_namedgroups(pvt->named_callgroups); + pvt->named_pickupgroups = ast_unref_namedgroups(pvt->named_pickupgroups); + + ao2_cleanup(pvt->caps); + ao2_cleanup(pvt->jointcaps); + ao2_cleanup(pvt->peercaps); + ao2_cleanup(pvt->redircaps); + ao2_cleanup(pvt->prefcaps); + + if (pvt->last_device_state_info) { + ao2_ref(pvt->last_device_state_info, -1); + pvt->last_device_state_info = NULL; + } +} + +/* Initiate a call in the SIP channel */ +struct ast_channel *sip_pvt_channel_alloc(struct sip_pvt *pvt, int state, const char *peername, const struct ast_assigned_ids *assignedids, + const struct ast_channel *requestor, ast_callid callid) +{ + static unsigned int chan_counter = 0; /* Used in naming sip channel */ + struct ast_format_cap *caps; + struct ast_channel *chan; + struct ast_variable *var = NULL; + struct ast_format *format; + struct ast_format_cap *prefcaps = NULL; /* SHALLOW COPY DO NOT DESTROY! */ + struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + int needvideo = FALSE; + int needtext = FALSE; + const char *name; + char *exten; + + + if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + return NULL; + } + + if (peername) { + name = peername; + } else { + name = ast_strdupa(pvt->fromdomain); + } + + /* Don't hold a sip pvt lock while we allocate a channel */ + ao2_unlock(pvt); + + if (pvt->relatedpeer && pvt->relatedpeer->endpoint) { + chan = ast_channel_alloc_with_endpoint(TRUE, state, pvt->cid_num, pvt->cid_name, pvt->accountcode, pvt->exten, pvt->context, + assignedids, requestor, pvt->amaflags, pvt->relatedpeer->endpoint, + "SIP/%s-%08x", name, (unsigned int) ast_atomic_fetchadd_int((int *) &chan_counter, +1)); + } else { + chan = ast_channel_alloc(TRUE, state, pvt->cid_num, pvt->cid_name, pvt->accountcode, pvt->exten, pvt->context, + assignedids, requestor, pvt->amaflags, + "SIP/%s-%08x", name, (unsigned int) ast_atomic_fetchadd_int((int *) &chan_counter, +1)); + } + + if (!chan) { + ast_log(LOG_WARNING, "Unable to allocate channel\n"); + + ao2_ref(caps, -1); + ao2_lock(pvt); + + return NULL; + } + + ast_channel_stage_snapshot(chan); + + /* If we sent in a callid, bind it to the channel. */ + if (callid) { + ast_channel_callid_set(chan, callid); + } + + ao2_lock(pvt); + ast_channel_tech_set(chan, &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(pvt->jointcaps)) { /* The joint capabilities of us and peer */ + prefcaps = pvt->jointcaps; + } else if (ast_format_cap_count(pvt->caps)) { /* Our configured capability for this peer */ + prefcaps = pvt->caps; + } else { + prefcaps = sip_config.caps; + } + + /* Set the native formats */ + ast_format_cap_append_from_cap(caps, prefcaps, 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(prefcaps, AST_MEDIA_TYPE_AUDIO))) { /* get the best audio format */ + int framing; + + ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_AUDIO); /* remove only the other audio formats */ + framing = ast_format_cap_get_format_framing(prefcaps, format); + + ast_format_cap_append(caps, 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(caps, 0))) { + ast_log(LOG_WARNING, "No compatible formats could be found for %s\n", ast_channel_name(chan)); + + ao2_ref(caps, -1); + + ast_channel_stage_snapshot_done(chan); + ast_channel_unlock(chan); + + ast_hangup(chan); + + return NULL; + } + } + + ast_channel_nativeformats_set(chan, caps); + ao2_ref(caps, -1); + + ast_debug(3, "Our native formats are %s \n", ast_format_cap_get_names(ast_channel_nativeformats(chan), &codec_buf)); + ast_debug(3, "Joint capabilities are %s \n", ast_format_cap_get_names(pvt->jointcaps, &codec_buf)); + ast_debug(3, "Our capabilities are %s \n", ast_format_cap_get_names(pvt->caps, &codec_buf)); + ast_debug(3, "AST_CODEC_CHOOSE formats are %s \n", ast_format_get_name(format)); + + if (ast_format_cap_count(pvt->prefcaps)) { + ast_debug(3, "Our preferred formats from the incoming channel are %s \n", ast_format_cap_get_names(pvt->prefcaps, &codec_buf)); + } + + /* 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. */ + if (pvt->vrtp) { + if (ast_test_flag(&pvt->flags[1], SIP_VIDEOSUPPORT_ALWAYS)) { + needvideo = TRUE; + } else if (ast_format_cap_count(pvt->prefcaps)) { + needvideo = ast_format_cap_has_type(pvt->prefcaps, AST_MEDIA_TYPE_VIDEO); /* Outbound call */ + } else { + needvideo = ast_format_cap_has_type(pvt->jointcaps, AST_MEDIA_TYPE_VIDEO); /* Inbound call */ + } + + if (!needvideo) { + ast_rtp_instance_destroy(pvt->vrtp); + pvt->vrtp = NULL; + } + } + + if (pvt->trtp) { + if (ast_format_cap_count(pvt->prefcaps)) { + needtext = ast_format_cap_has_type(pvt->prefcaps, AST_MEDIA_TYPE_TEXT); /* Outbound call */ + } else { + needtext = ast_format_cap_has_type(pvt->jointcaps, AST_MEDIA_TYPE_TEXT); /* Inbound call */ + } + } + + if (needvideo) { + ast_debug(3, "This channel can handle video.\n"); + } else { + ast_debug(3, "This channel will not be able to handle video.\n"); + } + + sip_pvt_set_dsp_detect(pvt, TRUE); + + if (ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_INBAND || ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + if (pvt->rtp) { + ast_rtp_instance_dtmf_mode_set(pvt->rtp, AST_RTP_DTMF_MODE_INBAND); + } + } else if (ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833) { + if (pvt->rtp) { + ast_rtp_instance_dtmf_mode_set(pvt->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_pvt_setup_udptl */ + if (pvt->rtp) { + ast_channel_set_fd(chan, SIP_AUDIO_RTP_FD, ast_rtp_instance_fd(pvt->rtp, 0)); + + if (ast_test_flag(&pvt->flags[2], SIP_RTCP_MUX)) { + ast_channel_set_fd(chan, SIP_AUDIO_RTCP_FD, -1); + } else { + ast_channel_set_fd(chan, SIP_AUDIO_RTCP_FD, ast_rtp_instance_fd(pvt->rtp, 1)); + } + + ast_rtp_instance_set_write_format(pvt->rtp, format); + ast_rtp_instance_set_read_format(pvt->rtp, format); + } + + if (needvideo && pvt->vrtp) { + ast_channel_set_fd(chan, SIP_VIDEO_RTP_FD, ast_rtp_instance_fd(pvt->vrtp, 0)); + + if (ast_test_flag(&pvt->flags[2], SIP_RTCP_MUX)) { + ast_channel_set_fd(chan, SIP_VIDEO_RTCP_FD, -1); + } else { + ast_channel_set_fd(chan, SIP_VIDEO_RTCP_FD, ast_rtp_instance_fd(pvt->vrtp, 1)); + } + } + + if (needtext && pvt->trtp) { + ast_channel_set_fd(chan, SIP_TEXT_RTP_FD, ast_rtp_instance_fd(pvt->trtp, 0)); + } + + if (pvt->udptl) { + ast_channel_set_fd(chan, SIP_UDPTL_FD, ast_udptl_fd(pvt->udptl)); + } + + if (state == AST_STATE_RING) { + ast_channel_rings_set(chan, 1); + } + + ast_channel_adsicpe_set(chan, AST_ADSI_UNAVAILABLE); + + ast_channel_set_writeformat(chan, format); + ast_channel_set_rawwriteformat(chan, format); + + ast_channel_set_readformat(chan, format); + ast_channel_set_rawreadformat(chan, format); + + ao2_ref(format, -1); + + ast_channel_tech_pvt_set(chan, ao2_t_bump(pvt, "sip_pvt_channel_alloc: set chan->tech_pvt to pvt")); + + ast_channel_callgroup_set(chan, pvt->callgroup); + ast_channel_pickupgroup_set(chan, pvt->pickupgroup); + + ast_channel_named_callgroups_set(chan, pvt->named_callgroups); + ast_channel_named_pickupgroups_set(chan, pvt->named_pickupgroups); + + ast_channel_caller(chan)->id.name.presentation = pvt->callingpres; + ast_channel_caller(chan)->id.number.presentation = pvt->callingpres; + + if (!ast_strlen_zero(pvt->parkinglot)) { + ast_channel_parkinglot_set(chan, pvt->parkinglot); + } + + if (!ast_strlen_zero(pvt->accountcode)) { + ast_channel_accountcode_set(chan, pvt->accountcode); + } + + if (pvt->amaflags) { + ast_channel_amaflags_set(chan, pvt->amaflags); + } + + if (!ast_strlen_zero(pvt->language)) { + ast_channel_language_set(chan, pvt->language); + } + + if (!ast_strlen_zero(pvt->zone)) { + struct ast_tone_zone *zone; + + if (!(zone = ast_get_indication_zone(pvt->zone))) { + ast_log(LOG_ERROR, "Unknown country code '%s' for tonezone. Check indications.conf for available country codes.\n", pvt->zone); + } + + ast_channel_zone_set(chan, zone); + } + + sip_pvt_set_owner(pvt, chan); + sip_module_ref(); + + ast_channel_context_set(chan, pvt->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_pvt structure so that there aren't issues when forming URI's */ + exten = ast_strdupa(pvt->exten); + + ao2_unlock(pvt); + ast_channel_unlock(chan); + + if (!ast_exists_extension(NULL, pvt->context, pvt->exten, 1, pvt->cid_num)) { + ast_uri_decode(exten, ast_uri_sip_user); + } + + ast_channel_lock(chan); + ao2_lock(pvt); + + ast_channel_exten_set(chan, exten); + ast_channel_priority_set(chan, 1); + + /* Don't use ast_set_callerid() here because it will generate an unnecessary NewCallerID event */ + if (!ast_strlen_zero(pvt->cid_num)) { + ast_channel_caller(chan)->ani.number.valid = TRUE; + ast_channel_caller(chan)->ani.number.str = ast_strdup(pvt->cid_num); + } + + if (!ast_strlen_zero(pvt->rdnis)) { + ast_channel_redirecting(chan)->from.number.valid = TRUE; + ast_channel_redirecting(chan)->from.number.str = ast_strdup(pvt->rdnis); + } + + ast_channel_caller(chan)->id.tag = ast_strdup(pvt->cid_tag); + + if (!ast_strlen_zero(pvt->exten) && strcmp(pvt->exten, "s")) { + ast_channel_dialed(chan)->number.str = ast_strdup(pvt->exten); + } + + if (!ast_strlen_zero(pvt->uri)) { + pbx_builtin_setvar_helper(chan, "SIPURI", pvt->uri); + } + + if (!ast_strlen_zero(pvt->domain)) { + pbx_builtin_setvar_helper(chan, "SIPDOMAIN", pvt->domain); + } + + if (!ast_strlen_zero(pvt->callid)) { + pbx_builtin_setvar_helper(chan, "SIPCALLID", pvt->callid); + } + + if (pvt->rtp) { + ast_jb_configure(chan, &sip_jb_config); + } + + if (!pvt->relatedpeer) { + ast_set_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_DEVSTATE_CACHE); + } + + /* Set channel variables for this call from configuration */ + for (var = pvt->chanvars; var; var = var->next) { + char value[2048]; + + pbx_builtin_setvar_helper(chan, var->name, ast_get_encoded_str(var->value, value, sizeof(value))); + } + + if (pvt->recordhistory) { + sip_history_append(pvt, "NewChan", "Channel %s - from %s", ast_channel_name(chan), pvt->callid); + } + + ast_channel_stage_snapshot_done(chan); + + return chan; +} + +/* 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_pvt_unlink(struct sip_pvt *pvt) +{ + struct ast_channel *owner; + + ao2_t_bump(pvt, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done"); + + ao2_t_unlink(sip_pvts, pvt, "unlinking dialog via ao2_unlink"); + ao2_t_unlink(sip_pvts_need_destroy, pvt, "unlinking sip_pvt_need_destroy via ao2_unlink"); + ao2_t_unlink(sip_pvts_rtp_check, pvt, "unlinking sip_pvt_rtp_check via ao2_unlink"); + + /* Unlink us from the owner (channel) if we have one */ + if ((owner = sip_pvt_lock_full(pvt))) { + ast_debug(1, "Detaching from channel %s\n", ast_channel_name(owner)); + + ao2_t_cleanup(ast_channel_tech_pvt(owner), "resetting channel dialog ptr in unlink_all"); + ast_channel_tech_pvt_set(owner, NULL); + + ast_channel_unlock(owner); + ast_channel_unref(owner); + + sip_pvt_set_owner(pvt, NULL); + } + + ao2_unlock(pvt); + + if (pvt->registry) { + if (pvt->registry->pvt == pvt) { + ao2_t_cleanup(pvt->registry->pvt, "nulling out the registry's call dialog field in unlink_all"); + pvt->registry->pvt = NULL; + } + + ao2_t_replace(pvt->registry, NULL, "delete pvt->registry"); + } + + /* Remove link from peer to subscription of MWI */ + if (pvt->relatedpeer && pvt->relatedpeer->mwipvt == pvt) { + ao2_t_cleanup(pvt->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt"); + pvt->relatedpeer->mwipvt = NULL; + } + + /* Remove link from peer to subscription for Feature Events */ + if (pvt->relatedpeer && pvt->relatedpeer->fepvt == pvt) { + ao2_t_cleanup(pvt->relatedpeer->fepvt, "delete ->relatedpeer->fepvt"); + pvt->relatedpeer->fepvt = NULL; + } + + if (pvt->relatedpeer && pvt->relatedpeer->qualifypvt == pvt) { + ao2_t_cleanup(pvt->relatedpeer->qualifypvt, "unset the relatedpeer->qualifypvt field in tandem with relatedpeer field itself"); + pvt->relatedpeer->qualifypvt = NULL; + } + + if (pvt->stateid != -1) { + ast_extension_state_del(pvt->stateid, sip_pvt_extension_state_event); + pvt->stateid = -1; + } + + if (pvt->conference) { + ao2_ref(pvt->conference, -1); + pvt->conference = NULL; + } + + if (pvt->recordoutpvt) { + ao2_t_cleanup(pvt->recordoutpvt, "delete ->recordoutpvt"); + pvt->recordoutpvt = NULL; + } + + if (pvt->recordinpvt) { + ao2_t_cleanup(pvt->recordinpvt, "delete ->recordinpvt"); + pvt->recordinpvt = NULL; + } + + ao2_t_bump(pvt, "Stop scheduled items for unlink action"); + + if (ast_sched_add(sip_sched_context, 0, sip_pvt_unlink_sched, pvt) < 0) { + /* Fall back to unscheduling things immediately despite the potential deadlock risk. */ + ao2_t_cleanup(pvt, "Failed to schedule stop scheduled items for unlink action"); + __sip_pvt_unlink_sched(pvt); + } + + ao2_t_cleanup(pvt, "Let's unbump the count in the unlink so the poor pvt can disappear if it is time"); +} + +static void __sip_pvt_unlink_sched(struct sip_pvt *pvt) +{ + struct sip_packet *packet; + + /* remove all current packets in this dialog */ + ao2_lock(pvt); + + while ((packet = AST_LIST_REMOVE_HEAD(&pvt->packet_queue, next))) { + /* Unlink and destroy the packet object. */ + + AST_SCHED_DEL_UNREF(sip_sched_context, packet->retransid, + ao2_t_ref(packet, -1, "Stop scheduled packet retransmission")); + + ao2_t_ref(packet, -1, "Packet retransmission list"); + } + + ao2_unlock(pvt); + + AST_SCHED_DEL_UNREF(sip_sched_context, pvt->waitid, ao2_t_cleanup(pvt, "Stop scheduled waitid")); + AST_SCHED_DEL_UNREF(sip_sched_context, pvt->initid, ao2_t_cleanup(pvt, "Stop scheduled initid")); + + AST_SCHED_DEL_UNREF(sip_sched_context, pvt->reinviteid, ao2_t_cleanup(pvt, "Stop scheduled reinviteid")); + AST_SCHED_DEL_UNREF(sip_sched_context, pvt->autokillid, ao2_t_cleanup(pvt, "Stop scheduled autokillid")); + + AST_SCHED_DEL_UNREF(sip_sched_context, pvt->request_queue_sched_id, ao2_t_cleanup(pvt, "Stop scheduled request_queue_sched_id")); + AST_SCHED_DEL_UNREF(sip_sched_context, pvt->provisional_keepalive_sched_id, ao2_t_cleanup(pvt, "Stop scheduled provisional keepalive")); + + AST_SCHED_DEL_UNREF(sip_sched_context, pvt->t38id, ao2_t_cleanup(pvt, "Stop scheduled t38id")); + + if (pvt->stimer) { + pvt->stimer->active = FALSE; + ao2_t_bump(pvt, "temporarily bump pvt for stimer stop"); + __sip_stimer_stop(pvt); + } +} + +/* Run by the sched thread. */ +static int sip_pvt_unlink_sched(const void *data) +{ + struct sip_pvt *pvt = (void *) data; + + __sip_pvt_unlink_sched(pvt); + ao2_t_cleanup(pvt, "Stop scheduled items for unlink action"); + + 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_pvt_need_destroy(void *data, void *arg, int flags) +{ + struct sip_pvt *pvt = data; + + if (ao2_trylock(pvt)) { + /* 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 responsed to or an active call + * if that's the case, wait with destruction */ + if (pvt->needdestroy && AST_LIST_EMPTY(&pvt->packet_queue) && !pvt->owner) { + /* We absolutely cannot destroy the rtp struct while a bridge is active or we WILL crash */ + if (pvt->rtp && ast_rtp_instance_get_bridged(pvt->rtp)) { + ast_debug(2, "Bridge still active. Delaying destruction of SIP dialog '%s' Method: %s\n", pvt->callid, sip_methods[pvt->method].name); + ao2_unlock(pvt); + + return 0; + } + + if (pvt->vrtp && ast_rtp_instance_get_bridged(pvt->vrtp)) { + ast_debug(2, "Bridge still active. Delaying destroy of SIP dialog '%s' Method: %s\n", pvt->callid, sip_methods[pvt->method].name); + ao2_unlock(pvt); + + return 0; + } + + ao2_unlock(pvt); + + /* no, the unlink should handle this. the CMP_MATCH will unlink this dialog from the dialog hash table */ + sip_pvt_unlink(pvt); + + return 0; /* the unlink_all should unlink this from the table, so.... no need to return a match */ + } + + ao2_unlock(pvt); + + return 0; +} + +/* Run by the sched thread. */ +static int __sip_pvt_cancel_destroy(const void *data) +{ + struct sip_pvt *pvt = (void *) data; + + ao2_lock(pvt); + + if (pvt->autokillid > -1) { + sip_history_append(pvt, "CancelDestroy", ""); + AST_SCHED_DEL_UNREF(sip_sched_context, pvt->autokillid, ao2_t_cleanup(pvt, "Stop scheduled autokillid")); + } + + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "Cancel destroy action"); + + return 0; +} + +void sip_pvt_cancel_destroy(struct sip_pvt *pvt) +{ + if (pvt->final_destruction_scheduled) { + return; + } + + ao2_t_bump(pvt, "Cancel destroy action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_pvt_cancel_destroy, pvt) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(pvt, "Failed to schedule cancel destroy action"); + ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); + } +} + +/* Run by the sched thread. */ +static int __sip_pvt_sched_destroy(const void *data) +{ + struct sip_sched_data *sched_data = (void *) data; + struct sip_pvt *pvt = sched_data->pvt; + int ms = sched_data->ms; + + ast_free(sched_data); + ao2_lock(pvt); + + if (pvt->autokillid > -1) { + sip_history_append(pvt, "CancelDestroy", ""); + AST_SCHED_DEL_UNREF(sip_sched_context, pvt->autokillid, ao2_t_cleanup(pvt, "Stop scheduled autokillid")); + } + + if (pvt->recordhistory) { + sip_history_append(pvt, "SchedDestroy", "%d ms", ms); + } + + ao2_t_bump(pvt, "Schedule autokillid"); + pvt->autokillid = ast_sched_add(sip_sched_context, ms, sip_pvt_auto_destruct, pvt); + + if (pvt->autokillid < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(pvt, "Failed to schedule autokillid"); + } + + if (pvt->stimer) { + sip_stimer_stop(pvt); + } + + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "Destroy action"); + + return 0; +} + +int sip_pvt_sched_destroy(struct sip_pvt *pvt, int ms) +{ + struct sip_sched_data *sched_data; + + if (pvt->final_destruction_scheduled) { + return 0; /* already set final destruction */ + } + + if (ms < 0) { + if (pvt->timer_t1 == 0) { + pvt->timer_t1 = sip_config.timer_t1; /* Set timer T1 if not set (RFC 3261) */ + } + + if (pvt->timer_b == 0) { + pvt->timer_b = sip_config.timer_b; /* Set timer B if not set (RFC 3261) */ + } + + ms = pvt->timer_t1 * 64; + } + + if (sip_debug_test_pvt(pvt)) { + ast_verb(3, "Scheduling destruction of SIP dialog '%s' in %d ms (Method: %s)\n", + pvt->callid, ms, sip_methods[pvt->method].name); + } + + if (!(sched_data = ast_malloc(sizeof(*sched_data)))) { + /* Uh Oh. Expect bad behavior. */ + return -1; + } + + sched_data->pvt = pvt; + sched_data->ms = ms; + + ao2_t_bump(pvt, "Destroy action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_pvt_sched_destroy, sched_data) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(pvt, "Failed to schedule destroy action"); + 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_pvt_auto_destruct(const void *data) +{ + struct sip_pvt *pvt = (struct sip_pvt *) data; + struct ast_channel *owner; + + /* If this is a subscription, tell the phone that we got a timeout */ + if (pvt->subscribed && (pvt->subscribed == SIP_SUBSCRIBED_DIALOG_INFO_XML || pvt->subscribed == SIP_SUBSCRIBED_PIDF_XML)) { + struct sip_extension_state_data state_data = {.exten_state = AST_EXTENSION_DEACTIVATED}; + + sip_send_notify_with_extension_state(pvt, &state_data, TRUE); /* Send last notification */ + pvt->subscribed = SIP_SUBSCRIBED_NONE; + + sip_history_append(pvt, "Subscribestatus", "timeout"); + ast_debug(3, "Re-scheduled destruction of SIP subscription %s\n", pvt->callid ? pvt->callid : ""); + + 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(&pvt->packet_queue)) { + if (!pvt->needdestroy) { + char method[31]; + + ast_debug(3, "Re-scheduled destruction of SIP call %s\n", pvt->callid ? pvt->callid : ""); + sip_history_append(pvt, "ReliableXmit", "timeout"); + + if (sscanf(pvt->lastmsg, "Tx: %30s", method) == 1 || sscanf(pvt->lastmsg, "Rx: %30s", method) == 1) { + if (pvt->ongoing_reinvite || sip_method_cmp(SIP_CANCEL, method) || sip_method_cmp(SIP_BYE, method)) { + sip_pvt_set_need_destroy(pvt, "autodestruct"); + } + } + + return 10000; + } else { + /* They've had their chance to respond. Time to bail */ + sip_packet_pretend_ack(pvt); + } + } + + /* Lock both the pvt and the channel safely so that we can queue up a frame. */ + if ((owner = sip_pvt_lock_full(pvt))) { + ast_log(LOG_WARNING, "Autodestruct on dialog '%s' with owner %s in place (Method: %s). Rescheduling destruction for 10000 ms\n", + pvt->callid, ast_channel_name(owner), sip_methods[pvt->method].name); + + ast_queue_hangup_with_cause(owner, AST_CAUSE_PROTOCOL_ERROR); + + ast_channel_unlock(owner); + ast_channel_unref(owner); + ao2_unlock(pvt); + + return 10000; + } + + /* Reset schedule ID */ + pvt->autokillid = -1; + + if (pvt->refer && !pvt->alreadygone) { + ast_debug(3, "Finally hanging up channel after transfer: %s\n", pvt->callid); + sip_history_append(pvt, "ReferBYE", "Sending BYE on transferer call leg %s", pvt->callid); + + sip_pvt_stop_rtp(pvt); + sip_send_request_with_auth(pvt, SIP_BYE, 0, SIP_SEND_RELIABLE, TRUE); + + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + ao2_unlock(pvt); + } else { + ast_debug(3, "Auto destroying SIP dialog '%s'\n", pvt->callid); + sip_history_append(pvt, "AutoDestroy", "%s", pvt->callid); + + ao2_unlock(pvt); + sip_pvt_unlink(pvt); /* once it's unlinked and unrefd everywhere, it'll be freed automagically */ + } + + ao2_t_cleanup(pvt, "autokillid complete"); + + return 0; +} + +/* Scheduled congestion on a call. Only called by the scheduler, must return the reference when done. */ +int sip_pvt_auto_congest(const void *data) +{ + struct sip_pvt *pvt = (struct sip_pvt *) data; + + ao2_lock(pvt); + pvt->initid = -1; /* event gone, will not be rescheduled */ + + if (pvt->owner) { + /* XXX fails on possible deadlock */ + if (!ast_channel_trylock(pvt->owner)) { + sip_history_append(pvt, "Cong", "Auto-congesting (timer)"); + ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION); + ast_channel_unlock(pvt->owner); + } + + /* Give the channel a chance to act before we proceed with destruction */ + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + } + + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "unreffing arg passed into sip_pvt_auto_congest callback (p->initid)"); + + return 0; +} + +/* Check RTP Timeout on dialogs */ +int sip_pvt_rtp_check(void *data, void *arg, int flags) +{ + struct sip_pvt *pvt = data; + time_t *now = arg; + int res; + + if (ao2_trylock(pvt)) { + return 0; + } + + if (pvt->rtp || pvt->vrtp) { + res = sip_pvt_rtp_check_timeout(pvt, *now); + } else { + /* Dialog has no active RTP or VRTP. unlink it from sip_pvts_rtp_check. */ + res = CMP_MATCH; + } + + ao2_unlock(pvt); + + return res; +} + +static int sip_pvt_rtp_check_timeout(struct sip_pvt *pvt, time_t now) +{ + int timeout; + int hold_timeout; + int keepalive; + + if (!pvt->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 (!pvt->owner) { + return CMP_MATCH; + } + + /* If the call is redirected outside Asterisk, no need to check timers */ + if (!ast_sockaddr_isnull(&pvt->redirip)) { + return CMP_MATCH; + } + + /* If the call is involved in a T38 fax session do not check RTP timeout */ + if (pvt->t38.state == SIP_T38_ENABLED) { + return CMP_MATCH; + } + /* If the call is not in UP state return for later check. */ + if (ast_channel_state(pvt->owner) != AST_STATE_UP) { + return 0; + } + + /* Store these values locally to avoid multiple function calls */ + timeout = ast_rtp_instance_get_timeout(pvt->rtp); + hold_timeout = ast_rtp_instance_get_hold_timeout(pvt->rtp); + keepalive = ast_rtp_instance_get_keepalive(pvt->rtp); + + /* If we have no timers set, return now */ + if (!keepalive && !timeout && !hold_timeout) { + return CMP_MATCH; + } + + /* Check AUDIO RTP keepalives */ + if (pvt->lastrtptx && keepalive && (now > pvt->lastrtptx + keepalive)) { + /* Need to send an empty RTP packet */ + pvt->lastrtptx = time(NULL); + ast_rtp_instance_sendcng(pvt->rtp, 0); + } + + /* Check AUDIO RTP timers */ + if (pvt->lastrtprx && (timeout || hold_timeout) && (now > pvt->lastrtprx + timeout)) { + if (!ast_test_flag(&pvt->flags[1], SIP_CALL_ONHOLD) || (hold_timeout && (now > pvt->lastrtprx + hold_timeout))) { + /* Needs a hangup */ + if (timeout) { + if (!pvt->owner || ast_channel_trylock(pvt->owner)) { + /* Don't block, just try again later. If there was no owner, the call is dead already. */ + return 0; + } + + ast_log(LOG_NOTICE, "Disconnecting call '%s' for lack of RTP activity in %ld seconds\n", + ast_channel_name(pvt->owner), (long) (now - pvt->lastrtprx)); + + sip_publish_session_timeout(pvt->owner, "RTPTimeout"); + + /* Issue a softhangup - cause 44 (as used by Cisco for RTP timeouts) */ + ast_channel_hangupcause_set(pvt->owner, AST_CAUSE_REQUESTED_CHAN_UNAVAIL); + ast_softhangup_nolock(pvt->owner, AST_SOFTHANGUP_DEV); + ast_channel_unlock(pvt->owner); + + /* 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(pvt->rtp, 0); + ast_rtp_instance_set_hold_timeout(pvt->rtp, 0); + + if (pvt->vrtp) { + ast_rtp_instance_set_timeout(pvt->vrtp, 0); + ast_rtp_instance_set_hold_timeout(pvt->vrtp, 0); + } + + /* finally unlink the dialog from sip_pvts_rtp_check. */ + return CMP_MATCH; + } + } + } + + return 0; +} + +/* Check pending actions on SIP call both sip_pvt and sip_pvt's owner channel (if present) must be locked for this function. */ +void sip_pvt_check_pendings(struct sip_pvt *pvt) +{ + if (ast_test_flag(&pvt->flags[0], SIP_PENDINGBYE)) { + if (pvt->reinviteid > -1) { + /* Outstanding pvt->reinviteid timeout, so wait... */ + return; + } + + if (pvt->invitestate == SIP_INVITE_PROCEEDING || pvt->invitestate == SIP_INVITE_EARLY_MEDIA) { + /* if we can't BYE, then this is really a pending CANCEL */ + pvt->invitestate = SIP_INVITE_CANCELLED; + sip_send_request(pvt, SIP_CANCEL, pvt->lastinvite, SIP_SEND_RELIABLE, FALSE); + + /* If the cancel occurred on an initial invite, cancel the pending BYE */ + if (!ast_test_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED)) { + ast_clear_flag(&pvt->flags[0], SIP_PENDINGBYE | SIP_NEEDREINVITE); + } + + /* 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 (pvt->pendinginvite && !pvt->ongoing_reinvite) { + return; + } + + if (pvt->owner) { + ast_softhangup_nolock(pvt->owner, AST_SOFTHANGUP_DEV); + } + + /* Perhaps there is an SD change INVITE outstanding */ + sip_send_request_with_auth(pvt, SIP_BYE, 0, SIP_SEND_RELIABLE, TRUE); + ast_clear_flag(&pvt->flags[0], SIP_PENDINGBYE | SIP_NEEDREINVITE); + } + + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + } else if (ast_test_flag(&pvt->flags[0], SIP_NEEDREINVITE)) { + /* if we can't REINVITE, hold it for later */ + if (pvt->pendinginvite + || pvt->invitestate == SIP_INVITE_CALLING || pvt->invitestate == SIP_INVITE_PROCEEDING || pvt->invitestate == SIP_INVITE_EARLY_MEDIA + || pvt->waitid > -1) { + ast_debug(2, "NOT Sending pending reinvite (yet) on '%s'\n", pvt->callid); + } else { + ast_debug(2, "Sending pending reinvite on '%s'\n", pvt->callid); + + /* Didn't get to reinvite yet, so do it now */ + sip_send_reinvite_with_sdp(pvt, pvt->t38.state == SIP_T38_LOCAL_REINVITE, FALSE); + ast_clear_flag(&pvt->flags[0], SIP_NEEDREINVITE); + } + } +} + +/* Run by the sched thread. */ +static int __sip_pvt_sched_check_pendings(const void *data) +{ + struct sip_pvt *pvt = (void *) data; + struct ast_channel *owner; + + owner = sip_pvt_lock_full(pvt); + sip_pvt_check_pendings(pvt); + + if (owner) { + ast_channel_unlock(owner); + ast_channel_unref(owner); + } + + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "Check pending actions action"); + + return 0; +} + +void sip_pvt_sched_check_pendings(struct sip_pvt *pvt) +{ + ao2_t_bump(pvt, "Check pending actions action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_pvt_sched_check_pendings, pvt) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(pvt, "Failed to schedule check pending actions action"); + } +} + +/* Locks both pvt and pvt owner if owner is present. This function gives a ref to pvt->owner if it is present and locked. */ +struct ast_channel *sip_pvt_lock_full(struct sip_pvt *pvt) +{ + struct ast_channel *chan; + + /* 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(pvt); + + if ((chan = pvt->owner)) { + /* The channel can not go away while we hold the pvt lock. Give the channel a ref so it will not go away + * after we let the pvt lock go. */ + ast_channel_ref(chan); + } else { + /* no channel, return pvt locked */ + return NULL; + } + + /* We had to hold the pvt 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(pvt); + + /* Look, no deadlock avoidance, hooray! */ + ast_channel_lock(chan); + ao2_lock(pvt); + + if (pvt->owner == chan) { + /* 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(chan); + ast_channel_unref(chan); + + ao2_unlock(pvt); + } + + /* If owner exists, it is locked and reffed */ + return pvt->owner; +} + +/* Find a companion dialog */ +struct sip_pvt *sip_pvt_find(const char *callid, const char *tag, const char *theirtag) +{ + struct sip_pvt pvt = {.callid = callid, .tag = tag, .theirtag = theirtag}; + + return ao2_t_find(sip_pvts, &pvt, OBJ_POINTER, "ao2_find of pvt in sip_pvts table"); +} + +/* 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_pvt and increment the refcount of both the sip_pvt + * and its owner channel. These two references are returned in the out parameters */ +int sip_pvt_find_full(const char *callid, const char *totag, const char *fromtag, struct sip_pvt **found_pvt, struct ast_channel **found_chan) +{ + RAII_VAR(struct sip_pvt *, pvt, NULL, ao2_cleanup); + struct sip_pvt tmp_pvt = {.callid = callid}; + + if (totag) { + ast_debug(4, "Looking for call-id %s (from-tag %s, to-tag %s)\n", callid, fromtag ? fromtag : "", totag ? totag : ""); + } + + /* Search dialogs and find the match */ + if ((pvt = ao2_t_find(sip_pvts, &tmp_pvt, OBJ_POINTER, "ao2_find of pvt in sip_pvts table"))) { + /* Go ahead and lock it (and its owner) before returning */ + SCOPED_LOCK(lock, pvt, ao2_lock, ao2_unlock); + + if (sip_config.pedanticsipchecking) { + unsigned char from_mismatch = FALSE, to_mismatch = FALSE; + + if (ast_strlen_zero(fromtag)) { + ast_debug(4, "Matched %s call for call-id %s - no from tag specified, pedantic check fails\n", + pvt->outgoing_call == TRUE ? "OUTGOING": "INCOMING", pvt->callid); + return -1; + } + + if (ast_strlen_zero(totag)) { + ast_debug(4, "Matched %s call for call-id %s - no to tag specified, pedantic check fails\n", + pvt->outgoing_call == TRUE ? "OUTGOING": "INCOMING", pvt->callid); + return -1; + } + + /* 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 totag is always compared to the local tag, regardless if this our call is an incoming or outgoing call. */ + from_mismatch = !!strcmp(fromtag, pvt->theirtag); + to_mismatch = !!strcmp(totag, pvt->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(&pvt->flags[1], SIP_DIALOG_ESTABLISHED)) || to_mismatch) { + if (from_mismatch) { + ast_debug(4, "Matched %s call for call-id %s - pedantic from tag check fails; their tag is %s our tag is %s\n", + pvt->outgoing_call == TRUE ? "OUTGOING": "INCOMING", pvt->callid, fromtag, pvt->theirtag); + } + + if (to_mismatch) { + ast_debug(4, "Matched %s call for call-id %s - pedantic to tag check fails; their tag is %s our tag is %s\n", + pvt->outgoing_call == TRUE ? "OUTGOING": "INCOMING", pvt->callid, totag, pvt->tag); + } + + return -1; + } + } + + if (totag) { + ast_debug(4, "Matched %s call - their tag is %s Our tag is %s\n", + pvt->outgoing_call == TRUE ? "OUTGOING": "INCOMING", pvt->theirtag, pvt->tag); + } + + *found_pvt = pvt; + + if (found_chan) { + *found_chan = pvt->owner ? ast_channel_ref(pvt->owner) : NULL; + } + } else { + /* return error if sip_pvt was not found */ + return -1; + } + + /* If we're here pvt has been copied to *found_pvt, prevent RAII_VAR cleanup */ + pvt = NULL; + + return 0; +} + +void sip_pvt_copy_data(struct sip_pvt *to_pvt, struct sip_pvt *from_pvt) +{ + ao2_lock(from_pvt); + + to_pvt->sa = from_pvt->sa; + to_pvt->recv = from_pvt->recv; + + sip_socket_copy_data(&to_pvt->socket, &from_pvt->socket); + + ast_copy_flags(&to_pvt->flags[0], &from_pvt->flags[0], SIP_FLAGS0_TO_COPY); + ast_copy_flags(&to_pvt->flags[1], &from_pvt->flags[1], SIP_FLAGS1_TO_COPY); + ast_copy_flags(&to_pvt->flags[2], &from_pvt->flags[2], SIP_FLAGS2_TO_COPY); + + /* Recalculate our side, and recalculate Call ID */ + sip_pvt_set_ouraddrfor(to_pvt, &to_pvt->sa, &to_pvt->ourip); + sip_pvt_change_callid(to_pvt, NULL); + + ast_string_field_set(to_pvt, tohost, from_pvt->tohost); + to_pvt->portinuri = from_pvt->portinuri; + to_pvt->fromdomainport = from_pvt->fromdomainport; + + ast_string_field_set(to_pvt, fullcontact, from_pvt->fullcontact); + ast_string_field_set(to_pvt, username, from_pvt->username); + ast_string_field_set(to_pvt, fromuser, from_pvt->fromuser); + ast_string_field_set(to_pvt, fromname, from_pvt->fromname); + + ao2_unlock(from_pvt); +} + +/* Initialize RTP portion of a dialog */ +static int sip_pvt_setup_rtp(struct sip_pvt *pvt) +{ + struct ast_sockaddr addr; + struct ast_rtp_engine_ice *ice; + + if (!sip_methods[pvt->method].need_rtp) { + return 0; + } + + if (!ast_sockaddr_isnull(&sip_config.rtpbindaddr)) { + ast_sockaddr_copy(&addr, &sip_config.rtpbindaddr); + } else { + ast_sockaddr_copy(&addr, &sip_config.bindaddr); + } + + /* Make sure previous RTP instances/FD's do not leak */ + sip_pvt_cleanup_rtp(pvt); + + if (!(pvt->rtp = ast_rtp_instance_new(pvt->engine, sip_sched_context, &addr, NULL))) { + return -1; + } + + if (!ast_test_flag(&pvt->flags[2], SIP_ICE_SUPPORT) && (ice = ast_rtp_instance_get_ice(pvt->rtp))) { + ice->stop(pvt->rtp); + } + + if (ast_test_flag(&pvt->flags[1], SIP_VIDEOSUPPORT_ALWAYS) || + (ast_test_flag(&pvt->flags[1], SIP_VIDEOSUPPORT) && (ast_format_cap_has_type(pvt->caps, AST_MEDIA_TYPE_VIDEO)))) { + if (!(pvt->vrtp = ast_rtp_instance_new(pvt->engine, sip_sched_context, &addr, NULL))) { + return -1; + } + + if (!ast_test_flag(&pvt->flags[2], SIP_ICE_SUPPORT) && (ice = ast_rtp_instance_get_ice(pvt->vrtp))) { + ice->stop(pvt->vrtp); + } + + ast_rtp_instance_set_timeout(pvt->vrtp, pvt->rtptimeout); + ast_rtp_instance_set_hold_timeout(pvt->vrtp, pvt->rtpholdtimeout); + ast_rtp_instance_set_keepalive(pvt->vrtp, pvt->rtpkeepalive); + + ast_rtp_instance_set_prop(pvt->vrtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_STANDARD); + ast_rtp_instance_set_qos(pvt->vrtp, sip_config.tos_video, sip_config.cos_video, "SIP VIDEO"); + } + + if (ast_test_flag(&pvt->flags[1], SIP_TEXTSUPPORT)) { + if (!(pvt->trtp = ast_rtp_instance_new(pvt->engine, sip_sched_context, &addr, NULL))) { + return -1; + } + + if (!ast_test_flag(&pvt->flags[2], SIP_ICE_SUPPORT) && (ice = ast_rtp_instance_get_ice(pvt->trtp))) { + ice->stop(pvt->trtp); + } + + /* Do not timeout text as its not constant*/ + ast_rtp_instance_set_keepalive(pvt->trtp, pvt->rtpkeepalive); + ast_rtp_instance_set_prop(pvt->trtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_STANDARD); + } + + ast_rtp_instance_set_timeout(pvt->rtp, pvt->rtptimeout); + ast_rtp_instance_set_hold_timeout(pvt->rtp, pvt->rtpholdtimeout); + ast_rtp_instance_set_keepalive(pvt->rtp, pvt->rtpkeepalive); + + ast_rtp_instance_set_prop(pvt->rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_STANDARD); + ast_rtp_instance_set_prop(pvt->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833); + ast_rtp_instance_set_prop(pvt->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&pvt->flags[0], SIP_RFC2833_COMPENSATE)); + + ast_rtp_instance_set_qos(pvt->rtp, sip_config.tos_audio, sip_config.cos_audio, "SIP RTP"); + sip_pvt_set_rtp_nat(pvt); + + return 0; +} + +/* Immediately stop RTP, VRTP and UDPTL as applicable */ +void sip_pvt_stop_rtp(struct sip_pvt *pvt) +{ + /* Immediately stop RTP, VRTP and UDPTL as applicable */ + if (pvt->rtp) { + ast_rtp_instance_stop(pvt->rtp); + } + + if (pvt->vrtp) { + ast_rtp_instance_stop(pvt->vrtp); + } + + if (pvt->trtp) { + ast_rtp_instance_stop(pvt->trtp); + } + + if (pvt->udptl) { + ast_udptl_stop(pvt->udptl); + } +} + +/* Cleanup the RTP and SRTP portions of a dialog. This procedure excludes vsrtp as it is initialized differently. */ +static void sip_pvt_cleanup_rtp(struct sip_pvt *pvt) +{ + if (pvt->rtp) { + ast_rtp_instance_destroy(pvt->rtp); + pvt->rtp = NULL; + } + + if (pvt->vrtp) { + ast_rtp_instance_destroy(pvt->vrtp); + pvt->vrtp = NULL; + } + + if (pvt->trtp) { + ast_rtp_instance_destroy(pvt->trtp); + pvt->trtp = NULL; + } + + if (pvt->srtp) { + ast_sdp_srtp_destroy(pvt->srtp); + pvt->srtp = NULL; + } + + if (pvt->tsrtp) { + ast_sdp_srtp_destroy(pvt->tsrtp); + pvt->tsrtp = NULL; + } +} + +void sip_pvt_setup_rtcp(struct sip_pvt *pvt, struct ast_rtp_instance *instance, int which, int remote_rtcp_mux) +{ + int local_rtcp_mux = ast_test_flag(&pvt->flags[2], SIP_RTCP_MUX); + int fd = -1; + + if (local_rtcp_mux && remote_rtcp_mux) { + ast_rtp_instance_set_prop(instance, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_MUX); + } else { + ast_rtp_instance_set_prop(instance, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_STANDARD); + fd = ast_rtp_instance_fd(instance, 1); + } + + if (pvt->owner) { + ast_channel_set_fd(pvt->owner, which, fd); + } +} + +/* Check the media stream list to see if the given type already exists */ +int sip_pvt_has_offered_media(struct sip_pvt *pvt, int type) +{ + struct sip_offered_media *offered_media = NULL; + + AST_LIST_TRAVERSE(&pvt->offered_media, offered_media, next) { + if (type == offered_media->type) { + return TRUE; + } + } + + return FALSE; +} + +/* Destroy SDP media offer list */ +void sip_pvt_free_offered_media(struct sip_pvt *pvt) +{ + struct sip_offered_media *offered_media; + + while ((offered_media = AST_LIST_REMOVE_HEAD(&pvt->offered_media, next))) { + ast_free(offered_media->decline_m_line); + ast_free(offered_media); + } +} + +/* 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_pvt_build_from_peer(struct sip_pvt *pvt, struct sip_peer *peer) +{ + struct sip_auth_head *peerauth; + + /* 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 (pvt->socket.type && sip_peer_check_transport(peer, pvt->socket.type)) { + return -1; + } + + sip_socket_copy_data(&pvt->socket, &peer->socket); + + if (!(ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) && + (!peer->maxms || (peer->lastms >= 0 && peer->lastms <= peer->maxms))) { + pvt->sa = ast_sockaddr_isnull(&peer->addr) ? peer->defaddr : peer->addr; + pvt->recv = pvt->sa; + } else { + return -1; + } + + /* Get flags directly from peer only as they are needed using pvt->relatedpeer */ + ast_copy_flags(&pvt->flags[0], &peer->flags[0], SIP_FLAGS0_TO_COPY); + ast_copy_flags(&pvt->flags[1], &peer->flags[1], SIP_FLAGS1_TO_COPY); + ast_copy_flags(&pvt->flags[2], &peer->flags[2], SIP_FLAGS2_TO_COPY); + + /* Take the peer's caps */ + if (peer->caps) { + ast_format_cap_remove_by_type(pvt->caps, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(pvt->caps, peer->caps, AST_MEDIA_TYPE_UNKNOWN); + } + + pvt->amaflags = peer->amaflags; + + ast_string_field_set(pvt, engine, peer->engine); + + pvt->rtptimeout = peer->rtptimeout; + pvt->rtpholdtimeout = peer->rtpholdtimeout; + pvt->rtpkeepalive = peer->rtpkeepalive; + + sip_route_copy(&pvt->route, &peer->path); + + if (!sip_route_empty(&pvt->route)) { + /* Parse SIP URI of first route-set hop and use it as target address */ + sip_parse_contact(sip_route_first_uri(&pvt->route), &pvt->sa); + } + + if (sip_pvt_setup_rtp(pvt)) { + return -1; + } + + if (pvt->rtp) { /* Audio */ + ast_rtp_instance_set_prop(pvt->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833); + ast_rtp_instance_set_prop(pvt->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&pvt->flags[0], SIP_RFC2833_COMPENSATE)); + /* Set Frame packetization */ + pvt->autoframing = peer->autoframing; + ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(pvt->rtp), ast_format_cap_get_framing(pvt->caps)); + } + + /* XXX TODO: get fields directly from peer only as they are needed using pvt->relatedpeer */ + ast_string_field_set(pvt, peername, peer->name); + ast_string_field_set(pvt, authname, peer->username); + ast_string_field_set(pvt, username, peer->username); + ast_string_field_set(pvt, peersecret, peer->secret); + ast_string_field_set(pvt, peermd5secret, peer->md5secret); + ast_string_field_set(pvt, mohsuggest, peer->mohsuggest); + ast_string_field_set(pvt, mohinterpret, peer->mohinterpret); + ast_string_field_set(pvt, tohost, peer->tohost); + ast_string_field_set(pvt, fullcontact, peer->fullcontact); + ast_string_field_set(pvt, accountcode, peer->accountcode); + ast_string_field_set(pvt, context, peer->context); + ast_string_field_set(pvt, cid_num, peer->cid_num); + ast_string_field_set(pvt, cid_name, peer->cid_name); + ast_string_field_set(pvt, cid_tag, peer->cid_tag); + ast_string_field_set(pvt, mwi_from, peer->mwi_from); + + if (!ast_strlen_zero(peer->parkinglot)) { + ast_string_field_set(pvt, parkinglot, peer->parkinglot); + } + + ast_string_field_set(pvt, engine, peer->engine); + sip_proxy_ref(pvt, sip_proxy_get(pvt, peer)); + + pvt->callgroup = peer->callgroup; + pvt->pickupgroup = peer->pickupgroup; + ast_unref_namedgroups(pvt->named_callgroups); + pvt->named_callgroups = ast_ref_namedgroups(peer->named_callgroups); + ast_unref_namedgroups(pvt->named_pickupgroups); + pvt->named_pickupgroups = ast_ref_namedgroups(peer->named_pickupgroups); + + ast_copy_string(pvt->zone, peer->zone, sizeof(pvt->zone)); + pvt->allowtransfer = peer->allowtransfer; + pvt->jointnoncodeccapability = pvt->noncodeccapability; + + /* Update dialog authorization credentials */ + ao2_lock(peer); + + if ((peerauth = peer->auth)) { + ao2_t_ref(peerauth, +1, "Ref peer auth for dialog"); + } + + ao2_unlock(peer); + ao2_lock(pvt); + + if (pvt->peerauth) { + ao2_t_ref(pvt->peerauth, -1, "Unref old dialog peer auth"); + } + + pvt->peerauth = peerauth; + ao2_unlock(pvt); + + pvt->maxcallbitrate = peer->maxcallbitrate; + pvt->disallowed_methods = peer->disallowed_methods; + + if (ast_strlen_zero(pvt->tohost)) { + ast_string_field_set(pvt, tohost, ast_sockaddr_stringify_host_remote(&pvt->sa)); + } + + if (!ast_strlen_zero(peer->fromdomain)) { + ast_string_field_set(pvt, fromdomain, peer->fromdomain); + + if (!pvt->initreq.headers) { + char *callid = ast_strdupa(pvt->callid); + char *sep; + + /* this sure looks to me like we are going to change the callid on this dialog!! */ + if ((sep = strchr(callid, '@'))) { + char *new_callid; + int callidlen; + + *sep = '\0'; + /* Change the dialog callid. */ + callidlen = strlen(callid) + strlen(peer->fromdomain) + 2; + new_callid = ast_alloca(callidlen); + + snprintf(new_callid, callidlen, "%s@%s", callid, peer->fromdomain); + sip_pvt_change_callid(pvt, new_callid); + } + } + } + + if (!ast_strlen_zero(peer->fromuser)) { + ast_string_field_set(pvt, fromuser, peer->fromuser); + } + + if (!ast_strlen_zero(peer->language)) { + ast_string_field_set(pvt, 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->maxms && peer->lastms) { + pvt->timer_t1 = peer->lastms < sip_config.t1min ? sip_config.t1min : peer->lastms; + } else { + pvt->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) { + pvt->timer_b = peer->timer_b; + } else { + pvt->timer_b = 64 * pvt->timer_t1; + } + + if (ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833 || ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + pvt->noncodeccapability |= AST_RTP_DTMF; + } else { + pvt->noncodeccapability &= ~AST_RTP_DTMF; + } + + pvt->directmediaacl = ast_duplicate_acl_list(peer->directmediaacl); + + if (peer->call_limit) { + ast_set_flag(&pvt->flags[0], SIP_CALL_LIMIT); + } + + if (!pvt->portinuri) { + pvt->portinuri = peer->portinuri; + } + + if (peer->chanvars) { + pvt->chanvars = ast_variables_dup(peer->chanvars); + } + + if (peer->fromdomainport) { + pvt->fromdomainport = peer->fromdomainport; + } + + pvt->callingpres = peer->callingpres; + pvt->donotdisturb = peer->donotdisturb; + + if (!ast_strlen_zero(peer->callforward)) { + ast_string_field_set(pvt, callforward, peer->callforward); + } + + return 0; +} + +/* create address structure from device name Or, if peer not found, find it in the global DNS */ +int sip_pvt_build(struct sip_pvt *pvt, const char *name, struct ast_sockaddr *addr, int newdialog) +{ + struct sip_peer *peer; + char host[MAXHOSTNAMELEN], service[MAXHOSTNAMELEN]; + int port, res = 0; + + AST_DECLARE_APP_ARGS(hostport, + AST_APP_ARG(host); + AST_APP_ARG(port); + ); + + AST_NONSTANDARD_RAW_ARGS(hostport, ast_strdupa(name), ':'); + + if (hostport.port) { + pvt->portinuri = TRUE; + } + + pvt->timer_t1 = sip_config.timer_t1; /* Default SIP retransmission timer T1 (RFC 3261) */ + pvt->timer_b = sip_config.timer_b; /* Default SIP transaction timer B (RFC 3261) */ + + if ((peer = sip_peer_find(name, NULL, TRUE, FALSE, 0))) { + if (newdialog) { + sip_socket_set_transport(&pvt->socket, 0); + } + + res = sip_pvt_build_from_peer(pvt, peer); + + pvt->relatedpeer = ao2_t_bump(peer, "sip_pvt_build: setting dialog's relatedpeer pointer"); + ao2_t_cleanup(peer, "sip_pvt_build: unref peer from sip_peer_find hashtab lookup"); + + return res; + } else if (ast_check_digits(name)) { + /* Although an IPv4 hostname *could* be represented as a 32-bit integer, it is uncommon and it makes dialing + * SIP/${EXTEN} for a peer that isn't defined resolve to an IP that is almost certainly not intended. It is + * much better to just reject purely numeric hostnames */ + ast_log(LOG_WARNING, "Purely numeric hostname (%s), and not a peer--rejecting!\n", name); + return -1; + } else { + pvt->rtptimeout = sip_config.rtptimeout; + pvt->rtpholdtimeout = sip_config.rtpholdtimeout; + pvt->rtpkeepalive = sip_config.rtpkeepalive; + + if (sip_pvt_setup_rtp(pvt)) { + return -1; + } + } + + ast_string_field_set(pvt, tohost, hostport.host); + pvt->allowed_methods &= ~sip_config.disallowed_methods; + + /* Get the outbound proxy information */ + sip_proxy_ref(pvt, sip_proxy_get(pvt, NULL)); + + if (addr) { + /* This address should be updated using dnsmgr */ + ast_sockaddr_copy(&pvt->sa, addr); + } else { + /* 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. */ + if (!hostport.port && sip_config.srvlookup) { + snprintf(service, sizeof(service), "_%s._%s.%s", + sip_srv_service(pvt->socket.type), sip_srv_protocol(pvt->socket.type), name); + + if ((res = ast_get_srv(NULL, host, sizeof(host), &port, service)) > 0) { + name = host; + } + } + + if (ast_sockaddr_resolve_first_af(&pvt->sa, name, 0, sip_transport_family(pvt->socket.type ? pvt->socket.type : AST_TRANSPORT_UDP))) { + ast_log(LOG_WARNING, "No such host: %s\n", name); + return -1; + } + + if (res > 0) { + ast_sockaddr_set_port(&pvt->sa, port); + } + } + + if (!pvt->socket.type) { + sip_socket_set_transport(&pvt->socket, AST_TRANSPORT_UDP); + } + + if (!ast_sockaddr_port(&pvt->sa)) { + ast_sockaddr_set_port(&pvt->sa, pvt->socket.type == AST_TRANSPORT_TLS ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + } + + ast_sockaddr_copy(&pvt->recv, &pvt->sa); + + return 0; +} + +/* Safely change the callid of the given SIP dialog. */ +void sip_pvt_change_callid(struct sip_pvt *pvt, const char *callid) +{ + int in_pvts, in_rtp_check; + char *old_callid = ast_strdupa(pvt->callid); + struct sip_pvt *old_pvt; + + ao2_lock(sip_pvts); + ao2_lock(sip_pvts_rtp_check); + + if ((old_pvt = ao2_t_callback(sip_pvts, OBJ_UNLINK | OBJ_POINTER, ao2_match_by_addr, pvt, "About to change the callid"))) { + ao2_t_ref(old_pvt, -1, "Removed dialog callid from sip_pvts"); + in_pvts = TRUE; + } else { + in_pvts = FALSE; + } + + if ((old_pvt = ao2_t_callback(sip_pvts_rtp_check, OBJ_UNLINK | OBJ_POINTER, ao2_match_by_addr, pvt, "About to change the callid"))) { + ao2_t_ref(old_pvt, -1, "Removed dialog callid from sip_pvts_rtp_check"); + in_rtp_check = TRUE; + } else { + in_rtp_check = FALSE; + } + + if (callid) { + ast_string_field_set(pvt, callid, callid); + } else { + sip_pvt_build_callid(pvt); + } + + if (in_pvts) { + ao2_t_link(sip_pvts, pvt, "New dialog callid, inserted back into sip_pvts"); + } + + if (in_rtp_check) { + ao2_t_link(sip_pvts_rtp_check, pvt, "New dialog callid, inserted back into sip_pvts_rtp_check"); + } + + ao2_unlock(sip_pvts_rtp_check); + ao2_unlock(sip_pvts); + + if (strcmp(old_callid, pvt->callid)) { + ast_debug(1, "SIP call-id changed from '%s' to '%s'\n", old_callid, pvt->callid); + } +} + +/* Build SIP Call-ID value for a non-REGISTER transaction. The passed in pvt must not be in a dialogs container since this function + * changes the hash key used by the container. */ +static void sip_pvt_build_callid(struct sip_pvt *pvt) +{ + char buf[33]; + const char *host = S_OR(pvt->fromdomain, ast_sockaddr_stringify_remote(&pvt->ourip)); + + ast_string_field_build(pvt, callid, "%s@%s", sip_generate_random_string(buf, sizeof(buf)), host); +} + +/* builds the sip_pvt's nonce field which is used for the authentication challenge. When forceupdate 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_pvt_build_nonce(struct sip_pvt *pvt, int forceupdate) +{ + if (pvt->stalenonce || forceupdate || ast_strlen_zero(pvt->nonce)) { + ast_string_field_build(pvt, nonce, "%08lx", (unsigned long) ast_random()); /* Create nonce for challenge */ + pvt->stalenonce = FALSE; + } +} + +/* Build a Via header for a request */ +void sip_pvt_build_via(struct sip_pvt *pvt) +{ + /* z9hG4bK is a magic cookie. See RFC 3261 section 8.1.1.7 */ + ast_string_field_build(pvt, via, "SIP/2.0/%s %s;branch=z9hG4bK%08x", + sip_pvt_get_transport(pvt), ast_sockaddr_stringify_remote(&pvt->ourip), (unsigned int) pvt->branch); +} + +/* Make our SIP dialog tag */ +void sip_pvt_build_our_tag(struct sip_pvt *pvt) +{ + ast_string_field_build(pvt, tag, "as%08lx", (unsigned long) ast_random()); +} + +/* Build contact header. This is the Contact header that we send out in SIP requests and responses involving this sip_pvt. + * 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_pvt_build_contact(struct sip_pvt *pvt, struct sip_request *req, int incoming) +{ + char tmp[SIP_BUFFER_SIZE]; + char *user = ast_uri_encode(pvt->exten, tmp, sizeof(tmp), ast_uri_sip_user); + int use_tls = FALSE; + char *transport = ast_strdupa(ast_transport2str(pvt->socket.type)); + + if (incoming) { + const char *record_route = sip_request_get_header(req, "Record-Route"); + + if (!strncmp(SIP_REQUEST_PART(req, rlpart2), "sips:", 5)) { + use_tls = TRUE; + } else if (record_route) { + char *record_route_uri = sip_get_in_brackets(ast_strdupa(record_route)); + + if (!strncmp(record_route_uri, "sips:", 5)) { + use_tls = TRUE; + } + } else { + const char *contact = sip_request_get_header(req, "Contact"); + char *contact_uri = sip_get_in_brackets(ast_strdupa(contact)); + + if (!strncmp(contact_uri, "sips:", 5)) { + use_tls = TRUE; + } + + } + } else { + const char *route = sip_request_get_header(req, "Route"); + + if (!strncmp(SIP_REQUEST_PART(req, rlpart2), "sips:", 5)) { + use_tls = TRUE; + } else if (route) { + char *route_uri = sip_get_in_brackets(ast_strdupa(route)); + + if (!strncmp(route_uri, "sips:", 5)) { + use_tls = TRUE; + } + } + } + + if (pvt->socket.type == AST_TRANSPORT_UDP) { + ast_string_field_build(pvt, our_contact, "<%s:%s%s%s>", + use_tls ? "sips" : "sip", + user, ast_strlen_zero(user) ? "" : "@", ast_sockaddr_stringify_remote(&pvt->ourip)); + } else { + ast_string_field_build(pvt, our_contact, "<%s:%s%s%s;transport=%s>", + use_tls ? "sips" : "sip", user, ast_strlen_zero(user) ? "" : "@", + ast_sockaddr_stringify_remote(&pvt->ourip), ast_str_to_lower(transport)); + } +} + +/* Build route list from Record-Route header */ +void sip_pvt_build_route(struct sip_pvt *pvt, struct sip_request *req, int backwards, int resp) +{ + int start = 0; + const char *header; + + /* Once a persistent route is set, don't fool with it */ + if (!sip_route_empty(&pvt->route) && pvt->route_persistent) { + ast_debug(1, "Retaining previous route: <%s>\n", sip_route_first_uri(&pvt->route)); + return; + } + + sip_route_clear(&pvt->route); + + /* We only want to create the route set the first time this is called except it is called from a provisional response.*/ + if (resp < 100 || resp > 199) { + pvt->route_persistent = TRUE; + } + + /* Build a tailq, then assign it to pvt->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 (;;) { + header = sip_request_get_header_full(req, "Record-Route", &start); + + if (*header == '\0') { + break; + } + + sip_route_parse(&pvt->route, header, backwards); + } + + /* Only append the contact if we are dealing with a strict router or have no route */ + if (sip_route_empty(&pvt->route) || sip_route_is_strict(&pvt->route)) { + /* 2nd append the Contact: if there is one */ + /* Can be multiple Contact headers, comma separated values - we just take the first */ + int len = 0; + const char *start, *end; + + header = sip_request_get_header(req, "Contact"); + + if ((start = strchr(header, '<'))) { + start += 1; + + if ((end = strchr(start, '>'))) { + end -= 1; + + header = start; + len = end - start; + } + } else { + len = strlen(header); + } + + if (header && len) { + sip_route_add(&pvt->route, header, len, FALSE); + } + } + + /* For debugging dump what we ended up with */ + if (sip_debug_test_pvt(pvt)) { + sip_route_dump(&pvt->route); + } +} + +/* Check Contact: URI of SIP message */ +void sip_pvt_extract_uri(struct sip_pvt *pvt, struct sip_request *req) +{ + char *contact, *uri; + + contact = ast_strdupa(sip_request_get_header(req, "Contact")); + uri = sip_get_in_brackets(contact); + + /* Cut the URI at the at sign after the @, not in the username part */ + uri = sip_uri_remove_parameters(uri); + + if (!ast_strlen_zero(uri)) { + ast_string_field_set(pvt, uri, uri); + } +} + +/* Set the peers nat flags if they are using auto_* settings */ +void sip_pvt_set_nat(const struct sip_pvt *pvt, struct sip_peer *peer) +{ + if (!pvt || !peer) { + return; + } + + if (ast_test_flag(&peer->flags[2], SIP_NAT_AUTO_RPORT)) { + if (pvt->natdetected) { + 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 (pvt->natdetected) { + ast_set_flag(&peer->flags[1], SIP_SYMMETRICRTP); + } else { + ast_clear_flag(&peer->flags[1], SIP_SYMMETRICRTP); + } + } +} + +/* Set nat mode on the various data sockets */ +void sip_pvt_set_rtp_nat(struct sip_pvt *pvt) +{ + const char *mode; + int natflags; + + natflags = ast_test_flag(&pvt->flags[1], SIP_SYMMETRICRTP); + mode = natflags ? "On" : "Off"; + + if (pvt->rtp) { + ast_debug(1, "Setting NAT on RTP to %s\n", mode); + ast_rtp_instance_set_prop(pvt->rtp, AST_RTP_PROPERTY_NAT, natflags); + } + + if (pvt->vrtp) { + ast_debug(1, "Setting NAT on VRTP to %s\n", mode); + ast_rtp_instance_set_prop(pvt->vrtp, AST_RTP_PROPERTY_NAT, natflags); + } + + if (pvt->udptl) { + ast_debug(1, "Setting NAT on UDPTL to %s\n", mode); + ast_udptl_setnat(pvt->udptl, natflags); + } + + if (pvt->trtp) { + ast_debug(1, "Setting NAT on TRTP to %s\n", mode); + ast_rtp_instance_set_prop(pvt->trtp, AST_RTP_PROPERTY_NAT, natflags); + } +} + +/* Check and see if the requesting UA is likely to be behind a NAT. If the requesting NAT is behind NAT, set the * 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_pvt_check_for_nat(struct sip_pvt *pvt, const struct ast_sockaddr *addr) +{ + if (!addr || !pvt) { + return; + } + + if (ast_sockaddr_cmp_addr(addr, &pvt->recv)) { + char *srcaddr = ast_strdupa(ast_sockaddr_stringify_addr(addr)); + char *dstaddr = ast_strdupa(ast_sockaddr_stringify_addr(&pvt->recv)); + + ast_debug(3, "NAT detected for %s / %s\n", srcaddr, dstaddr); + pvt->natdetected = TRUE; + + if (ast_test_flag(&pvt->flags[2], SIP_NAT_AUTO_RPORT)) { + ast_set_flag(&pvt->flags[0], SIP_NAT_FORCE_RPORT); + } + + if (ast_test_flag(&pvt->flags[2], SIP_NAT_AUTO_COMEDIA)) { + ast_set_flag(&pvt->flags[1], SIP_SYMMETRICRTP); + } + } else { + pvt->natdetected = FALSE; + + if (ast_test_flag(&pvt->flags[2], SIP_NAT_AUTO_RPORT)) { + ast_clear_flag(&pvt->flags[0], SIP_NAT_FORCE_RPORT); + } + + if (ast_test_flag(&pvt->flags[2], SIP_NAT_AUTO_COMEDIA)) { + ast_clear_flag(&pvt->flags[1], SIP_SYMMETRICRTP); + } + } + +} + +/* check Via: header for hostname, port and rport request/answer */ +void sip_pvt_check_via(struct sip_pvt *pvt, const struct sip_request *req) +{ + char *via, *sep, *maddr, *vaddr; + struct ast_sockaddr addr = {{0}}; + uint16_t port; + + via = ast_strdupa(sip_request_get_header(req, "Via")); + + /* Work on the leftmost value of the topmost Via header */ + if ((sep = strchr(via, ','))) { + *sep = '\0'; + } + + /* Check for rport */ + if ((sep = strstr(via, ";rport")) && (sep[6] != '=')) { /* rport query, not answer */ + ast_set_flag(&pvt->flags[1], SIP_RPORT_PRESENT); + ast_set_flag(&pvt->flags[0], SIP_NAT_RPORT_PRESENT); + } + + /* Check for maddr */ + if ((maddr = strstr(via, "maddr="))) { + maddr += 6; + sep = maddr + strspn(maddr, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.:[]"); + *sep = '\0'; + } + + if ((sep = strchr(via, ';'))) { + *sep = '\0'; + } + + if ((sep = strchr(via, ' '))) { + *sep = '\0'; + vaddr = ast_strip(sep + 1); + + if (strcasecmp(via, "SIP/2.0/UDP") && strcasecmp(via, "SIP/2.0/TCP") && strcasecmp(via, "SIP/2.0/TLS")) { + ast_log(LOG_WARNING, "Don't know how to respond via '%s'\n", via); + return; + } + + if (maddr && ast_sockaddr_resolve_first_af(&pvt->sa, maddr, 0, sip_transport_family(AST_TRANSPORT_UDP))) { + pvt->sa = pvt->recv; + } + + if (ast_sockaddr_resolve_first_af(&addr, vaddr, 0, sip_transport_family(AST_TRANSPORT_UDP))) { + ast_log(LOG_WARNING, "Could not resolve socket address for '%s'\n", vaddr); + port = SIP_STANDARD_PORT; + } else if (!(port = ast_sockaddr_port(&addr))) { + port = SIP_STANDARD_PORT; + ast_sockaddr_set_port(&addr, port); + } + + ast_sockaddr_set_port(&pvt->sa, port); + sip_pvt_check_for_nat(pvt, &addr); + + if (sip_debug_test_pvt(pvt)) { + ast_verb(3, "Sending to %s (%s)\n", ast_sockaddr_stringify(sip_pvt_real_dst(pvt)), sip_nat_mode2str(&pvt->flags[0])); + } + } +} + +/* Process the Via header according to RFC 3261 section 18.2.2. This function will update the destination of the response + * according to the Via header in the request and RFC 3261 section 18.2.2. We do not have a transport layer so we ignore + * certain values like the 'received' param (we set the destination address to the address the request came from in the + * sip_response_prepare() function). */ +int sip_pvt_process_via(struct sip_pvt *pvt, const struct sip_request *req) +{ + struct sip_via *via; + + if (!(via = sip_via_parse(sip_request_get_header(req, "Via")))) { + ast_log(LOG_ERROR, "error processing via header\n"); + return -1; + } + + if (via->maddr) { + if (ast_sockaddr_resolve_first_af(&pvt->sa, via->maddr, PARSE_PORT_FORBID, sip_transport_family(pvt->socket.type))) { + ast_log(LOG_WARNING, "Can't find address for maddr '%s'\n", via->maddr); + sip_via_free(via); + + return -1; + } + + if (ast_sockaddr_is_ipv4_multicast(&pvt->sa)) { + setsockopt(sip_socket_fd, IPPROTO_IP, IP_MULTICAST_TTL, &via->ttl, sizeof(via->ttl)); + } + } + + ast_sockaddr_set_port(&pvt->sa, via->port ? via->port : SIP_STANDARD_PORT); + sip_via_free(via); + + return 0; +} + +void sip_pvt_set_need_destroy(struct sip_pvt *pvt, const char *reason) +{ + if (pvt->final_destruction_scheduled) { + return; /* This is already scheduled for final destruction, let the scheduler take care of it. */ + } + + sip_history_append(pvt, "NeedDestroy", "Setting needdestroy because %s", reason); + + if (!pvt->needdestroy) { + pvt->needdestroy = TRUE; + ao2_t_link(sip_pvts_need_destroy, pvt, "link pvt into sip_pvts_need_destroy container"); + } +} + +/* Encapsulate setting of SIP_ALREADYGONE to be able to trace it with debugging */ +void sip_pvt_set_already_gone(struct sip_pvt *pvt) +{ + ast_debug(3, "Setting SIP_ALREADYGONE on dialog %s\n", pvt->callid); + pvt->alreadygone = TRUE; +} + +/* Set the owning channel on the \ref sip_pvt object */ +void sip_pvt_set_owner(struct sip_pvt *pvt, struct ast_channel *chan) +{ + pvt->owner = chan; + + if (pvt->rtp) { + ast_rtp_instance_set_channel_id(pvt->rtp, pvt->owner ? ast_channel_uniqueid(pvt->owner) : ""); + } + + if (pvt->vrtp) { + ast_rtp_instance_set_channel_id(pvt->vrtp, pvt->owner ? ast_channel_uniqueid(pvt->owner) : ""); + } + + if (pvt->trtp) { + ast_rtp_instance_set_channel_id(pvt->trtp, pvt->owner ? ast_channel_uniqueid(pvt->owner) : ""); + } +} + +/* 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 \verbatim 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_pvt_set_destination(struct sip_pvt *pvt, const char *uri) +{ + char *maddr, host[256]; + const char *sep; + int len, port; + int debug = sip_debug_test_pvt(pvt); + int use_tls = FALSE; + + if (debug) { + ast_verb(3, "sip_pvt_set_destination: Parsing <%s> for address/port to send to\n", uri); + } + + /* 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; + } + } + + len = strcspn(uri, ";>") + 1; + + if (len > sizeof(host)) { + len = sizeof(host); + } + + ast_copy_string(host, uri, len); + uri += len - 1; /* XXX bug here if string has been trimmed to sizeof(host) */ + + if (ast_sockaddr_resolve_first_af(&pvt->sa, host, 0, sip_transport_family(pvt->socket.type))) { + ast_log(LOG_WARNING, "Can't find address for host '%s'\n", host); + return; + } + + /* Got the host - but maybe there's a "maddr=" to override address? */ + maddr = strstr(uri, "maddr="); + + if (maddr) { + maddr += 6; + len = strspn(maddr, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.:[]") + 1; + + if (len > sizeof(host)) { + len = sizeof(host); + } + + ast_copy_string(host, maddr, len); + port = ast_sockaddr_port(&pvt->sa); + + if (ast_sockaddr_resolve_first_af(&pvt->sa, host, PARSE_PORT_FORBID, sip_transport_family(pvt->socket.type))) { + ast_log(LOG_WARNING, "Can't find address for host '%s'\n", host); + return; + } + + ast_sockaddr_set_port(&pvt->sa, port); + } + + if (!ast_sockaddr_port(&pvt->sa)) { + ast_sockaddr_set_port(&pvt->sa, use_tls ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + } + + if (debug) { + ast_verb(3, "sip_pvt_set_destination: set destination to %s\n", ast_sockaddr_stringify(&pvt->sa)); + } +} + +/* 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_pvt_get_destination(struct sip_pvt *pvt, struct sip_request *req) +{ + char *uri, *domain, *port, *from, *decoded_uri; + RAII_VAR(struct ast_features_pickup_config *, pickup_config, NULL, ao2_cleanup); + const char *pickupexten; + + if (!(pickup_config = ast_get_chan_features_pickup_config(pvt->owner))) { + ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n"); + pickupexten = ""; + } else { + /* Don't need to duplicate since channel is locked for the duration of this function */ + pickupexten = pickup_config->pickupexten; + } + + if (!req) { + req = &pvt->initreq; + } + + /* Find the request URI */ + uri = ast_strdupa(SIP_REQUEST_PART(req, rlpart2)); + uri = ast_strdupa(sip_get_in_brackets(uri)); + + if (sip_uri_parse(uri, "sip:,sips:", &uri, NULL, &domain, NULL)) { + ast_log(LOG_WARNING, "Not a SIP header (%s)?\n", uri); + return SIP_DESTINATION_INVALID_URI; + } + + sip_pedantic_decode(uri); + sip_pedantic_decode(domain); + + ast_sockaddr_split_hostport(domain, &domain, &port, PARSE_PORT_IGNORE); + ast_string_field_set(pvt, domain, domain); + + if (ast_strlen_zero(uri)) { + /* Either there really was no extension found or the request URI had encoded nulls that made the string "empty". Use "s" + * as the extension. */ + uri = "s"; + } + + /* Now find the From: caller ID and name. Why is this done in sip_pvt_get_destination? Isn't it already done? Needs to be checked. */ + from = ast_strdupa(sip_request_get_header(req, "From")); + + if (!ast_strlen_zero(from)) { + from = sip_get_in_brackets(from); + + if (sip_uri_parse(from, "sip:,sips:,tel:", &from, NULL, &domain, NULL)) { + ast_log(LOG_WARNING, "Not a SIP header (%s)?\n", from); + return SIP_DESTINATION_INVALID_URI; + } + + sip_pedantic_decode(from); + sip_pedantic_decode(domain); + ast_sockaddr_split_hostport(domain, &domain, &port, PARSE_PORT_IGNORE); + + ast_string_field_set(pvt, fromdomain, domain); + } + + if (!AST_LIST_EMPTY(&sip_domains)) { + char domain_context[AST_MAX_EXTENSION]; + + domain_context[0] = '\0'; + + if (!sip_domain_check(pvt->domain, domain_context, sizeof(domain_context))) { + if (!sip_config.allow_external_domains && (req->method == SIP_INVITE || req->method == SIP_REFER)) { + ast_debug(1, "Got SIP %s to non-local domain '%s'; refusing request.\n", sip_methods[req->method].name, pvt->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(&pvt->flags[1], SIP_HAVEPEERCONTEXT) && !ast_strlen_zero(domain_context)) { + ast_string_field_set(pvt, context, domain_context); + } + } + + /* If the request coming in is a subscription and subscribecontext has been specified use it */ + if (req->method == SIP_SUBSCRIBE && !ast_strlen_zero(pvt->subscribecontext)) { + ast_string_field_set(pvt, context, pvt->subscribecontext); + } + + if (sip_debug_test_pvt(pvt)) { + ast_verb(3, "Looking for %s in %s (domain %s)\n", uri, pvt->context, pvt->domain); + } + + /* Since extensions.conf can have unescaped characters, try matching a decoded uri in addition to the non-decoded uri. */ + decoded_uri = ast_strdupa(uri); + ast_uri_decode(decoded_uri, ast_uri_sip_user); + + /* If this is a subscription we actually just need to see if a hint exists for the extension */ + if (req->method == SIP_SUBSCRIBE) { + int set_decoded = FALSE; + + if (ast_get_hint(NULL, 0, NULL, 0, NULL, pvt->context, uri) || + (ast_get_hint(NULL, 0, NULL, 0, NULL, pvt->context, decoded_uri) && (set_decoded = TRUE))) { + if (req == &pvt->initreq) { + ast_string_field_set(pvt, exten, set_decoded ? decoded_uri : uri); + } + + 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, pvt->context, uri, 1, S_OR(pvt->cid_num, from))) { + if (req == &pvt->initreq) { + ast_string_field_set(pvt, exten, uri); + } + + return SIP_DESTINATION_EXTEN_FOUND; + } + + if (ast_exists_extension(NULL, pvt->context, decoded_uri, 1, S_OR(pvt->cid_num, from)) || !strcmp(decoded_uri, pickupexten)) { + if (req == &pvt->initreq) { + ast_string_field_set(pvt, exten, decoded_uri); + } + + return SIP_DESTINATION_EXTEN_FOUND; + } + } + + if (ast_test_flag(&sip_config.flags[1], SIP_ALLOWOVERLAP) && (ast_canmatch_extension(NULL, pvt->context, uri, 1, S_OR(pvt->cid_num, from)) + || ast_canmatch_extension(NULL, pvt->context, decoded_uri, 1, S_OR(pvt->cid_num, from)) + || !strncmp(decoded_uri, pickupexten, strlen(decoded_uri)))) { + /* Overlap dialing is enabled and we need more digits to match an extension. */ + return SIP_DESTINATION_EXTEN_MATCHMORE; + } + + return SIP_DESTINATION_EXTEN_NOT_FOUND; +} + +/* NAT fix - decide which IP address to use for Asterisk server? + * Using the localaddr structure built up with localnet statements in sip.conf 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_pvt_set_ouraddrfor(struct sip_pvt *pvt, const struct ast_sockaddr *them, struct ast_sockaddr *us) +{ + struct ast_sockaddr theirs; + /* Set want_remap to non-zero if we want to remap 'us' 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 = FALSE; + + /* starting guess for the internal address */ + ast_sockaddr_copy(us, &sip_internip); + /* now ask the system what would it use to talk to 'them' */ + ast_ouraddrfor(them, us); + ast_sockaddr_copy(&theirs, them); + + if (ast_sockaddr_is_ipv6(&theirs) && !ast_sockaddr_is_ipv4_mapped(&theirs)) { + if (sip_config.localaddr && !ast_sockaddr_isnull(&sip_config.externaddr) && !ast_sockaddr_is_any(&sip_config.bindaddr)) { + ast_log(LOG_WARNING, "Address remapping activated in sip.conf " + "but we're using IPv6, which doesn't need it. Please remove \"localnet\" and/or \"externaddr\" settings.\n"); + } + } else { + want_remap = sip_config.localaddr && !ast_sockaddr_isnull(&sip_config.externaddr) && ast_apply_ha(sip_config.localaddr, &theirs) == AST_SENSE_ALLOW; + } + + if (want_remap && (!sip_config.matchexternaddrlocally || !ast_apply_ha(sip_config.localaddr, us))) { + /* if we used externhost, see if it is time to refresh the info */ + if (sip_config.externexpire && time(NULL) >= sip_config.externexpire) { + if (ast_sockaddr_resolve_first_af(&sip_config.externaddr, sip_config.externhost, 0, AST_AF_INET)) { + ast_log(LOG_NOTICE, "Warning: Re-lookup of '%s' failed!\n", sip_config.externhost); + } + + sip_config.externexpire = time(NULL) + sip_config.externrefresh; + } + + if (!ast_sockaddr_isnull(&sip_config.externaddr)) { + ast_sockaddr_copy(us, &sip_config.externaddr); + + switch (pvt->socket.type) { + case AST_TRANSPORT_TCP: + if (!sip_config.externtcpport && ast_sockaddr_port(&sip_config.externaddr)) { + /* for consistency, default to the externaddr port */ + sip_config.externtcpport = ast_sockaddr_port(&sip_config.externaddr); + } + + if (!sip_config.externtcpport) { + sip_config.externtcpport = ast_sockaddr_port(&sip_tcp_session.local_address); + } + + if (!sip_config.externtcpport) { + sip_config.externtcpport = SIP_STANDARD_PORT; + } + + ast_sockaddr_set_port(us, sip_config.externtcpport); + break; + case AST_TRANSPORT_TLS: + if (!sip_config.externtlsport) { + sip_config.externtlsport = ast_sockaddr_port(&sip_tls_session.local_address); + } + + if (!sip_config.externtlsport) { + sip_config.externtlsport = SIP_STANDARD_TLS_PORT; + } + + ast_sockaddr_set_port(us, sip_config.externtlsport); + break; + case AST_TRANSPORT_UDP: + if (!ast_sockaddr_port(&sip_config.externaddr)) { + ast_sockaddr_set_port(us, ast_sockaddr_port(&sip_config.bindaddr)); + } + + break; + default: + break; + } + } + ast_debug(1, "Target address %s is not local, substituting externaddr\n", ast_sockaddr_stringify(them)); + } else { + /* no remapping, but we bind to a specific address, so use it. */ + switch (pvt->socket.type) { + 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(us, &sip_tcp_session.local_address); + } else { + ast_sockaddr_set_port(us, 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(us, &sip_tls_session.local_address); + } else { + ast_sockaddr_set_port(us, 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.bindaddr)) { + ast_sockaddr_copy(us, &sip_config.bindaddr); + } + + if (!ast_sockaddr_port(us)) { + ast_sockaddr_set_port(us, ast_sockaddr_port(&sip_config.bindaddr)); + } + } + } + + ast_debug(3, "Setting AST_TRANSPORT_%s with address %s\n", ast_transport2str(pvt->socket.type), ast_sockaddr_stringify(us)); +} + +/* 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_pvt_set_realm(struct sip_pvt *pvt, const struct sip_request *req) +{ + char domain[MAXHOSTNAMELEN]; + + if (!ast_strlen_zero(pvt->realm)) { + return; + } + + if (sip_config.domainsasrealm && !AST_LIST_EMPTY(&sip_domains)) { + /* Check From header first */ + if (!sip_get_domain(sip_request_get_header(req, "From"), domain, sizeof(domain))) { + if (sip_domain_check(domain, NULL, 0)) { + ast_string_field_set(pvt, realm, domain); + return; + } + } + + /* Check To header */ + if (!sip_get_domain(sip_request_get_header(req, "To"), domain, sizeof(domain))) { + if (sip_domain_check(domain, NULL, 0)) { + ast_string_field_set(pvt, realm, domain); + return; + } + } + } + + /* Use default realm from config file */ + ast_string_field_set(pvt, realm, sip_config.realm); +} + +void sip_pvt_set_dsp_detect(struct sip_pvt *pvt, int enabled) +{ + int features = 0; + + if (enabled) { + if (pvt->dsp) { + return; + } + + if (ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_INBAND || ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + if (pvt->rtp) { + ast_rtp_instance_dtmf_mode_set(pvt->rtp, AST_RTP_DTMF_MODE_INBAND); + } + + features |= DSP_FEATURE_DIGIT_DETECT; + } + + if (ast_test_flag(&pvt->flags[1], SIP_FAX_DETECT_CNG)) { + features |= DSP_FEATURE_FAX_DETECT; + } + + if (!features) { + return; + } + + if (!(pvt->dsp = ast_dsp_new())) { + return; + } + + ast_dsp_set_features(pvt->dsp, features); + + if (sip_config.relaxdtmf) { + ast_dsp_set_digitmode(pvt->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF); + } + } else { + if (pvt->dsp) { + ast_dsp_free(pvt->dsp); + pvt->dsp = NULL; + } + } +} + +/* A wrapper for sip_parse_allow geared toward sip_pvts This function, in addition to setting the allowed methods for a + * sip_pvt also will take into account the setting of the SIP_RPID_UPDATE flag. */ +unsigned int sip_pvt_set_allowed_methods(struct sip_pvt *pvt, struct sip_request *req) +{ + pvt->allowed_methods = sip_parse_allow(req); + pvt->allowed_methods &= ~pvt->disallowed_methods; + + return pvt->allowed_methods; +} + +/* Set all IP media addresses for this call called from sip_request_add_sdp() */ +void sip_pvt_get_our_media_addr(struct sip_pvt *pvt, int needvideo, int needtext, struct ast_sockaddr *addr, struct ast_sockaddr *vaddr, + struct ast_sockaddr *taddr, struct ast_sockaddr *dest, struct ast_sockaddr *vdest, struct ast_sockaddr *tdest) +{ + int use_externip = FALSE; + + /* First, get our address */ + ast_rtp_instance_get_local_address(pvt->rtp, addr); + + if (pvt->vrtp) { + ast_rtp_instance_get_local_address(pvt->vrtp, vaddr); + } + + if (pvt->trtp) { + ast_rtp_instance_get_local_address(pvt->trtp, taddr); + } + + /* 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_externip = ast_sockaddr_cmp_addr(&pvt->ourip, addr); + + /* 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(&pvt->redirip)) { /* If we have a redirection IP, use it */ + ast_sockaddr_copy(dest, &pvt->redirip); + } else { + /* Audio 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. + * + * Audio Destination Port: Provided by the RTP engine. */ + ast_sockaddr_copy(dest, + !ast_sockaddr_isnull(&sip_config.mediaaddr) ? &sip_config.mediaaddr : + !ast_sockaddr_is_any(addr) && !use_externip ? addr : + &pvt->ourip); + ast_sockaddr_set_port(dest, ast_sockaddr_port(addr)); + } + + if (needvideo) { + /* Determine video destination */ + if (!ast_sockaddr_isnull(&pvt->vredirip)) { + ast_sockaddr_copy(vdest, &pvt->vredirip); + } else { + /* 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. + * + * Video Destination Port: Provided by the RTP engine. */ + ast_sockaddr_copy(vdest, + !ast_sockaddr_isnull(&sip_config.mediaaddr) ? &sip_config.mediaaddr : + !ast_sockaddr_is_any(vaddr) && !use_externip ? vaddr : + &pvt->ourip); + ast_sockaddr_set_port(vdest, ast_sockaddr_port(vaddr)); + } + } + + if (needtext) { + /* Determine text destination */ + if (!ast_sockaddr_isnull(&pvt->tredirip)) { + ast_sockaddr_copy(tdest, &pvt->tredirip); + } else { + /* 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. + * + * Text Destination Port: Provided by the RTP engine. */ + ast_sockaddr_copy(tdest, + !ast_sockaddr_isnull(&sip_config.mediaaddr) ? &sip_config.mediaaddr : + !ast_sockaddr_is_any(taddr) && !use_externip ? taddr : + &pvt->ourip); + ast_sockaddr_set_port(tdest, ast_sockaddr_port(taddr)); + } + } +} + +/* The real destination address for a write */ +const struct ast_sockaddr *sip_pvt_real_dst(const struct sip_pvt *pvt) +{ + if (pvt->outboundproxy) { + return &pvt->outboundproxy->addr; + } + + return ast_test_flag(&pvt->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&pvt->flags[0], SIP_NAT_RPORT_PRESENT) ? &pvt->recv : &pvt->sa; +} + +/* Return transport of dialog. this is based on a false assumption. We don't always use the outbound proxy for all requests + * in a dialog. It depends on the "force" parameter. The FIRST request is always sent to the ob proxy. Fix this function to + * work correctly */ +const char *sip_pvt_get_transport(struct sip_pvt *pvt) +{ + if (pvt->outboundproxy && pvt->outboundproxy->transport) { + sip_socket_set_transport(&pvt->socket, pvt->outboundproxy->transport); + } + + return ast_transport2str(pvt->socket.type); +} + +/* Initialize the initital request packet in the pvt structure. */ +void sip_pvt_initial_request(struct sip_pvt *pvt, struct sip_request *req) +{ + if (pvt->initreq.headers) { + ast_debug(1, "Initializing already initialized SIP dialog %s (presumably reinvite)\n", pvt->callid); + } else { + ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].name, pvt->callid); + } + + /* Use this as the basis */ + sip_request_copy(&pvt->initreq, req); + sip_request_parse(&pvt->initreq); + + if (req->debug) { + ast_verb(3, "Initreq: %d headers, %d lines\n", pvt->initreq.headers, pvt->initreq.lines); + } +} + +/* Change hold state for a call */ +void sip_pvt_change_onhold(struct sip_pvt *pvt, struct sip_request *req, int onhold, int sendonly) +{ + if (sip_config.notifyhold && (!onhold || !ast_test_flag(&pvt->flags[1], SIP_CALL_ONHOLD))) { + if (pvt->relatedpeer) { + /* If they put someone on hold, increment the value... otherwise decrement it */ + ast_atomic_fetchadd_int(&pvt->relatedpeer->onhold, onhold ? +1 : -1); + + /* Request device state update */ + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", pvt->relatedpeer->name); + } + } + + sip_history_append(pvt, onhold ? "Hold" : "Unhold", "%s", ast_str_buffer(req->data)); + + if (!onhold) { /* Put off remote hold */ + ast_clear_flag(&pvt->flags[1], SIP_CALL_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(&pvt->flags[1], SIP_CALL_ONHOLD); + + if (sendonly == 1) { /* One directional hold (sendonly/recvonly) */ + ast_set_flag(&pvt->flags[1], SIP_CALL_ONHOLD_ONEDIR); + } else if (sendonly == 2) { /* Inactive stream */ + ast_set_flag(&pvt->flags[1], SIP_CALL_ONHOLD_INACTIVE); + } else { + ast_set_flag(&pvt->flags[1], SIP_CALL_ONHOLD_ACTIVE); + } +} + +/* sip_pvt_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_pvt_update_call_counter(struct sip_pvt *pvt, int event) +{ + struct sip_peer *peer = NULL; + + ast_debug(3, "Updating call counter for %s call\n", pvt->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(&pvt->flags[0], SIP_CALL_LIMIT) && !ast_test_flag(&pvt->flags[1], SIP_CALL_ONHOLD)) { + return 0; + } + + /* Check the list of devices */ + if (!pvt->relatedpeer) { + ast_debug(2, "%s is not a local device, no call limit\n", pvt->peername); + return 0; + } + + peer = ao2_t_bump(pvt->relatedpeer, "ref related peer for sip_pvt_update_call_counter"); + + switch (event) { + /* incoming and outgoing affects the inuse counter */ + case SIP_DEC_CALL_LIMIT: + /* Decrement ringing count if applicable */ + ao2_lock(pvt); + ao2_lock(peer); + + if (peer->ringing > 0) { + if (ast_test_flag(&pvt->flags[0], SIP_INC_RINGING)) { + peer->ringing--; + ast_clear_flag(&pvt->flags[0], SIP_INC_RINGING); + } + } else { + peer->ringing = 0; + } + + if (peer->inuse > 0) { + if (ast_test_flag(&pvt->flags[0], SIP_INC_COUNT)) { + peer->inuse--; + ast_clear_flag(&pvt->flags[0], SIP_INC_COUNT); + } + } else { + peer->inuse = 0; + } + + + if (ast_test_flag(&pvt->flags[1], SIP_CALL_ONHOLD) && sip_config.notifyhold) { + peer->onhold--; + ast_clear_flag(&pvt->flags[1], SIP_CALL_ONHOLD); + } + + ao2_unlock(peer); + ao2_unlock(pvt); + + if (sip_debug) { + ast_debug(2, "Call %s %s '%s' removed from call limit %d\n", + pvt->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", + pvt->outgoing_call ? "to" : "from", "peer", peer->name, peer->call_limit); + ao2_t_cleanup(peer, "sip_pvt_update_call_counter: unref peer pvt, call limit exceeded"); + + return -1; + } + } + + ao2_lock(pvt); + ao2_lock(peer); + + if (event == SIP_INC_CALL_RINGING) { + if (!ast_test_flag(&pvt->flags[0], SIP_INC_RINGING)) { + peer->ringing++; + ast_set_flag(&pvt->flags[0], SIP_INC_RINGING); + } + } + + if (!ast_test_flag(&pvt->flags[0], SIP_INC_COUNT)) { + peer->inuse++; + ast_set_flag(&pvt->flags[0], SIP_INC_COUNT); + } + + ao2_unlock(peer); + ao2_unlock(pvt); + + if (sip_debug) { + ast_debug(2, "Call %s %s '%s' is %d out of %d\n", + pvt->outgoing_call ? "to" : "from", "peer", peer->name, peer->inuse, peer->call_limit); + } + + break; + case SIP_DEC_CALL_RINGING: + ao2_lock(pvt); + ao2_lock(peer); + + if (ast_test_flag(&pvt->flags[0], SIP_INC_RINGING)) { + if (peer->ringing > 0) { + peer->ringing--; + } + + ast_clear_flag(&pvt->flags[0], SIP_INC_RINGING); + } + + ao2_unlock(peer); + ao2_unlock(pvt); + + break; + default: + ast_log(LOG_ERROR, "sip_pvt_update_call_counter(%s, %d) called with no event!\n", peer->name, event); + break; + } + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + ao2_t_cleanup(peer, "sip_pvt_update_call_counter: ao2_t_cleanup from call counter"); + + return 0; +} + +/* Notify peer that the connected line has changed */ +void sip_pvt_update_connected_line(struct sip_pvt *pvt, const void *data, size_t datalen) +{ + struct ast_party_id connected_id = ast_channel_connected_effective_id(pvt->owner); + + if (!ast_test_flag(&pvt->flags[0], SIP_SENDRPID)) { + return; + } + + sip_history_append(pvt, "ConnectedLine", "%s party is now %s <%s>", + ast_test_flag(&pvt->flags[0], SIP_OUTGOING) ? "Calling" : "Called", + S_COR(connected_id.name.valid, connected_id.name.str, ""), + S_COR(connected_id.number.valid, connected_id.number.str, "")); + + if (ast_channel_state(pvt->owner) == AST_STATE_UP || ast_test_flag(&pvt->flags[0], SIP_OUTGOING)) { + struct sip_request req; + + if (sip_method_allowed(&pvt->allowed_methods, SIP_UPDATE) && !ast_strlen_zero(pvt->okcontacturi)) { + sip_request_prepare(&req, pvt, SIP_UPDATE, 0, TRUE); + sip_request_add_rpid(&req, pvt); + + sip_request_send(pvt, &req, SIP_SEND_CRITICAL, pvt->ocseq); + } else if (!pvt->pendinginvite && (pvt->invitestate == SIP_INVITE_CONFIRMED || pvt->invitestate == SIP_INVITE_TERMINATED)) { + sip_request_prepare(&req, pvt, ast_test_flag(&pvt->flags[0], SIP_REINVITE_UPDATE) ? SIP_UPDATE : SIP_INVITE, 0, TRUE); + + sip_request_add_header(&req, "Allow", SIP_ALLOWED_METHODS); + sip_request_add_supported(&req, pvt); + + sip_request_add_rpid(&req, pvt); + sip_request_add_sdp(&req, pvt, FALSE, TRUE, FALSE); + + sip_pvt_initial_request(pvt, &req); + pvt->lastinvite = pvt->ocseq; + ast_set_flag(&pvt->flags[0], SIP_OUTGOING); + + sip_request_send(pvt, &req, SIP_SEND_CRITICAL, pvt->ocseq); + } else { + /* We cannot send the update yet, so we have to wait until we can */ + ast_set_flag(&pvt->flags[0], SIP_NEEDREINVITE); + } + } else { + struct sip_request resp; + + if (ast_channel_state(pvt->owner) == AST_STATE_RING && !ast_test_flag(&pvt->flags[0], SIP_PROGRESS_SENT)) { + sip_response_prepare(&resp, pvt, "180 Ringing", &pvt->initreq); + sip_request_add_rpid(&resp, pvt); + + sip_response_send(pvt, &resp, SIP_SEND_UNRELIABLE, 0); + ast_set_flag(&pvt->flags[0], SIP_RINGING); + } else if (ast_channel_state(pvt->owner) == AST_STATE_RINGING) { + sip_response_prepare(&resp, pvt, "183 Session Progress", &pvt->initreq); + sip_request_add_rpid(&resp, pvt); + + sip_response_send(pvt, &resp, SIP_SEND_UNRELIABLE, 0); + ast_set_flag(&pvt->flags[0], SIP_PROGRESS_SENT); + } else { + ast_set_flag(&pvt->flags[1], SIP_CONNECTLINEUPDATE_PEND); + ast_debug(1, "Unable able to send update to '%s' in state '%s'\n", + ast_channel_name(pvt->owner), ast_state2str(ast_channel_state(pvt->owner))); + } + } +} + +void sip_pvt_queue_connected_line_update(struct sip_pvt *pvt, int source) +{ + struct ast_party_connected_line connected; + struct ast_set_party_connected_line update_connected; + + ast_party_connected_line_init(&connected); + memset(&update_connected, 0, sizeof(update_connected)); + + update_connected.id.number = TRUE; + connected.id.number.valid = TRUE; + connected.id.number.str = (char *) pvt->cid_num; + connected.id.number.presentation = pvt->callingpres; + + update_connected.id.name = TRUE; + connected.id.name.valid = TRUE; + connected.id.name.str = (char *) pvt->cid_name; + connected.id.name.presentation = pvt->callingpres; + + /* Invalidate any earlier private connected id representation */ + ast_set_party_id_all(&update_connected.priv); + + connected.id.tag = (char *) pvt->cid_tag; + connected.source = source; + + ast_channel_queue_connected_line_update(pvt->owner, &connected, &update_connected); +} + +/* update redirecting information for a channel based on headers */ +void sip_pvt_set_redirecting(struct sip_pvt *pvt, struct sip_request *req, struct ast_party_redirecting *redirecting, + struct ast_set_party_redirecting *update_redirecting, int set_call_forward) +{ + char *from_name = NULL; + char *from_number = NULL; + char *to_name = NULL; + char *to_number = NULL; + char *reason = NULL; + int code = AST_REDIRECTING_REASON_UNCONDITIONAL; + int res = 0; + + res = sip_parse_diversion(pvt, req, &from_name, &from_number, &code, &reason); + + if (res == -1) { + if (req->method == SIP_RESPONSE) { + sip_get_name_and_number(sip_request_get_header(req, "To"), &from_name, &from_number); + } else { + return; + } + } + + /* At this point, all redirecting "from" info should be filled in appropriately on to the "to" info */ + if (req->method == SIP_RESPONSE) { + sip_parse_redirect_contact(pvt, req, &to_name, &to_number, set_call_forward); + } else { + sip_get_name_and_number(sip_request_get_header(req, "To"), &to_name, &to_number); + } + + if (!ast_strlen_zero(from_number)) { + ast_debug(3, "Got redirecting from number %s\n", from_number); + + update_redirecting->from.number = TRUE; + redirecting->from.number.valid = TRUE; + + ast_free(redirecting->from.number.str); + redirecting->from.number.str = from_number; + } else { + ast_free(from_number); + } + + if (!ast_strlen_zero(from_name)) { + ast_debug(3, "Got redirecting from name %s\n", from_name); + + update_redirecting->from.name = TRUE; + redirecting->from.name.valid = TRUE; + + ast_free(redirecting->from.name.str); + redirecting->from.name.str = from_name; + } else { + ast_free(from_name); + } + + if (!ast_strlen_zero(pvt->cid_tag)) { + ast_free(redirecting->from.tag); + redirecting->from.tag = ast_strdup(pvt->cid_tag); + + ast_free(redirecting->to.tag); + redirecting->to.tag = ast_strdup(pvt->cid_tag); + } + + if (!ast_strlen_zero(to_number)) { + ast_debug(3, "Got redirecting to number %s\n", to_number); + + update_redirecting->to.number = TRUE; + redirecting->to.number.valid = TRUE; + + ast_free(redirecting->to.number.str); + redirecting->to.number.str = to_number; + } else { + ast_free(to_number); + } + + if (!ast_strlen_zero(to_name)) { + ast_debug(3, "Got redirecting to name %s\n", to_name); + + update_redirecting->to.name = TRUE; + redirecting->to.name.valid = TRUE; + + ast_free(redirecting->to.name.str); + redirecting->to.name.str = to_name; + } else { + ast_free(to_name); + } + + redirecting->reason.code = code; + ast_free(redirecting->reason.str); + redirecting->reason.str = reason; + + if (reason) { + ast_debug(3, "Got redirecting reason %s\n", ast_strlen_zero(reason) ? sip_reason2str(&redirecting->reason) : reason); + } +} + +/* Send a provisional response indicating that a call was redirected */ +void sip_pvt_update_redirecting(struct sip_pvt *pvt, const void *data, size_t datalen) +{ + struct sip_request resp; + + if (ast_channel_state(pvt->owner) == AST_STATE_UP || ast_test_flag(&pvt->flags[0], SIP_OUTGOING)) { + return; + } + + sip_response_prepare(&resp, pvt, "181 Call is being forwarded", &pvt->initreq); + sip_request_add_diversion(&resp, pvt); + + sip_response_send(pvt, &resp, SIP_SEND_UNRELIABLE, 0); +} + +/* 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_pvt_parse_reply_digest(struct sip_pvt *pvt, struct sip_request *req, char *header, int method, char *digest, int digestlen) +{ + char *authtoken, *oldnonce; + int start = 0; + /* table of recognised keywords, and places where they should be copied */ + struct sip_digest_keys keys[] = { + [SIP_DIGEST_REALM] = {"realm=", ""}, + [SIP_DIGEST_NONCE] = {"nonce=", ""}, + [SIP_DIGEST_OPAQUE] = {"opaque=", ""}, + [SIP_DIGEST_QOP] = {"qop=", ""}, + [SIP_DIGEST_DOMAIN] = {"domain=", ""}, + [SIP_DIGEST_LAST] = {NULL, NULL}, + }; + + do { + authtoken = ast_strdupa(sip_request_get_header_full(req, header, &start)); + + if (ast_strlen_zero(authtoken)) { + return -1; + } + } while (strcasestr(authtoken, "algorithm=") && !strcasestr(authtoken, "algorithm=MD5")); + + if (strncasecmp(authtoken, "Digest ", 7)) { + ast_log(LOG_WARNING, "missing Digest.\n"); + return -1; + } + + sip_parse_digest(authtoken + 7, keys); + oldnonce = ast_strdupa(pvt->nonce); + + ast_string_field_set(pvt, realm, keys[SIP_DIGEST_REALM].value); + ast_string_field_set(pvt, nonce, keys[SIP_DIGEST_NONCE].value); + ast_string_field_set(pvt, opaque, keys[SIP_DIGEST_OPAQUE].value); + ast_string_field_set(pvt, qop, keys[SIP_DIGEST_QOP].value); + ast_string_field_set(pvt, domain, keys[SIP_DIGEST_DOMAIN].value); + + /* Reset nonce count */ + if (strcmp(pvt->nonce, oldnonce)) { + pvt->noncecount = 0; + } + + /* Save auth data for following registrations */ + if (pvt->registry) { + struct sip_registry *registry = pvt->registry; + + if (strcmp(registry->nonce, pvt->nonce)) { + ast_string_field_set(registry, realm, pvt->realm); + ast_string_field_set(registry, nonce, pvt->nonce); + ast_string_field_set(registry, authdomain, pvt->domain); + ast_string_field_set(registry, opaque, pvt->opaque); + ast_string_field_set(registry, qop, pvt->qop); + + registry->noncecount = 0; + } + } + + return sip_pvt_build_reply_digest(pvt, method, digest, digestlen); +} + +/* Build reply digest. Build digest challenge for authentication of registrations and calls Also used for authentication of BYE */ +int sip_pvt_build_reply_digest(struct sip_pvt *pvt, int method, char* digest, int digestlen) +{ + char a1[256], a2[512], a1_hash[64], a2_hash[64], resp[1024], resp_hash[64]; + char uri[256], opaque[256], cnonce[80]; + const char *username, *secret, *md5secret; + struct sip_auth *auth; /* Realm authentication credential */ + struct sip_auth_head *auths; + + if (!ast_strlen_zero(pvt->domain)) { + snprintf(uri, sizeof(uri), "%s:%s", pvt->socket.type == AST_TRANSPORT_TLS ? "sips" : "sip", pvt->domain); + } else if (!ast_strlen_zero(pvt->uri)) { + ast_copy_string(uri, pvt->uri, sizeof(uri)); + } else { + snprintf(uri, sizeof(uri), "%s:%s@%s", pvt->socket.type == AST_TRANSPORT_TLS ? "sips" : "sip", pvt->username, ast_sockaddr_stringify_host_remote(&pvt->sa)); + } + + snprintf(cnonce, sizeof(cnonce), "%08lx", (unsigned long) ast_random()); + + /* Check if we have peer credentials */ + ao2_lock(pvt); + + if ((auths = pvt->peerauth)) { + ao2_t_ref(auths, +1, "Ref peer auth for digest"); + } + + ao2_unlock(pvt); + + if (!(auth = sip_auth_find(auths, pvt->realm))) { + /* If not, check global credentials */ + if (auths) { + ao2_t_ref(auths, -1, "Unref peer auth for digest"); + } + + ast_mutex_lock(&sip_auth_lock); + + if ((auths = sip_auths)) { + ao2_t_ref(auths, +1, "Ref global auth for digest"); + } + + ast_mutex_unlock(&sip_auth_lock); + auth = sip_auth_find(auths, pvt->realm); + } + + if (auth) { + ast_debug(3, "Using realm %s from peer %s %s\n", auth->username, pvt->peername, pvt->username); + + username = auth->username; + secret = auth->secret; + md5secret = auth->md5secret; + + if (sip_debug) { + ast_debug(1, "Using realm %s authentication for call %s\n", pvt->realm, pvt->callid); + } + } else { + /* No authentication, use peer or register= config */ + username = pvt->authname; + secret = pvt->relatedpeer && !ast_strlen_zero(pvt->relatedpeer->remotesecret) ? pvt->relatedpeer->remotesecret : pvt->peersecret; + md5secret = pvt->peermd5secret; + } + + if (ast_strlen_zero(username)) { + /* We have no authentication */ + if (auths) { + ao2_t_ref(auths, -1, "Unref auth for digest"); + } + + return -1; + } + + /* Calculate SIP digest response */ + snprintf(a1, sizeof(a1), "%s:%s:%s", username, pvt->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); + pvt->noncecount++; + + if (!ast_strlen_zero(pvt->qop)) { + snprintf(resp, sizeof(resp), "%s:%s:%08x:%s:%s:%s", a1_hash, pvt->nonce, (unsigned) pvt->noncecount, cnonce, "auth", a2_hash); + } else { + snprintf(resp, sizeof(resp), "%s:%s:%s", a1_hash, pvt->nonce, a2_hash); + } + + ast_md5_hash(resp_hash, resp); + + /* only include the opaque string if it's set */ + if (!ast_strlen_zero(pvt->opaque)) { + snprintf(opaque, sizeof(opaque), ", opaque=\"%s\"", pvt->opaque); + } else { + opaque[0] = '\0'; + } + + /* We hard code our qop to "auth" for now. */ + if (!ast_strlen_zero(pvt->qop)) { + snprintf(digest, digestlen, "Digest username=\"%s\", realm=\"%s\", algorithm=MD5, uri=\"%s\", nonce=\"%s\", response=\"%s\"%s, qop=auth, cnonce=\"%s\", nc=%08x", + username, pvt->realm, uri, pvt->nonce, resp_hash, opaque, cnonce, (unsigned) pvt->noncecount); + } else { + snprintf(digest, digestlen, "Digest username=\"%s\", realm=\"%s\", algorithm=MD5, uri=\"%s\", nonce=\"%s\", response=\"%s\"%s", + username, pvt->realm, uri, pvt->nonce, resp_hash, opaque); + } + + sip_history_append(pvt, "AuthResp", "Auth response sent for %s in realm %s - nc %d", username, pvt->realm, pvt->noncecount); + + if (auths) { + ao2_t_ref(auths, -1, "Unref auth for digest"); + } + + return 0; +} + +/* Check user authorization from peer definition */ +static int sip_pvt_check_auth(struct sip_pvt *pvt, struct sip_request *req, const char *username, const char *secret, const char *md5secret, + const char *uri, int reliable) +{ + char *reqheader, *respheader, *authtoken; + int bogus = FALSE; + int wrongnonce = FALSE; + int authok = FALSE; + const char *usednonce = pvt->nonce; + char a1[256]; + char a2[512]; + char a1_hash[64]; + char a2_hash[64]; + char resp[1024]; + char resp_hash[64] = ""; + /* Always auth with WWW-auth since we're NOT a proxy Using proxy-auth in a B2BUA may block proxy authorization in + * the same transaction */ + const char *msg = "401 Unauthorized"; + /* table of recognised keywords, and their value in the digest */ + struct sip_digest_keys keys[] = { + [SIP_DIGEST_RESPONSE] = {"response=", ""}, + [SIP_DIGEST_URI] = {"uri=", ""}, + [SIP_DIGEST_USERNAME] = {"username=", ""}, + [SIP_DIGEST_NONCE] = {"nonce=", ""}, + [SIP_DIGEST_LAST] = {NULL, NULL} + }; + + /* Always OK if no secret */ + if (ast_strlen_zero(secret) && ast_strlen_zero(md5secret)) { + return SIP_AUTH_SUCCESSFUL; + } else if (ast_test_flag(&pvt->flags[1], SIP_CISCO_USECALLMANAGER) && (req->method == SIP_REFER || req->method == SIP_PUBLISH)) { + return SIP_AUTH_SUCCESSFUL; /* Buggy Cisco USECALLMANAGER phones can't auth REFER or PUBLISH correctly */ + } + + /* Note: the apparent swap of arguments below, compared to other usages of sip_auth_headers(). */ + sip_auth_headers(SIP_WWW_AUTH, &respheader, &reqheader); + authtoken = ast_strdupa(sip_request_get_header(req, reqheader)); + + if (req->ignore && !ast_strlen_zero(pvt->nonce) && ast_strlen_zero(authtoken)) { + /* 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_auth(pvt, msg, req, pvt->nonce, reliable, respheader, FALSE); + /* Schedule auto destroy in 32 seconds (according to RFC 3261) */ + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + } + + return SIP_AUTH_CHALLENGE_SENT; + } else if (ast_strlen_zero(pvt->nonce) || ast_strlen_zero(authtoken)) { + /* We have no auth, so issue challenge and request authentication */ + sip_pvt_build_nonce(pvt, TRUE); /* Create nonce for challenge */ + sip_send_response_with_auth(pvt, msg, req, pvt->nonce, reliable, respheader, FALSE); + + /* Schedule auto destroy in 32 seconds */ + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + return SIP_AUTH_CHALLENGE_SENT; + } + + sip_parse_digest(authtoken, keys); + + /* 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; + } + + /* Verify that digest username matches the username we auth as */ + if (strcmp(username, keys[SIP_DIGEST_USERNAME].value) && !bogus) { + ast_log(LOG_WARNING, "username mismatch, have <%s>, digest has <%s>\n", + username, keys[SIP_DIGEST_USERNAME].value); + + /* Oops, we're trying something here */ + return SIP_AUTH_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 (strcasecmp(pvt->nonce, keys[SIP_DIGEST_NONCE].value) || pvt->stalenonce) { + wrongnonce = TRUE; + usednonce = keys[SIP_DIGEST_NONCE].value; + } else { + pvt->stalenonce = TRUE; /* now, since the nonce has a response, mark it as stale so it can't be sent or responded to again */ + } + + /* 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, pvt->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[req->method].name, S_OR(keys[SIP_DIGEST_URI].value, uri)); + ast_md5_hash(a2_hash, a2); + + snprintf(resp, sizeof(resp), "%s:%s:%s", a1_hash, usednonce, a2_hash); + ast_md5_hash(resp_hash, resp); + + /* lastly, check that the peer isn't the fake peer */ + if (!strncasecmp(keys[SIP_DIGEST_RESPONSE].value, resp_hash, strlen(resp_hash)) && !bogus) { + authok = TRUE; + } + + if (wrongnonce) { + if (authok) { + if (sip_debug) { + ast_log(LOG_NOTICE, "Correct auth, but based on stale nonce received from '%s'\n", sip_request_get_header(req, "From")); + } + + /* We got working auth token, based on stale nonce . */ + sip_pvt_build_nonce(pvt, FALSE); + sip_send_response_with_auth(pvt, msg, req, pvt->nonce, reliable, respheader, TRUE); + } else { + /* Everything was wrong, so give the device one more try with a new challenge */ + if (!req->ignore) { + if (sip_debug) { + ast_log(LOG_NOTICE, "Bad authentication received from '%s'\n", sip_request_get_header(req, "To")); + } + + sip_pvt_build_nonce(pvt, TRUE); + } else { + if (sip_debug) { + ast_log(LOG_NOTICE, "Duplicate authentication received from '%s'\n", sip_request_get_header(req, "To")); + } + } + + sip_send_response_with_auth(pvt, msg, req, pvt->nonce, reliable, respheader, FALSE); + } + + /* Schedule auto destroy in 32 seconds */ + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + + return SIP_AUTH_CHALLENGE_SENT; + } + + if (authok) { + sip_history_append(pvt, "AuthOK", "Auth challenge successful for %s", username); + return SIP_AUTH_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_AUTH_SECRET_FAILED; +} + +/* Validate device authentication */ +static int __sip_pvt_check_peer_auth(struct sip_pvt *pvt, char *name, struct sip_request *req, char *uri, struct ast_sockaddr *addr, + struct sip_peer **authpeer, int reliable) +{ + int res; + int debug = sip_debug_test_addr(addr); + struct sip_peer *peer; + struct sip_peer *bogus_peer; + + /* First find devices based on username */ + peer = sip_peer_find(name, NULL, TRUE, FALSE, 0); + + /* Then find devices based on IP */ + if (!peer) { + peer = sip_peer_find(NULL, &pvt->recv, TRUE, FALSE, pvt->socket.type); + } + + if (!peer) { + if (debug) { + ast_verb(3, "No matching peer for '%s' from '%s'\n", name, ast_sockaddr_stringify(&pvt->recv)); + } + + /* 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.alwaysauthreject) { + return SIP_AUTH_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, "__sip_pvt_check_peer_auth: Get the bogus peer."))) { + return SIP_AUTH_DONT_KNOW; + } + + bogus_peer = peer; + } else { + bogus_peer = NULL; + } + + if (!ast_apply_acl(peer->acl, addr, "SIP Peer ACL: ")) { + ast_debug(2, "Found peer '%s' for '%s', but fails host access\n", peer->name, name); + ao2_t_cleanup(peer, "ao2_t_cleanup: __sip_pvt_check_peer_auth: from sip_peer_find call, early return of SIP_AUTH_ACL_FAILED"); + + return SIP_AUTH_ACL_FAILED; + } + + if (debug && peer != bogus_peer) { + ast_verb(3, "Found peer '%s' for '%s' from %s\n", peer->name, name, ast_sockaddr_stringify(&pvt->recv)); + } + + /* Set Frame packetization */ + if (pvt->rtp) { + ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(pvt->rtp), ast_format_cap_get_framing(peer->caps)); + pvt->autoframing = peer->autoframing; + } + + /* Take the peer */ + ast_copy_flags(&pvt->flags[0], &peer->flags[0], SIP_FLAGS0_TO_COPY); + ast_copy_flags(&pvt->flags[1], &peer->flags[1], SIP_FLAGS1_TO_COPY); + ast_copy_flags(&pvt->flags[2], &peer->flags[2], SIP_FLAGS2_TO_COPY); + + if (ast_test_flag(&pvt->flags[1], SIP_T38SUPPORT) && pvt->udptl) { + pvt->t38_maxdatagram = peer->t38_maxdatagram; + sip_pvt_set_t38_capabilities(pvt); + } + + /* Copy SIP extensions profile to peer */ + if (pvt->sipoptions) { + peer->sipoptions = pvt->sipoptions; + } + + sip_pvt_set_rtp_nat(pvt); + + ast_string_field_set(pvt, authname, peer->name); + ast_string_field_set(pvt, peersecret, peer->secret); + ast_string_field_set(pvt, peermd5secret, peer->md5secret); + ast_string_field_set(pvt, subscribecontext, peer->subscribecontext); + ast_string_field_set(pvt, mohinterpret, peer->mohinterpret); + ast_string_field_set(pvt, mohsuggest, peer->mohsuggest); + + if (!ast_strlen_zero(peer->parkinglot)) { + ast_string_field_set(pvt, parkinglot, peer->parkinglot); + } + + ast_string_field_set(pvt, engine, peer->engine); + + pvt->disallowed_methods = peer->disallowed_methods; + sip_pvt_set_allowed_methods(pvt, req); + + if (peer->callingpres) { /* Peer calling pres setting will override RPID */ + pvt->callingpres = peer->callingpres; + } + + if (peer->maxms && peer->lastms) { + pvt->timer_t1 = peer->lastms < sip_config.t1min ? sip_config.t1min : peer->lastms; + } else { + pvt->timer_t1 = peer->timer_t1; + } + + /* Set timer B to control transaction timeouts */ + if (peer->timer_b) { + pvt->timer_b = peer->timer_b; + } else { + pvt->timer_b = 64 * pvt->timer_t1; + } + + pvt->allowtransfer = peer->allowtransfer; + + /* Cisco peers only auth using the credentials of the primary peer */ + if (ast_test_flag(&pvt->flags[1], SIP_CISCO_USECALLMANAGER) && peer->cisco_lineindex > 1) { + ast_string_field_set(pvt, authname, peer->cisco_authname); + } + + if (!(res = sip_pvt_check_auth(pvt, req, pvt->authname, pvt->peersecret, pvt->peermd5secret, uri, reliable))) { + /* sip_peer_build, called through sip_peer_find, is not able to check the sip_pvt->natdetected 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_pvt_set_nat(pvt, peer); + + if (pvt->natdetected && ast_test_flag(&peer->flags[2], SIP_NAT_AUTO_RPORT)) { + ast_sockaddr_copy(&peer->addr, &pvt->recv); + } + + /* If we have a call limit, set flag */ + if (peer->call_limit) { + ast_set_flag(&pvt->flags[0], SIP_CALL_LIMIT); + } + + ast_string_field_set(pvt, peername, peer->name); + ast_string_field_set(pvt, authname, peer->name); + + if (req->method == SIP_INVITE) { + /* destroy old channel vars and copy in new ones. */ + ast_variables_destroy(pvt->chanvars); + pvt->chanvars = NULL; + + if (peer->chanvars) { + pvt->chanvars = ast_variables_dup(peer->chanvars); + } + } + + if (authpeer) { + ao2_t_ref(peer, 1, "copy pointer into (*authpeer)"); + (*authpeer) = 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(pvt, username, peer->username); + /* Use the default username for authentication on outbound calls */ + ast_string_field_set(pvt, authname, peer->username); + } + + if (!sip_parse_rpid(pvt, req)) { + if (!ast_strlen_zero(peer->cid_num)) { + char *cid_num = ast_strdupa(peer->cid_num); + + if (sip_config.shrinkcallerid && ast_is_shrinkable_phonenumber(cid_num)) { + ast_shrink_phone_number(cid_num); + } + + ast_string_field_set(pvt, cid_num, cid_num); + } + + if (!ast_strlen_zero(peer->cid_name)) { + ast_string_field_set(pvt, cid_name, peer->cid_name); + } + + if (peer->callingpres) { + pvt->callingpres = peer->callingpres; + } + } + + if (!ast_strlen_zero(peer->cid_tag)) { + ast_string_field_set(pvt, cid_tag, peer->cid_tag); + } + + ast_string_field_set(pvt, fullcontact, peer->fullcontact); + + if (!ast_strlen_zero(peer->context)) { + ast_string_field_set(pvt, context, peer->context); + } + + if (!ast_strlen_zero(peer->messagecontext)) { + ast_string_field_set(pvt, messagecontext, peer->messagecontext); + } + + if (!ast_strlen_zero(peer->mwi_from)) { + ast_string_field_set(pvt, mwi_from, peer->mwi_from); + } + + ast_string_field_set(pvt, peersecret, peer->secret); + ast_string_field_set(pvt, peermd5secret, peer->md5secret); + + ast_string_field_set(pvt, language, peer->language); + ast_string_field_set(pvt, accountcode, peer->accountcode); + pvt->amaflags = peer->amaflags; + + pvt->callgroup = peer->callgroup; + pvt->pickupgroup = peer->pickupgroup; + + ast_unref_namedgroups(pvt->named_callgroups); + pvt->named_callgroups = ast_ref_namedgroups(peer->named_callgroups); + ast_unref_namedgroups(pvt->named_pickupgroups); + pvt->named_pickupgroups = ast_ref_namedgroups(peer->named_pickupgroups); + + ast_format_cap_remove_by_type(pvt->caps, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(pvt->caps, peer->caps, AST_MEDIA_TYPE_UNKNOWN); + + ast_format_cap_remove_by_type(pvt->jointcaps, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(pvt->jointcaps, peer->caps, AST_MEDIA_TYPE_UNKNOWN); + + ast_copy_string(pvt->zone, peer->zone, sizeof(pvt->zone)); + + if (peer->maxforwards > 0) { + pvt->maxforwards = peer->maxforwards; + } + if (ast_format_cap_count(pvt->peercaps)) { + struct ast_format_cap *joint; + + if ((joint = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + ast_format_cap_get_compatible(pvt->jointcaps, pvt->peercaps, joint); + ao2_ref(pvt->jointcaps, -1); + pvt->jointcaps = joint; + } + } + + pvt->maxcallbitrate = peer->maxcallbitrate; + + if (ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833 || ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + pvt->noncodeccapability |= AST_RTP_DTMF; + } else { + pvt->noncodeccapability &= ~AST_RTP_DTMF; + } + + pvt->jointnoncodeccapability = pvt->noncodeccapability; + + pvt->rtptimeout = peer->rtptimeout; + pvt->rtpholdtimeout = peer->rtpholdtimeout; + pvt->rtpkeepalive = peer->rtpkeepalive; + + if (!sip_pvt_setup_rtp(pvt)) { + if (pvt->rtp) { + ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(pvt->rtp), ast_format_cap_get_framing(peer->caps)); + pvt->autoframing = peer->autoframing; + } + } else { + res = SIP_AUTH_RTP_FAILED; + } + } + + ao2_t_cleanup(peer, "__sip_pvt_check_peer_auth: ao2_t_cleanup: tossing temp ptr to peer from sip_peer_find"); + + 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_pvt_check_peer_auth(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr, struct sip_peer **authpeer, int reliable) +{ + char *from, *name, *domain, *port, *uri; + int res = SIP_AUTH_DONT_KNOW; + char cid_name[256]; + + uri = ast_strdupa(SIP_REQUEST_PART(req, rlpart2)); + sip_uri_terminate(uri); /* trim extra stuff */ + + if (ast_strlen_zero(pvt->exten)) { + if (!strncasecmp(uri, "sip:", 4)) { + uri += 4; + } else if (!strncasecmp(uri, "sips:", 5)) { + uri += 5; + } else if (!strncasecmp(uri, "tel:", 4)) { /* TEL URI INVITE */ + uri += 4; + } + + uri = strsep(&uri, "@"); + ast_string_field_set(pvt, exten, uri); + + if (ast_strlen_zero(pvt->our_contact)) { + sip_pvt_build_contact(pvt, req, TRUE); + } + } + + from = ast_strdupa(sip_request_get_header(req, "From")); + /* strip the display-name portion off the beginning of the FROM header. */ + sip_get_name(from, cid_name, sizeof(cid_name)); + + if (!ast_strlen_zero(cid_name)) { + ast_string_field_set(pvt, cid_name, cid_name); + } + + from = sip_get_in_brackets(from); + /* save the URI part of the From header */ + ast_string_field_set(pvt, from, from); + + if (sip_uri_parse(from, "sip:,sips:,tel:", &name, NULL, &domain, NULL)) { + ast_log(LOG_NOTICE, "From address missing 'sip:', using it anyway\n"); + } + + sip_pedantic_decode(name); + 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 res; + } + + if (ast_strlen_zero(name)) { + /* . Asterisk 1.4 and 1.6 have always treated that as a username, so we continue the tradition: + * uri is now . */ + name = domain; + } else { + /* Non-empty name, try to get caller id from it */ + char *cid_num = ast_strdupa(name); + + /* We need to be able to handle from-headers looking like */ + cid_num = strsep(&cid_num, ";"); + + if (sip_config.shrinkcallerid && ast_is_shrinkable_phonenumber(cid_num)) { + ast_shrink_phone_number(cid_num); + } + + ast_string_field_set(pvt, cid_num, cid_num); + } + + 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(req, "Authorization"); + + if (ast_strlen_zero(authorization)) { + authorization = sip_request_get_header(req, "Proxy-Authorization"); + } + + if (!ast_strlen_zero(authorization) && (authorization = strstr(authorization, "username=\""))) { + name = ast_strdupa(authorization + 10); + name = strsep(&name, "\""); + } + } + + res = __sip_pvt_check_peer_auth(pvt, name, req, uri, addr, authpeer, reliable); + + if (res != SIP_AUTH_DONT_KNOW) { + return res; + } + + res = SIP_AUTH_SECRET_FAILED; /* we don't want any guests, authentication will fail */ + + if (ast_test_flag(&pvt->flags[1], SIP_RPORT_PRESENT)) { + ast_set_flag(&pvt->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_pvt_check_register_auth(struct sip_pvt *pvt, struct sip_request *req, struct ast_sockaddr *addr) +{ + int res = SIP_AUTH_NOT_FOUND; + struct sip_peer *peer; + char *to, *name, *domain, *port, *uri; + int addr_changed, registered = FALSE; + + uri = ast_strdupa(SIP_REQUEST_PART(req, rlpart2)); + sip_uri_terminate(uri); + + to = ast_strdupa(sip_request_get_header(req, "To")); + to = sip_uri_remove_parameters(sip_get_in_brackets(to)); + + if (sip_uri_parse(to, "sip:,sips:", &name, NULL, &domain, NULL)) { + ast_log(LOG_NOTICE, "Invalid to address: '%s' from %s (missing sip:) trying to use anyway...\n", to, ast_sockaddr_stringify_addr(addr)); + return -1; + } + + sip_pedantic_decode(name); + sip_pedantic_decode(domain); + + ast_sockaddr_split_hostport(domain, &domain, &port, PARSE_PORT_IGNORE); + + if (ast_strlen_zero(domain)) { + /* , never good */ + sip_send_response(pvt, "404 Not Found", &pvt->initreq); + + return SIP_AUTH_UNKNOWN_DOMAIN; + } + + if (ast_strlen_zero(name)) { + /* , 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. */ + name = 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.alwaysauthreject) { + sip_send_response_with_fake_auth(pvt, &pvt->initreq); + } else { + sip_send_response(pvt, "404 Not Found (unknown domain)", &pvt->initreq); + } + + return SIP_AUTH_UNKNOWN_DOMAIN; + } + + /* Cisco USECALLMANAGER failover */ + if (strcasestr(sip_request_get_header(req, "Contact"), ";expires=0;cisco-keep-alive")) { + sip_send_response_with_date(pvt, "200 OK", req); + return 0; + } + + ast_string_field_set(pvt, exten, name); + sip_pvt_build_contact(pvt, req, TRUE); + + if (req->ignore) { + /* Expires is a special case, where we only want to load the peer if this isn't a deregistration attempt */ + const char *expires = sip_request_get_header(req, "Expires"); + int expire; + + if (ast_strlen_zero(expires)) { /* No expires header; look in Contact */ + if ((expires = strcasestr(sip_request_get_header(req, "Contact"), ";expires="))) { + expire = atoi(expires + 9); + } + } else { + expire = atoi(expires); + } + + if (!ast_strlen_zero(expires) && expire == 0) { + sip_send_response_with_date(pvt, "200 OK", req); + return 0; + } + } + + peer = sip_peer_find(name, NULL, TRUE, FALSE, 0); + + /* If we don't want username disclosure, use the bogus_peer when a user is not found. */ + if (!peer && sip_config.alwaysauthreject) { + peer = ao2_t_bump(sip_bogus_peer, "sip_pvt_check_register_auth: Get the bogus peer."); + } + + if (!(peer && ast_apply_acl(peer->acl, addr, "SIP Peer ACL: "))) { + /* Peer fails ACL check */ + if (peer) { + ao2_t_cleanup(peer, "sip_pvt_check_register_auth: ao2_t_cleanup: from sip_peer_find operation"); + peer = NULL; + + res = SIP_AUTH_ACL_FAILED; + } else { + res = SIP_AUTH_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_AUTH_PEER_NOT_DYNAMIC; + } else { + sip_pvt_set_nat(pvt, peer); + ast_copy_flags(&pvt->flags[0], &peer->flags[0], SIP_NAT_FORCE_RPORT); + + if (!(res = sip_pvt_check_auth(pvt, req, peer->name, peer->secret, peer->md5secret, uri, SIP_SEND_UNRELIABLE))) { + sip_pvt_cancel_destroy(pvt); + + if (sip_peer_check_transport(peer, req->socket.type)) { + ast_set_flag(&pvt->flags[0], SIP_PENDINGBYE); + sip_send_response_with_date(pvt, "403 Forbidden", req); + + res = SIP_AUTH_BAD_TRANSPORT; + } else { + /* We have a successful registration attempt with proper authentication, now, update the peer */ + switch (sip_parse_register_contact(pvt, peer, req, &addr_changed)) { + case SIP_PARSE_REGISTER_DENIED: + ast_log(LOG_WARNING, "Registration denied because of contact ACL\n"); + sip_send_response_with_date(pvt, "603 Denied", req); + + res = 0; + break; + case SIP_PARSE_REGISTER_FAILED: + ast_log(LOG_WARNING, "Failed to parse contact info\n"); + sip_send_response_with_date(pvt, "400 Bad Request", req); + + res = 0; + break; + case SIP_PARSE_REGISTER_QUERY: + ast_string_field_set(pvt, fullcontact, peer->fullcontact); + sip_send_response_with_date(pvt, "200 OK", req); + + res = 0; + break; + case SIP_PARSE_REGISTER_UPDATE: + ast_string_field_set(pvt, fullcontact, peer->fullcontact); + /* If expiry is 0, peer has been unregistered already */ + if (pvt->expiry != 0) { + sip_peer_update(peer, pvt->expiry); + } + + registered = TRUE; + ast_set2_flag(&pvt->flags[1], ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER), SIP_CISCO_USECALLMANAGER); + + /* Say OK and ask subsystem to retransmit msg counter */ + if (pvt->expiry != 0 && ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER) && addr_changed) { + sip_send_response_with_optionsind(pvt, req); + } else { + sip_send_response_with_date(pvt, "200 OK", req); + } + + res = 0; + break; + } + } + } + } + + ao2_unlock(peer); + } + + if (!res) { + if (pvt->expiry != 0 && registered) { + if (ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER)) { + /* We only need to do an update if the peer addr has changed */ + if (addr_changed) { + sip_peer_register_aliases(peer); + sip_peer_send_bulkupdate(peer); + sip_peer_extension_state_subscriptions(peer); + } + } else { + ao2_unlock(pvt); + sip_peer_send_mwi(peer, FALSE); + ao2_lock(pvt); + } + } else { + sip_peer_set_lastmsgs(peer, -1, 0, FALSE); + } + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + } + + if (res < 0) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + + switch (res) { + case SIP_AUTH_SECRET_FAILED: + /* Wrong password in authentication. Go away, don't try again until you fixed it */ + sip_send_response(pvt, "403 Forbidden", &pvt->initreq); + + if (sip_config.authfailureevents) { + const char *peer_addr = ast_strdupa(ast_sockaddr_stringify_addr(addr)); + const char *peer_port = ast_strdupa(ast_sockaddr_stringify_port(addr)); + + blob = ast_json_pack("{s: s, s: s, s: s, s: s}", + "peer_status", "Rejected", "cause", "SIP_AUTH_SECRET_FAILED", + "address", peer_addr, "port", peer_port); + } + + break; + case SIP_AUTH_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_AUTH_NOT_FOUND: + case SIP_AUTH_PEER_NOT_DYNAMIC: + case SIP_AUTH_ACL_FAILED: + if (sip_config.alwaysauthreject) { + sip_send_response_with_fake_auth(pvt, &pvt->initreq); + + if (sip_config.authfailureevents) { + const char *peer_addr = ast_strdupa(ast_sockaddr_stringify_addr(addr)); + const char *peer_port = ast_strdupa(ast_sockaddr_stringify_port(addr)); + + blob = ast_json_pack("{s: s, s: s, s: s, s: s}", + "peer_status", "Rejected", + "cause", res == SIP_AUTH_PEER_NOT_DYNAMIC ? "SIP_AUTH_PEER_NOT_DYNAMIC" : "URI_NOT_FOUND", + "address", peer_addr, "port", peer_port); + } + } else { + /* URI not found */ + if (res == SIP_AUTH_PEER_NOT_DYNAMIC) { + sip_send_response(pvt, "403 Forbidden", &pvt->initreq); + + if (sip_config.authfailureevents) { + const char *peer_addr = ast_strdupa(ast_sockaddr_stringify_addr(addr)); + const char *peer_port = ast_strdupa(ast_sockaddr_stringify_port(addr)); + + blob = ast_json_pack("{s: s, s: s, s: s, s: s}", + "peer_status", "Rejected", "cause", "SIP_AUTH_PEER_NOT_DYNAMIC", + "address", peer_addr, "port", peer_port); + } + } else { + sip_send_response(pvt, "404 Not Found", &pvt->initreq); + + if (sip_config.authfailureevents) { + const char *peer_addr = ast_strdupa(ast_sockaddr_stringify_addr(addr)); + const char *peer_port = ast_strdupa(ast_sockaddr_stringify_port(addr)); + + blob = ast_json_pack("{s: s, s: s, s: s, s: s}", + "peer_status", "Rejected", + "cause", res == SIP_AUTH_USERNAME_MISMATCH ? "SIP_AUTH_USERNAME_MISMATCH" : "URI_NOT_FOUND", + "address", peer_addr, "port", peer_port); + } + } + } + break; + case SIP_AUTH_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, "sip_pvt_check_register_auth: ao2_t_cleanup: tossing stack peer pointer at end of func"); + } + + return res; +} + +/* Handle auth requests to a MESSAGE request */ +int sip_pvt_check_message_auth(struct sip_pvt *pvt, int resp, struct sip_request *req) +{ + char *reqheader; + char digest[1024]; + + if (pvt->options) { + pvt->options->auth_type = (resp == 401 ? SIP_WWW_AUTH : SIP_PROXY_AUTH); + } + + if (pvt->authtries == SIP_MAX_AUTHTRIES) { + ast_log(LOG_NOTICE, "Failed to authenticate MESSAGE with host '%s'\n", ast_sockaddr_stringify(&pvt->sa)); + return -1; + } + + pvt->authtries++; + sip_auth_headers(resp == 401 ? SIP_WWW_AUTH : SIP_PROXY_AUTH, &reqheader, NULL); + + memset(digest, 0, sizeof(digest)); + + if (sip_pvt_parse_reply_digest(pvt, req, reqheader, SIP_MESSAGE, digest, sizeof(digest))) { + /* There's nothing to use for authentication */ + ast_debug(1, "Nothing to use for MESSAGE authentication\n"); + return -1; + } + + if (pvt->recordhistory) { + sip_history_append(pvt, "MessageAuth", "Try: %d", pvt->authtries); + } + + sip_send_message(pvt, FALSE, TRUE); + + return 0; +} + +/* Authenticate for outbound registration */ +int sip_pvt_handle_register_auth(struct sip_pvt *pvt, struct sip_request *req, int resp) +{ + char *reqheader, *respheader; + char digest[1024]; + + pvt->authtries++; + sip_auth_headers(resp, &reqheader, &respheader); + + memset(digest, 0, sizeof(digest)); + + if (sip_pvt_parse_reply_digest(pvt, req, reqheader, SIP_REGISTER, digest, sizeof(digest))) { + /* There's nothing to use for authentication. No digest challenge in request */ + if (sip_debug_test_pvt(pvt) && pvt->registry) { + ast_verb(3, "No authentication challenge, sending blank registration to domain/host name %s\n", pvt->registry->hostname); + /* No old challenge */ + } + + return -1; + } + + if (pvt->recordhistory) { + sip_history_append(pvt, "RegistryAuth", "Try: %d", pvt->authtries); + } + + if (sip_debug_test_pvt(pvt) && pvt->registry) { + ast_verb(3, "Responding to challenge, registration to domain/host name %s\n", pvt->registry->hostname); + } + + return sip_send_register(pvt->registry, SIP_REGISTER, digest, respheader); +} + +/* Add authentication on outbound SIP packet */ +int sip_pvt_handle_proxy_auth(struct sip_pvt *pvt, struct sip_request *req, int resp, int method, int init) +{ + char *reqheader, *respheader; + char digest[1024]; + + if (!pvt->options && !(pvt->options = ast_calloc(1, sizeof(*pvt->options)))) { + return -2; + } + + pvt->authtries++; + sip_auth_headers(resp, &reqheader, &respheader); + + ast_debug(2, "Auth attempt %d on %s\n", pvt->authtries, sip_methods[method].name); + memset(digest, 0, sizeof(digest)); + + if (sip_pvt_parse_reply_digest(pvt, req, reqheader, method, digest, sizeof(digest))) { + /* No way to authenticate */ + return -1; + } + + /* Now we have a reply digest */ + pvt->options->auth = digest; + pvt->options->authheader = respheader; + + return sip_send_invite(pvt, method, method == SIP_INVITE, init, NULL); +} + +/* Try setting the codecs suggested by the SIP_CODEC channel variable */ +void sip_pvt_try_suggested_codec(struct sip_pvt *pvt) +{ + const char *codecs; + char *codecs_copy, *codec; + struct ast_format_cap *orig_jointcaps; + int first_codec = TRUE; + + if (pvt->outgoing_call) { + codecs = pbx_builtin_getvar_helper(pvt->owner, "SIP_CODEC_OUTBOUND"); + } else { + codecs = pbx_builtin_getvar_helper(pvt->owner, "SIP_CODEC_INBOUND"); + + if (ast_strlen_zero(codecs)) { + codecs = pbx_builtin_getvar_helper(pvt->owner, "SIP_CODEC"); + } + } + + if (ast_strlen_zero(codecs)) { + return; + } + + codecs_copy = ast_strdupa(codecs); + + if (!(orig_jointcaps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + return; + } + + ast_format_cap_append_from_cap(orig_jointcaps, pvt->jointcaps, AST_MEDIA_TYPE_UNKNOWN); + + while ((codec = strsep(&codecs_copy, ","))) { + struct ast_format *format; + + codec = ast_strip(codec); + + if (!(format = ast_format_cache_get(codec))) { + ast_log(AST_LOG_NOTICE, "Ignoring ${SIP_CODEC*} variable because of unrecognized/not configured codec %s (check allow/disallow in sip.conf)\n", codec); + continue; + } + + if (ast_format_cap_iscompatible_format(orig_jointcaps, format) != AST_FORMAT_CMP_NOT_EQUAL) { + if (first_codec) { + ast_verb(4, "Set codec to '%s' for this call because of ${SIP_CODEC*} variable\n", codec); + + ast_format_cap_remove_by_type(pvt->jointcaps, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append(pvt->jointcaps, format, 0); + + ast_format_cap_remove_by_type(pvt->caps, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append(pvt->caps, format, 0); + + first_codec = FALSE; + } else { + ast_verb(4, "Add codec to '%s' for this call because of ${SIP_CODEC*} variable\n", codec); + + /* Add the format to the capabilities structure */ + ast_format_cap_append(pvt->jointcaps, format, 0); + ast_format_cap_append(pvt->caps, format, 0); + } + } else { + ast_log(AST_LOG_NOTICE, "Ignoring ${SIP_CODEC*} variable because it is not shared by both ends: %s\n", codec); + } + + ao2_ref(format, -1); + } + + /* 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(pvt->jointcaps, orig_jointcaps, AST_MEDIA_TYPE_UNKNOWN); + ao2_ref(orig_jointcaps, -1); + + return; +} + +/* This function creates a dialog to handle a forked request. This dialog + * exists only to properly terminiate the forked request immediately. */ +void sip_pvt_init_forked_invite(struct sip_request *req, const char *new_theirtag, struct sip_pvt *orig_pvt, struct ast_sockaddr *addr) +{ + struct sip_pvt *pvt; + const char *callid; + ast_callid logger_callid; + + ao2_lock(orig_pvt); + + callid = ast_strdupa(orig_pvt->callid); + logger_callid = orig_pvt->logger_callid; + + ao2_unlock(orig_pvt); + + if (!(pvt = sip_pvt_alloc(callid, addr, TRUE, SIP_INVITE, req, logger_callid))) { + return; /* alloc error */ + } + + /* Lock pvt and orig_pvt private structures. */ + ao2_lock(pvt); + + while (ao2_trylock(orig_pvt)) { + /* Can't use DEADLOCK_AVOIDANCE since pvt is an ao2 object */ + ao2_unlock(pvt); + sched_yield(); + ao2_lock(pvt); + } + + pvt->invitestate = SIP_INVITE_TERMINATED; + pvt->ocseq = orig_pvt->ocseq; + pvt->branch = orig_pvt->branch; + + memcpy(&pvt->flags, &orig_pvt->flags, sizeof(pvt->flags)); + sip_request_copy(&pvt->initreq, &orig_pvt->initreq); + + ast_string_field_set(pvt, theirtag, new_theirtag); + ast_string_field_set(pvt, tag, orig_pvt->tag); + ast_string_field_set(pvt, uri, orig_pvt->uri); + ast_string_field_set(pvt, our_contact, orig_pvt->our_contact); + ast_string_field_set(pvt, fullcontact, orig_pvt->fullcontact); + + ao2_unlock(orig_pvt); + + sip_parse_ok_contact(pvt, req); + sip_pvt_build_route(pvt, req, TRUE, 0); + + sip_send_request(pvt, SIP_ACK, pvt->ocseq, SIP_SEND_UNRELIABLE, TRUE); + sip_send_request(pvt, SIP_BYE, 0, SIP_SEND_RELIABLE, TRUE); + + sip_pvt_set_need_destroy(pvt, "forked request"); /* this dialog will terminate once the BYE is responed to or times out. */ + ao2_unlock(pvt); + + ao2_t_cleanup(pvt, "setup forked invite termination"); +} + +/* Reset the NEEDREINVITE flag after waiting when we get 491 on a Re-invite to avoid race conditions between asterisk servers. */ +int sip_pvt_start_reinvite_retry(const void *data) +{ + struct sip_pvt *pvt = (struct sip_pvt *) data; + struct ast_channel *owner; + + owner = sip_pvt_lock_full(pvt); + + ast_set_flag(&pvt->flags[0], SIP_NEEDREINVITE); + pvt->waitid = -1; + + sip_pvt_check_pendings(pvt); + ao2_unlock(pvt); + + if (owner) { + ast_channel_unlock(owner); + ast_channel_unref(owner); + } + + ao2_t_cleanup(pvt, "Schedule waitid complete"); + + return 0; +} + +/* Run by the sched thread. */ +static int __sip_pvt_stop_reinvite_retry(const void *data) +{ + struct sip_pvt *pvt = (void *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, pvt->waitid, ao2_t_cleanup(pvt, "Stop scheduled waitid")); + ao2_t_cleanup(pvt, "Stop reinvite retry action"); + + return 0; +} + +void sip_pvt_stop_reinvite_retry(struct sip_pvt *pvt) +{ + ao2_t_bump(pvt, "Stop reinvite retry action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_pvt_stop_reinvite_retry, pvt) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(pvt, "Failed to schedule stop reinvite retry action"); + } +} + +/* Run by the sched thread. */ +int sip_pvt_reinvite_timeout(const void *data) +{ + struct sip_pvt *pvt = (struct sip_pvt *) data; + struct ast_channel *owner; + + owner = sip_pvt_lock_full(pvt); + pvt->reinviteid = -1; + + sip_pvt_check_pendings(pvt); + + if (owner) { + ast_channel_unlock(owner); + ast_channel_unref(owner); + } + + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "reinviteid complete"); + + return 0; +} + +/* Run by the sched thread. */ +static int __sip_pvt_stop_reinvite(const void *data) +{ + struct sip_pvt *pvt = (void *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, pvt->reinviteid, ao2_t_cleanup(pvt, "Stop scheduled reinviteid")); + ao2_t_cleanup(pvt, "Stop reinviteid action"); + + return 0; +} + +void sip_pvt_stop_reinvite(struct sip_pvt *pvt) +{ + ao2_t_bump(pvt, "Stop reinviteid action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_pvt_stop_reinvite, pvt) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(pvt, "Failed to schedule stop reinviteid action"); + } +} + +/* Run by the sched thread. */ +static int sip_pvt_send_provisonal_keepalive_full(struct sip_pvt *pvt, int with_sdp) +{ + const char *msg = NULL; + struct ast_channel *chan; + int res = 0; + + chan = sip_pvt_lock_full(pvt); + + if (!pvt->last_provisional || !strncasecmp(pvt->last_provisional, "100", 3)) { + msg = "183 Session Progress"; + } + + if (pvt->invitestate < SIP_INVITE_COMPLETED) { + if (with_sdp) { + sip_send_response_with_sdp(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq, SIP_SEND_UNRELIABLE, FALSE, FALSE); + } else { + sip_send_response(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq); + } + + res = SIP_PROVISIONAL_KEEPALIVE_TIMEOUT; + } else { + pvt->provisional_keepalive_sched_id = -1; + } + + ao2_unlock(pvt); + + if (chan) { + ast_channel_unlock(chan); + ast_channel_unref(chan); + } + + if (!res) { + ao2_t_cleanup(pvt, "Schedule provisional keepalive complete"); + } + + return res; +} + +/* Run by the sched thread. */ +static int sip_pvt_send_provisonal_keepalive(const void *data) +{ + struct sip_pvt *pvt = (struct sip_pvt *) data; + + return sip_pvt_send_provisonal_keepalive_full(pvt, FALSE); +} + +/* Run by the sched thread. */ +static int sip_pvt_send_provisonal_keepalive_with_sdp(const void *data) +{ + struct sip_pvt *pvt = (void *) data; + + return sip_pvt_send_provisonal_keepalive_full(pvt, TRUE); +} + +/* Run by the sched thread. */ +static int __sip_pvt_update_provisional_keepalive_full(struct sip_pvt *pvt, int with_sdp) +{ + AST_SCHED_DEL_UNREF(sip_sched_context, pvt->provisional_keepalive_sched_id, + ao2_t_cleanup(pvt, "Stop scheduled provisional keepalive for update")); + + ao2_lock(pvt); + + if (pvt->invitestate < SIP_INVITE_COMPLETED) { + /* Provisional keepalive is still needed. */ + ao2_t_bump(pvt, "Schedule provisional keepalive"); + + pvt->provisional_keepalive_sched_id = ast_sched_add(sip_sched_context, SIP_PROVISIONAL_KEEPALIVE_TIMEOUT, + with_sdp ? sip_pvt_send_provisonal_keepalive_with_sdp : sip_pvt_send_provisonal_keepalive, pvt); + + if (pvt->provisional_keepalive_sched_id < 0) { + ao2_t_cleanup(pvt, "Failed to schedule provisional keepalive"); + } + } + + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "Update provisional keepalive action"); + + return 0; +} + +/* Run by the sched thread. */ +static int __sip_pvt_update_provisional_keepalive(const void *data) +{ + struct sip_pvt *pvt = (void *) data; + + return __sip_pvt_update_provisional_keepalive_full(pvt, FALSE); +} + +/* Run by the sched thread. */ +static int __sip_pvt_update_provisional_keepalive_with_sdp(const void *data) +{ + struct sip_pvt *pvt = (void *) data; + + return __sip_pvt_update_provisional_keepalive_full(pvt, TRUE); +} + +void sip_pvt_update_provisional_keepalive(struct sip_pvt *pvt, int with_sdp) +{ + ao2_t_bump(pvt, "Update provisional keepalive action"); + + if (ast_sched_add(sip_sched_context, 0, + with_sdp ? __sip_pvt_update_provisional_keepalive_with_sdp : __sip_pvt_update_provisional_keepalive, pvt) < 0) { + ao2_t_cleanup(pvt, "Failed to schedule update provisional keepalive action"); + } +} + +/* Run by the sched thread. */ +static int __sip_pvt_cancel_provisional_keepalive(const void *data) +{ + struct sip_pvt *pvt = (void *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, pvt->provisional_keepalive_sched_id, + ao2_t_cleanup(pvt, "Stop scheduled provisional keepalive")); + ao2_t_cleanup(pvt, "Stop provisional keepalive action"); + + return 0; +} + +void sip_pvt_cancel_provisional_keepalive(struct sip_pvt *pvt) +{ + ao2_t_bump(pvt, "Stop provisional keepalive action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_pvt_cancel_provisional_keepalive, pvt) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(pvt, "Failed to schedule stop provisional keepalive action"); + } +} + +void sip_pvt_extension_state_destroy(int id, void *data) +{ + struct sip_pvt *pvt = data; + + ao2_t_cleanup(pvt, "the extension_state containing this dialog ptr was destroyed"); +} + +/* 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_pvt_extension_state_update(struct sip_pvt *pvt, struct sip_extension_state_data *state_data, int force) +{ + ao2_lock(pvt); + + switch (state_data->exten_state) { + case AST_EXTENSION_DEACTIVATED: /* Retry after a while */ + case AST_EXTENSION_REMOVED: /* Extension is gone */ + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); /* Delete subscription in 32 secs */ + + ast_verb(2, "Extension state: Watcher for hint %s %s. Notify User %s\n", + pvt->exten, state_data->exten_state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", pvt->username); + + pvt->subscribed = SIP_SUBSCRIBED_NONE; + sip_history_append(pvt, "Subscribestatus", "%s", state_data->exten_state == AST_EXTENSION_REMOVED ? "HintRemoved" : "Deactivated"); + + break; + default: /* Tell user */ + if (force) { + /* we must skip the next two checks for a queued state change or resubscribe */ + } else if ((pvt->last_exten_state == state_data->exten_state && (~state_data->exten_state & AST_EXTENSION_RINGING)) && + (pvt->last_presence_state == state_data->presence_state && + !strcmp(pvt->last_presence_subtype, S_OR(state_data->presence_subtype, "")) && + !strcmp(pvt->last_presence_message, S_OR(state_data->presence_message, "")))) { + /* don't notify unchanged state or unchanged early-state causing parties again */ + ao2_unlock(pvt); + return 0; + } else if (state_data->exten_state & AST_EXTENSION_RINGING) { + /* check if another channel than last time is ringing now to be notified */ + struct ast_channel *chan = sip_find_ringing_channel(state_data->device_state_info); + + if (chan) { + if (!ast_tvcmp(ast_channel_creationtime(chan), pvt->last_ringing_channel_time)) { + /* we assume here that no two channels have the exact same creation time */ + ao2_ref(chan, -1); + ao2_unlock(pvt); + + return 0; + } else { + pvt->last_ringing_channel_time = ast_channel_creationtime(chan); + ao2_ref(chan, -1); + } + } + + /* 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_data->device_state_info) { + ao2_ref(state_data->device_state_info, 1); + } + + ao2_cleanup(pvt->last_device_state_info); + + pvt->last_device_state_info = state_data->device_state_info; + pvt->last_exten_state = state_data->exten_state; + pvt->last_presence_state = state_data->presence_state; + + ast_string_field_set(pvt, last_presence_subtype, S_OR(state_data->presence_subtype, "")); + ast_string_field_set(pvt, last_presence_message, S_OR(state_data->presence_message, "")); + + break; + } + + if (pvt->subscribed != SIP_SUBSCRIBED_NONE) { /* Only send state NOTIFY if we know the format */ + if (!pvt->pendinginvite) { + sip_send_notify_with_extension_state(pvt, state_data, FALSE); + + if (pvt->last_device_state_info) { + /* We don't need the saved ref anymore, don't keep channels ref'd. */ + ao2_ref(pvt->last_device_state_info, -1); + pvt->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(&pvt->flags[1], SIP_STATECHANGEQUEUE); + } + } + + if (!force) { + ast_debug(1, "Extension Changed %s@%s new state %s for peer %s %s\n", + pvt->exten, pvt->context, ast_extension_state2str(state_data->exten_state), pvt->username, + ast_test_flag(&pvt->flags[1], SIP_STATECHANGEQUEUE) ? "(queued)" : ""); + } + + ao2_unlock(pvt); + + return 0; +} + +int sip_pvt_extension_state_event(const char *context, const char *exten, struct ast_state_cb_info *info, void *data) +{ + struct sip_pvt *pvt = data; + struct sip_extension_state_data state_data = { + .device_state_info = info->device_state_info, + .exten_state = info->exten_state, + .presence_state = info->presence_state, + .presence_subtype = info->presence_subtype, + .presence_message = info->presence_message, + }; + + return sip_pvt_extension_state_update(pvt, &state_data, FALSE); +} + +/* Create and initialize UDPTL for the specified dialog */ +int sip_pvt_setup_udptl(struct sip_pvt *pvt) +{ + if (!ast_test_flag(&pvt->flags[1], SIP_T38SUPPORT)) { + return -1; + } + + /* If we've already initialized T38, don't take any further action */ + if (pvt->udptl) { + return 0; + } + + /* T38 can be supported by this dialog, create it and set the derived properties */ + if ((pvt->udptl = ast_udptl_new_with_bindaddr(sip_sched_context, sip_io_context, 0, &sip_config.bindaddr))) { + if (pvt->owner) { + ast_channel_set_fd(pvt->owner, SIP_UDPTL_FD, ast_udptl_fd(pvt->udptl)); + } + + ast_udptl_setqos(pvt->udptl, sip_config.tos_audio, sip_config.cos_audio); + pvt->t38_maxdatagram = pvt->relatedpeer ? pvt->relatedpeer->t38_maxdatagram : sip_config.t38_maxdatagram; + sip_pvt_set_t38_capabilities(pvt); + + ast_debug(1, "Setting NAT on UDPTL to %s\n", ast_test_flag(&pvt->flags[1], SIP_SYMMETRICRTP) ? "On" : "Off"); + + ast_udptl_setnat(pvt->udptl, !!ast_test_flag(&pvt->flags[1], SIP_SYMMETRICRTP)); + } else { + ast_log(AST_LOG_WARNING, "UDPTL creation failed - disabling T38 for this dialog\n"); + ast_clear_flag(&pvt->flags[1], SIP_T38SUPPORT); + + return -1; + } + + return 0; +} + +/* Called to deny a T38 reinvite if the core does not respond to our request Run by the sched thread. */ +static int sip_pvt_t38_abort(const void *data) +{ + struct sip_pvt *pvt = (struct sip_pvt *) data; + struct ast_channel *owner; + + owner = sip_pvt_lock_full(pvt); + pvt->t38id = -1; + + /* 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 (pvt->t38.state == SIP_T38_PEER_REINVITE) { + /* Still waiting for a response on timeout so reject the offer. */ + sip_pvt_change_t38_state(pvt, SIP_T38_REJECTED); + sip_send_response_reliable(pvt, "488 Not Acceptable Here", &pvt->initreq); + } + + if (owner) { + ast_channel_unlock(owner); + ast_channel_unref(owner); + } + + ao2_unlock(pvt); + ao2_t_cleanup(pvt, "t38id complete"); + + return 0; +} + +/* Run by the sched thread. */ +static int __sip_pvt_stop_t38_abort_timer(const void *data) +{ + struct sip_pvt *pvt = (void *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, pvt->t38id, ao2_t_cleanup(pvt, "Stop scheduled t38id")); + ao2_t_cleanup(pvt, "Stop t38id action"); + + return 0; +} + +static void sip_pvt_stop_t38_abort_timer(struct sip_pvt *pvt) +{ + ao2_t_bump(pvt, "Stop t38id action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_pvt_stop_t38_abort_timer, pvt) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(pvt, "Failed to schedule stop t38id action"); + } +} + +/* Run by the sched thread. */ +static int __sip_pvt_start_t38_abort_timer(const void *data) +{ + struct sip_pvt *pvt = (void *) data; + + AST_SCHED_DEL_UNREF(sip_sched_context, pvt->t38id, ao2_t_cleanup(pvt, "Stop scheduled t38id")); + + ao2_t_bump(pvt, "Schedule t38id"); + pvt->t38id = ast_sched_add(sip_sched_context, 5000, sip_pvt_t38_abort, pvt); + + if (pvt->t38id < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(pvt, "Failed to schedule t38id"); + } + + ao2_t_cleanup(pvt, "Start t38id action"); + + return 0; +} + +void sip_pvt_start_t38_abort_timer(struct sip_pvt *pvt) +{ + ao2_t_bump(pvt, "Start t38id action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_pvt_start_t38_abort_timer, pvt) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(pvt, "Failed to schedule start t38id action"); + } +} + +/* Set the global T38 capabilities on a SIP dialog structure */ +static void sip_pvt_set_t38_capabilities(struct sip_pvt *pvt) +{ + if (pvt->udptl) { + if (ast_test_flag(&pvt->flags[1], SIP_T38SUPPORT) == SIP_T38SUPPORT_UDPTL_REDUNDANCY) { + ast_udptl_set_error_correction_scheme(pvt->udptl, UDPTL_ERROR_CORRECTION_REDUNDANCY); + } else if (ast_test_flag(&pvt->flags[1], SIP_T38SUPPORT) == SIP_T38SUPPORT_UDPTL_FEC) { + ast_udptl_set_error_correction_scheme(pvt->udptl, UDPTL_ERROR_CORRECTION_FEC); + } else if (ast_test_flag(&pvt->flags[1], SIP_T38SUPPORT) == SIP_T38SUPPORT_UDPTL) { + ast_udptl_set_error_correction_scheme(pvt->udptl, UDPTL_ERROR_CORRECTION_NONE); + } + } +} + +/* Helper function which updates T.38 capability information and triggers a reinvite */ +int sip_pvt_update_t38_capabilities(struct sip_pvt *pvt, const struct ast_control_t38_parameters *parms) +{ + int res = 0; + struct ast_control_t38_parameters their_parms; + + if (!ast_test_flag(&pvt->flags[1], SIP_T38SUPPORT) || !pvt->udptl) { + return -1; + } + + switch (parms->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 (!parms->max_ifp) { + if (pvt->t38.state == SIP_T38_PEER_REINVITE) { + sip_pvt_stop_t38_abort_timer(pvt); + sip_send_response_reliable(pvt, "488 Not Acceptable Here", &pvt->initreq); + } + + sip_pvt_change_t38_state(pvt, SIP_T38_REJECTED); + break; + } else if (pvt->t38.state == SIP_T38_PEER_REINVITE) { + sip_pvt_stop_t38_abort_timer(pvt); + pvt->t38.our_parms = *parms; + + /* modify our parameters to conform to the peer's parameters, based on the rules in the ITU T.38 + * recommendation. */ + if (!pvt->t38.their_parms.fill_bit_removal) { + pvt->t38.our_parms.fill_bit_removal = FALSE; + } + + if (!pvt->t38.their_parms.transcoding_mmr) { + pvt->t38.our_parms.transcoding_mmr = FALSE; + } + + if (!pvt->t38.their_parms.transcoding_jbig) { + pvt->t38.our_parms.transcoding_jbig = FALSE; + } + + pvt->t38.our_parms.version = MIN(pvt->t38.our_parms.version, pvt->t38.their_parms.version); + pvt->t38.our_parms.rate_management = pvt->t38.their_parms.rate_management; + + ast_udptl_set_local_max_ifp(pvt->udptl, pvt->t38.our_parms.max_ifp); + sip_pvt_change_t38_state(pvt, SIP_T38_ENABLED); + + sip_send_response_with_t38_sdp(pvt, "200 OK", &pvt->initreq, SIP_SEND_CRITICAL); + } else if (pvt->t38.state != SIP_T38_ENABLED || + (pvt->t38.state == SIP_T38_ENABLED && parms->request_response == AST_T38_REQUEST_NEGOTIATE)) { + pvt->t38.our_parms = *parms; + ast_udptl_set_local_max_ifp(pvt->udptl, pvt->t38.our_parms.max_ifp); + + sip_pvt_change_t38_state(pvt, SIP_T38_LOCAL_REINVITE); + + if (!pvt->pendinginvite) { + sip_send_reinvite_with_sdp(pvt, TRUE, FALSE); + } else if (!ast_test_flag(&pvt->flags[0], SIP_PENDINGBYE)) { + ast_set_flag(&pvt->flags[0], SIP_NEEDREINVITE); + } + } + + break; + case AST_T38_TERMINATED: + case AST_T38_REFUSED: + case AST_T38_REQUEST_TERMINATE: /* Shutdown T38 */ + if (pvt->t38.state == SIP_T38_PEER_REINVITE) { + sip_pvt_stop_t38_abort_timer(pvt); + sip_pvt_change_t38_state(pvt, SIP_T38_REJECTED); + + sip_send_response_reliable(pvt, "488 Not Acceptable Here", &pvt->initreq); + } else if (pvt->t38.state == SIP_T38_ENABLED) { + sip_pvt_change_t38_state(pvt, SIP_T38_DISABLED); + + sip_send_reinvite_with_sdp(pvt, FALSE, FALSE); + } + break; + case AST_T38_REQUEST_PARMS: /* Application wants remote's parameters re-sent */ + their_parms = pvt->t38.their_parms; + + if (pvt->t38.state == SIP_T38_PEER_REINVITE) { + sip_pvt_stop_t38_abort_timer(pvt); + + their_parms.max_ifp = ast_udptl_get_far_max_ifp(pvt->udptl); + their_parms.request_response = AST_T38_REQUEST_NEGOTIATE; + + if (pvt->owner) { + ast_queue_control_data(pvt->owner, AST_CONTROL_T38_PARAMETERS, &their_parms, sizeof(their_parms)); + } + + /* 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_pvt_change_t38_state(struct sip_pvt *pvt, int state) +{ + int oldstate = pvt->t38.state; + struct ast_channel *chan = pvt->owner; + struct ast_control_t38_parameters parms = {.request_response = 0}; + + /* Don't bother changing if we are already in the state wanted */ + if (oldstate == state) { + return; + } + + pvt->t38.state = state; + ast_debug(2, "T38 state changed to %u on channel %s\n", pvt->t38.state, chan ? ast_channel_name(chan) : ""); + + /* If no channel was provided we can't send off a control frame */ + if (!chan) { + return; + } + + /* Given the state requested and old state determine what control frame we want to queue up */ + switch (state) { + case SIP_T38_PEER_REINVITE: + parms = pvt->t38.their_parms; + parms.max_ifp = ast_udptl_get_far_max_ifp(pvt->udptl); + parms.request_response = AST_T38_REQUEST_NEGOTIATE; + + ast_udptl_set_tag(pvt->udptl, "%s", ast_channel_name(chan)); + break; + case SIP_T38_ENABLED: + parms = pvt->t38.their_parms; + parms.max_ifp = ast_udptl_get_far_max_ifp(pvt->udptl); + parms.request_response = AST_T38_NEGOTIATED; + + ast_udptl_set_tag(pvt->udptl, "%s", ast_channel_name(chan)); + break; + case SIP_T38_REJECTED: + case SIP_T38_DISABLED: + if (oldstate == SIP_T38_ENABLED) { + parms.request_response = AST_T38_TERMINATED; + } else if (oldstate == SIP_T38_LOCAL_REINVITE) { + parms.request_response = AST_T38_REFUSED; + + } + break; + case SIP_T38_LOCAL_REINVITE: + /* wait until we get a peer response before responding to local reinvite */ + break; + } + + /* Woot we got a message, create a control frame and send it on! */ + if (parms.request_response) { + ast_queue_control_data(chan, AST_CONTROL_T38_PARAMETERS, &parms, sizeof(parms)); + } +} + +void sip_pvt_set_ice_components(struct sip_pvt *pvt, struct ast_rtp_instance *instance, int remote_rtcp_mux) +{ + struct ast_rtp_engine_ice *ice; + int local_rtcp_mux = ast_test_flag(&pvt->flags[2], SIP_RTCP_MUX); + + if (!(ice = ast_rtp_instance_get_ice(instance))) { + return; + } + + if (local_rtcp_mux && remote_rtcp_mux) { + /* We both support RTCP mux. Only one ICE component necessary */ + ice->change_components(instance, 1); + } else { + /* They either don't support RTCP mux or we don't know if they do yet. */ + ice->change_components(instance, 2); + } +} + +/* Start ICE negotiation on an RTP instance */ +void sip_pvt_start_ice(struct ast_rtp_instance *instance, int offer) +{ + struct ast_rtp_engine_ice *ice = ast_rtp_instance_get_ice(instance); + + if (!ice) { + return; + } + + /* If we are the offerer then we are the controlling agent, otherwise they are */ + ice->set_role(instance, offer ? AST_RTP_ICE_ROLE_CONTROLLING : AST_RTP_ICE_ROLE_CONTROLLED); + ice->start(instance); +} + +/* Append to SIP dialog history with arg list */ +void sip_history_append(struct sip_pvt *pvt, const char *type, const char *fmt, ...) +{ + va_list ap; + char event[80], *sep; + struct sip_history *history; + int len; + + if (!pvt) { + return; + } + + if (!pvt->recordhistory && !sip_config.recordhistory && !sip_config.dumphistory) { + return; + } + + if (pvt->history_entries == SIP_MAX_HISTORY_ENTRIES) { + history = AST_LIST_REMOVE_HEAD(pvt->history, next); + pvt->history_entries--; + ast_free(history); + } + + snprintf(event, sizeof(event), "%-15s ", type); + len = strlen(event); + + va_start(ap, fmt); + vsnprintf(event + len, sizeof(event) - len, fmt, ap); + va_end(ap); + + sep = event; + strsep(&sep, "\r\n"); /* Trim up everything after \r or \n */ + len = strlen(event) + 1; + + if (!(history = ast_calloc(1, sizeof(*history) + len))) { + return; + } + + if (!pvt->history && !(pvt->history = ast_calloc(1, sizeof(*pvt->history)))) { + ast_free(history); + return; + } + + memcpy(history->event, event, len); + + AST_LIST_INSERT_TAIL(pvt->history, history, next); + pvt->history_entries++; + + 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_pvt *pvt) +{ + int entries = 0; + struct sip_history *hist; + static int errmsg = FALSE; + + if (!pvt) { + return; + } + + if (!sip_debug && !DEBUG_ATLEAST(1)) { + if (!errmsg) { + ast_log(LOG_NOTICE, "You must have debugging enabled (SIP or Asterisk) in order to dump SIP history.\n"); + errmsg = TRUE; + } + + return; + } + + ast_log(LOG_DEBUG, "\n---------- SIP HISTORY for '%s' \n", pvt->callid); + + if (pvt->subscribed) { + ast_log(LOG_DEBUG, " * Subscription\n"); + } else { + ast_log(LOG_DEBUG, " * SIP Call\n"); + } + + if (pvt->history) { + AST_LIST_TRAVERSE(pvt->history, hist, next) { + ast_log(LOG_DEBUG, " %-3.3d. %s\n", ++entries, hist->event); + } + } + + if (!entries) { + ast_log(LOG_DEBUG, "Call '%s' has no history\n", pvt->callid); + } + + ast_log(LOG_DEBUG, "\n---------- END SIP HISTORY for '%s' \n", pvt->callid); +} + +/* Allocate SIP refer structure */ +int sip_refer_alloc(struct sip_pvt *pvt) +{ + sip_refer_free(pvt); + pvt->refer = ast_calloc_with_stringfields(1, struct sip_refer, 512); + + if (pvt->refer) { + pvt->refer->content = ast_str_create(128); + } + + return pvt->refer != NULL; +} + +/* Destroy SIP refer structure */ +static void sip_refer_free(struct sip_pvt *pvt) +{ + if (pvt->refer) { + ast_string_field_free_memory(pvt->refer); + ast_free(pvt->refer->content); + ast_free(pvt->refer); + + pvt->refer = NULL; + } +} + +/* Allocate SIP notify structure */ +int sip_notify_alloc(struct sip_pvt *pvt) +{ + pvt->notify = ast_calloc(1, sizeof(struct sip_notify)); + + if (pvt->notify) { + pvt->notify->content = ast_str_create(128); + } + + return pvt->notify != NULL; +} + +/* \breif Destroy a SIP notify structure */ +static void sip_notify_free(struct sip_pvt *pvt) +{ + if (pvt->notify) { + ast_variables_destroy(pvt->notify->headers); + ast_free(pvt->notify->content); + ast_free(pvt->notify); + + pvt->notify = NULL; + } +} diff -durN asterisk-22.2.0.orig/channels/sip/realtime.c asterisk-22.2.0/channels/sip/realtime.c --- asterisk-22.2.0.orig/channels/sip/realtime.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/realtime.c 2025-02-18 17:14:46.822902271 +1300 @@ -0,0 +1,255 @@ +/* + * 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/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 struct ast_variable *sip_realtime_peer(const char **name, struct ast_variable **varregs); +static int sip_realtime_by_name(const char **name, struct ast_sockaddr *addr, const char *host, struct ast_variable **varpeers, struct ast_variable **varregs); +static int sip_realtime_by_addr(const char **name, struct ast_sockaddr *addr, const char *host, struct ast_variable **varpeers, struct ast_variable **varregs); + +/* Another little helper function for backwards compatibility: this checks/fetches the sippeer that belongs to the sipreg. If none + * is found, we free the sipreg and return false. This way we can do the check inside the if-condition below. In the old code, not + * finding the sippeer also had it continue look for another match, so we do the same. */ +static struct ast_variable *sip_realtime_peer(const char **name, struct ast_variable **varregs) +{ + struct ast_variable *varpeers = NULL; + + *name = ast_variable_find_in_list(*varregs, "name"); + + if (!*name || !(varpeers = ast_load_realtime("sippeers", "name", *name, SENTINEL))) { + ast_variables_destroy(*varregs); + *varregs = NULL; + } + + return varpeers; +} + +/* If varregs is NULL, we don't use sipregs. Using empty if-bodies instead of goto's while avoiding unnecessary indents */ +static int sip_realtime_by_name(const char **name, struct ast_sockaddr *addr, const char *host, struct ast_variable **varpeers, struct ast_variable **varregs) +{ + /* Peer by name and host=dynamic */ + if ((*varpeers = ast_load_realtime("sippeers", "name", *name, "host", "dynamic", SENTINEL))) { + /* empty */ ; + /* Peer by name and host=IP */ + } else if (addr && !(*varpeers = ast_load_realtime("sippeers", "name", *name, "host", host, SENTINEL))) { + /* empty */ ; + /* Peer by name and host=HOSTNAME */ + } else if ((*varpeers = 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 (addr) { + const char *host; + + if ((host = ast_variable_find_in_list(*varpeers, "host"))) { + struct ast_sockaddr *addrs = NULL; + + if (ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, sip_transport_family(AST_TRANSPORT_UDP)) <= 0 + || ast_sockaddr_cmp(&addrs[0], addr)) { + /* No match */ + ast_variables_destroy(*varpeers); + *varpeers = NULL; + } + + ast_free(addrs); + } + } + } + + /* Did we find anything? */ + if (*varpeers) { + if (varregs) { + *varregs = ast_load_realtime("sipregs", "name", *name, SENTINEL); + } + + return TRUE; + } + + return FALSE; +} + +/* If varregs is NULL, we don't use sipregs. If we return true, then *name is set. Using empty if-bodies instead of goto's while + * avoiding unnecessary indents. */ +static int sip_realtime_by_addr(const char **name, struct ast_sockaddr *addr, const char *host, struct ast_variable **varpeers, struct ast_variable **varregs) +{ + char port[6]; /* up to 5 digits plus null terminator */ + + ast_copy_string(port, ast_sockaddr_stringify_port(addr), sizeof(port)); + + /* We're not finding this peer by this name anymore. Reset it. */ + *name = NULL; + + /* First check for fixed IP hosts */ + if ((*varpeers = ast_load_realtime("sippeers", "host", host, "port", port, SENTINEL))) { + /* empty */ ; + /* Check for registered hosts (in sipregs) */ + } else if (varregs && (*varregs = ast_load_realtime("sipregs", "host", host, "port", port, SENTINEL)) && + (*varpeers = sip_realtime_peer(name, varregs))) { + /* empty */ ; + } + + /* Nothing found? */ + if (!*varpeers) { + 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(*varpeers, "name"))) { + ast_log(LOG_WARNING, "Found peer for IP %s but it has no name\n", host); + + ast_variables_destroy(*varpeers); + *varpeers = NULL; + + if (varregs && *varregs) { + ast_variables_destroy(*varregs); + *varregs = NULL; + } + + return FALSE; + } + + /* Make sure varregs is populated if var is. The inverse, ensuring that var is set when varregs is, is taken care of */ + if (varregs && !*varregs) { + *varregs = ast_load_realtime("sipregs", "name", *name, SENTINEL); + } + + 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, struct ast_sockaddr *addr, int devstate_only) +{ + struct sip_peer *peer = NULL; + struct ast_variable *varpeers = NULL, *varregs = NULL; + char host[INET6_ADDRSTRLEN]; + int realtimeregs = ast_check_realtime("sipregs"); + + if (addr) { + ast_copy_string(host, ast_sockaddr_stringify_addr(addr), sizeof(host)); + } else { + host[0] = '\0'; + } + + if (name && sip_realtime_by_name(&name, addr, host, &varpeers, realtimeregs ? &varregs : NULL)) { + /* empty */ ; + } else if (addr && sip_realtime_by_addr(&name, addr, host, &varpeers, realtimeregs ? &varregs : NULL)) { + /* empty */ ; + } else { + return NULL; + } + + /* Peer found in realtime, now build it in memory */ + if (!(peer = sip_peer_build(name, varpeers, varregs, TRUE, devstate_only))) { + goto cleanup; + } + + 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_RTCACHEFRIENDS) && !devstate_only) { + /* Cache peer */ + ast_copy_flags(&peer->flags[1], &sip_config.flags[1], SIP_RTAUTOCLEAR|SIP_RTCACHEFRIENDS); + + if (ast_test_flag(&sip_config.flags[1], SIP_RTAUTOCLEAR)) { + AST_SCHED_REPLACE_UNREF(peer->expire, sip_sched_context, sip_config.rtautoclear * 1000, sip_peer_expire_register, peer, + ao2_t_cleanup(_data, "remove registration ref"), + ao2_t_cleanup(peer, "remove registration ref"), + ao2_t_bump(peer, "add registration ref")); + } + + ao2_t_link(sip_peers, peer, "link peer into sip_peers table"); + + if (!ast_sockaddr_isnull(&peer->addr)) { + ao2_t_link(sip_peers_by_addr, peer, "link peer into sip_peers_by_addr table"); + } + } + + peer->is_realtime = TRUE; + +cleanup: + ast_variables_destroy(varpeers); + ast_variables_destroy(varregs); + + 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], ipaddr[INET6_ADDRSTRLEN], lastms[20], regseconds[20]; + const char *sysname = ast_config_AST_SYSTEM_NAME; + char *syslabel = NULL; + int realtimeregs = ast_check_realtime("sipregs"); + + ast_copy_string(ipaddr, ast_sockaddr_isnull(&peer->addr) ? "" : ast_sockaddr_stringify_addr(&peer->addr), sizeof(ipaddr)); + ast_copy_string(port, ast_sockaddr_port(&peer->addr) ? ast_sockaddr_stringify_port(&peer->addr) : "", sizeof(port)); + + snprintf(lastms, sizeof(lastms), "%d", peer->lastms); + snprintf(regseconds, sizeof(regseconds), "%d", (int) (time(NULL) + expiry)); /* Expiration time */ + + if (ast_strlen_zero(sysname)) { /* No system name, disable this */ + sysname = NULL; + } else if (sip_config.rtsave_sysname) { + syslabel = "regserver"; + } + + ast_update_realtime(realtimeregs ? "sipregs" : "sippeers", "name", peer->name, "ipaddr", ipaddr, "port", port, + "regseconds", regseconds, "useragent", peer->useragent, "lastms", lastms, + peer->deprecated_username ? "username" : "defaultuser", peer->username, syslabel, sysname, SENTINEL); + + if (peer->fullcontact) { + ast_update_realtime(realtimeregs ? "sipregs" : "sippeers", "name", peer->name, "fullcontact", peer->fullcontact, SENTINEL); + } + + if (sip_config.rtsave_path) { + struct ast_str *path = sip_route_list(&peer->path, FALSE, 0); + + if (path) { + ast_update_realtime(realtimeregs ? "sipregs" : "sippeers", "name", peer->name, "path", ast_str_buffer(path), SENTINEL); + ast_free(path); + } + } +} diff -durN asterisk-22.2.0.orig/channels/sip/recording.c asterisk-22.2.0/channels/sip/recording.c --- asterisk-22.2.0.orig/channels/sip/recording.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/recording.c 2025-02-18 17:14:46.823902245 +1300 @@ -0,0 +1,242 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*** 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/pvt.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_pvt *pvt = data; + + ao2_lock(pvt); + + if (ast_channel_state(pvt->owner) != AST_STATE_UP) { + ao2_unlock(pvt); + return -1; + } + + if (!ast_test_flag(&pvt->flags[2], SIP_CISCO_RECORDING)) { + ao2_unlock(pvt); + return -1; + } + + ao2_unlock(pvt); + + return 0; +} + +static void *sip_recording_thread(void *data) +{ + struct sip_recording_data *recording_data = data; + struct sip_pvt *pvt, *target_pvt; + struct ast_channel *chan; + struct ast_format_cap *cap; + struct ast_party_connected_line connected; + char *peername, *uniqueid, *channame; + int cause; + + if (!(target_pvt = sip_pvt_find(recording_data->callid, recording_data->tag, recording_data->theirtag))) { + ast_debug(1, "call leg does not exist\n"); + goto cleanup; + } + + ao2_lock(target_pvt); + + if (!(chan = target_pvt->owner)) { + ast_debug(1, "no owner channel\n"); + ao2_unlock(target_pvt); + ao2_t_cleanup(target_pvt, "drop target_pvt"); + goto cleanup; + } + + peername = ast_strdupa(target_pvt->peername); + channame = ast_strdupa(ast_channel_name(chan)); + uniqueid = ast_strdupa(ast_channel_uniqueid(chan)); + + cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + ast_format_cap_append(cap, ast_channel_readformat(chan), 0); + + ao2_unlock(target_pvt); + + if (!(chan = ast_request("SIP", cap, NULL, NULL, peername, &cause))) { + ast_debug(1, "unable to request channel\n"); + ao2_t_cleanup(target_pvt, "drop target_pvt"); + goto cleanup; + } + + ao2_ref(cap, -1); + pvt = ast_channel_tech_pvt(chan); + + ast_string_field_set(pvt, join_callid, recording_data->callid); + ast_string_field_set(pvt, join_tag, recording_data->tag); + ast_string_field_set(pvt, join_theirtag, recording_data->theirtag); + + ast_set_flag(&pvt->flags[1], SIP_CALL_ONHOLD_INACTIVE); + ast_set_flag(&pvt->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 (pvt->relatedpeer) { + ast_atomic_fetchadd_int(&pvt->relatedpeer->onhold, +1); + } + + ast_party_connected_line_set_init(&connected, ast_channel_connected(chan)); + connected.id.name.valid = TRUE; + connected.id.name.str = "Record"; + ast_channel_set_connected_line(chan, &connected, NULL); + + ast_channel_context_set(chan, pvt->context); + ast_channel_exten_set(chan, "record"); + ast_channel_priority_set(chan, 1); + + pbx_builtin_setvar_helper(chan, "RECORD_PEERNAME", peername); + pbx_builtin_setvar_helper(chan, "RECORD_UNIQUEID", uniqueid); + pbx_builtin_setvar_helper(chan, "RECORD_CHANNEL", channame); + pbx_builtin_setvar_helper(chan, "RECORD_DIRECTION", recording_data->outgoing ? "out" : "in"); + + if (ast_call(chan, peername, 5000)) { + ast_debug(1, "unable to call\n"); + ast_hangup(chan); + ao2_t_cleanup(target_pvt, "drop target_pvt"); + goto cleanup; + } + + if (ast_safe_sleep_conditional(chan, 5000, sip_recording_wait_for_answer, pvt)) { + ast_debug(1, "no answer\n"); + ast_hangup(chan); + ao2_t_cleanup(target_pvt, "drop target_pvt"); + goto cleanup; + } + + ao2_lock(target_pvt); + + if (recording_data->outgoing) { + target_pvt->recordoutpvt = ao2_t_bump(pvt, "copying pvt into recordoutpvt"); + } else { + target_pvt->recordinpvt = ao2_t_bump(pvt, "copying pvt into recordinpvt"); + } + + ao2_unlock(target_pvt); + ao2_t_cleanup(target_pvt, "drop target_pvt"); + + if (!ast_check_hangup(chan)) { + ast_pbx_run(chan); + } + +cleanup: + ast_string_field_free_memory(recording_data); + ast_free(recording_data); + + return NULL; +} + +int sip_recording_start(const char *callid, const char *tag, const char *theirtag, int outgoing) +{ + pthread_t threadid; + struct sip_recording_data *recording_data; + + if (!(recording_data = ast_calloc_with_stringfields(1, struct sip_recording_data, 128))) { + return -1; + } + + ast_string_field_set(recording_data, callid, callid); + ast_string_field_set(recording_data, tag, tag); + ast_string_field_set(recording_data, theirtag, theirtag); + 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 *callid, const char *tag, const char *theirtag) +{ + struct sip_pvt *target_pvt; + struct ast_channel *chan; + + if (!(target_pvt = sip_pvt_find(callid, tag, theirtag))) { + ast_debug(1, "dialogid call leg does not exist\n"); + return -1; + } + + if (target_pvt->recordoutpvt) { + ao2_lock(target_pvt->recordoutpvt); + + if ((chan = target_pvt->recordoutpvt->owner)) { + ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); + } + + ao2_unlock(target_pvt->recordoutpvt); + + ao2_lock(target_pvt); + + ao2_t_cleanup(target_pvt->recordoutpvt, "drop recordoutpvt"); + target_pvt->recordoutpvt = NULL; + + ao2_unlock(target_pvt); + } + + if (target_pvt->recordinpvt) { + ao2_lock(target_pvt->recordinpvt); + + if ((chan = target_pvt->recordinpvt->owner)) { + ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); + } + + ao2_unlock(target_pvt->recordinpvt); + ao2_lock(target_pvt); + + ao2_t_cleanup(target_pvt->recordinpvt, "drop recordinpvt"); + target_pvt->recordinpvt = NULL; + + ao2_unlock(target_pvt); + } + + ao2_t_cleanup(target_pvt, "drop target_pvt"); + + return 0; +} diff -durN asterisk-22.2.0.orig/channels/sip/registry.c asterisk-22.2.0/channels/sip/registry.c --- asterisk-22.2.0.orig/channels/sip/registry.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/registry.c 2025-02-18 17:14:46.823902245 +1300 @@ -0,0 +1,720 @@ +/* + * 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/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/registry.h" +#include "include/pvt.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 int __sip_registry_sched_send(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 *obj, 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_REGSENT: + return "Request Sent"; + case SIP_REGISTRY_AUTHSENT: + 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_NOAUTH: + return "No Authentication"; + default: + break; + } + + return "Unknown"; +} + +int sip_registry_hash(const void *obj, const int flags) +{ + const struct sip_registry *registry = obj; + const char *config; + + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_KEY: + config = obj; + break; + case OBJ_SEARCH_OBJECT: + config = registry->config; + break; + default: + /* Hash can only work on something with a full key. */ + ast_assert(0); + return 0; + } + + return ast_str_hash(config); +} + +int sip_registry_cmp(void *obj, void *arg, int flags) +{ + const struct sip_registry *registry1 = obj, *registry2 = arg; + const char *config = arg; + int cmp; + + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_OBJECT: + config = registry2->config; + /* Fall through */ + case OBJ_SEARCH_KEY: + cmp = strcmp(registry1->config, config); + break; + default: + cmp = 0; + break; + } + + if (cmp) { + return 0; + } + + return CMP_MATCH; +} + +/* create sip_registry object from register=> line in sip.conf and link into registry container */ +int sip_registry_build(const char *config, int lineno) +{ + struct sip_registry *registry; + int port = 0; + int domainport = 0; + enum ast_transport transport = AST_TRANSPORT_UDP; + char *userpart = NULL, *hostpart = NULL; + + /* register => [peername?][transport://]user[@domain][:secret[:authuser]]@host[:port][/exten][~expiry] */ + AST_DECLARE_APP_ARGS(pre1, + AST_APP_ARG(peername); + AST_APP_ARG(userpart); + ); + AST_DECLARE_APP_ARGS(pre2, + AST_APP_ARG(transport); + AST_APP_ARG(blank); + AST_APP_ARG(userpart); + ); + AST_DECLARE_APP_ARGS(user1, + AST_APP_ARG(userpart); + AST_APP_ARG(secret); + AST_APP_ARG(authuser); + ); + AST_DECLARE_APP_ARGS(user2, + AST_APP_ARG(user); + AST_APP_ARG(domain); + ); + AST_DECLARE_APP_ARGS(user3, + AST_APP_ARG(authuser); + AST_APP_ARG(domainport); + ); + AST_DECLARE_APP_ARGS(host1, + AST_APP_ARG(hostpart); + AST_APP_ARG(expiry); + ); + AST_DECLARE_APP_ARGS(host2, + AST_APP_ARG(hostpart); + AST_APP_ARG(exten); + ); + AST_DECLARE_APP_ARGS(host3, + AST_APP_ARG(host); + AST_APP_ARG(port); + ); + + if (ast_strlen_zero(config)) { + return -1; + } + + userpart = ast_strdupa(config); + + /* register => [peername?][transport://]user[@domain][:secret[:authuser]]@host[:port][/exten][~expiry] + * userpart => [peername?][transport://]user[@domain][:secret[:authuser]] + * hostpart => host[:port][/exten][~expiry] */ + if ((hostpart = strrchr(userpart, '@'))) { + *hostpart++ = '\0'; + } + + if (ast_strlen_zero(userpart) || ast_strlen_zero(hostpart)) { + ast_log(LOG_WARNING, "Format for registration is [peername?][transport://]user[@domain][:secret[:authuser]]@host[:port][/exten][~expiry] at line %d\n", lineno); + return -1; + } + + /* pre1.peername => peername + * pre1.userpart => [transport://]user[@domain][:secret[:authuser]] + * hostpart => host[:port][/exten][~expiry] */ + AST_NONSTANDARD_RAW_ARGS(pre1, userpart, '?'); + if (ast_strlen_zero(pre1.userpart)) { + pre1.userpart = pre1.peername; + pre1.peername = NULL; + } + + /* pre1.peername => peername + * pre2.transport = transport + * pre2.userpart => user[@domain][:secret[:authuser]] + * hostpart => host[:port][/exten][~expiry] */ + AST_NONSTANDARD_RAW_ARGS(pre2, pre1.userpart, '/'); + + if (ast_strlen_zero(pre2.userpart)) { + pre2.userpart = pre2.transport; + pre2.transport = NULL; + } else { + pre2.transport[strlen(pre2.transport) - 1] = '\0'; /* Remove trailing : */ + } + + if (!ast_strlen_zero(pre2.blank)) { + ast_log(LOG_WARNING, "Format for registration is [peername?][transport://]user[@domain][:secret[:authuser]]@host[:port][/exten][~expiry] at line %d\n", lineno); + return -1; + } + + /* pre1.peername => peername + * pre2.transport = transport + * user1.userpart => user[@domain] + * user1.secret => secret + * user1.authuser => authuser + * hostpart => host[:port][/exten][~expiry] */ + AST_NONSTANDARD_RAW_ARGS(user1, pre2.userpart, ':'); + + /* pre1.peername => peername + * pre2.transport = transport + * user1.userpart => user[@domain] + * user1.secret => secret + * user1.authuser => authuser + * host1.hostpart => host[:port][/exten] + * host1.expiry => [expiry] */ + AST_NONSTANDARD_RAW_ARGS(host1, hostpart, '~'); + + /* pre1.peername => peername + * pre2.transport = transport + * user1.userpart => user[@domain] + * user1.secret => secret + * user1.authuser => authuser + * host2.hostpart => host[:port] + * host2.exten => [exten] + * host1.expiry => [expiry] */ + AST_NONSTANDARD_RAW_ARGS(host2, host1.hostpart, '/'); + + /* pre1.peername => peername + * pre2.transport = transport + * user1.userpart => user[@domain] + * user1.secret => secret + * user1.authuser => authuser + * host3.host => host + * host3.port => port + * host2.exten => exten + * host1.expiry => expiry */ + AST_NONSTANDARD_RAW_ARGS(host3, host2.hostpart, ':'); + + /* pre1.peername => peername + * pre2.transport = transport + * user2.user => user + * user2.domain => domain + * user1.secret => secret + * user1.authuser => authuser + * host3.host => host + * host3.port => port + * host2.exten => exten + * host1.expiry => expiry */ + AST_NONSTANDARD_RAW_ARGS(user2, user1.userpart, '@'); + + /* pre1.peername => peername + * pre2.transport = transport + * user2.user => user + * user2.domain => domain + * user1.secret => secret + * user3.authuser => authuser + * user3.domainport => domainport + * host3.host => host + * host3.port => port + * host2.exten => exten + * host1.expiry => expiry */ + AST_NONSTANDARD_RAW_ARGS(user3, user1.authuser, ':'); + + /* Reordering needed due to fields being [(:secret[:username])|(:domainport:secret:username)] + * but parsing being [secret[:username[:domainport]]] */ + if (user3.argc == 2) { + char *reorder = user3.domainport; + + user3.domainport = user1.secret; + user1.secret = user3.authuser; + user3.authuser = reorder; + } + + if (host3.port) { + if (!(port = sip_str2port(host3.port, 0))) { + ast_log(LOG_NOTICE, "'%s' is not a valid port number on line %d of sip.conf. using default.\n", host3.port, lineno); + } + } + + if (user3.domainport) { + if (!(domainport = sip_str2port(user3.domainport, 0))) { + ast_log(LOG_NOTICE, "'%s' is not a valid domain port number on line %d of sip.conf. using default.\n", user3.domainport, lineno); + } + } + + /* set transport type */ + if (!pre2.transport) { + transport = AST_TRANSPORT_UDP; + } else if (!strncasecmp(pre2.transport, "tcp", 3)) { + transport = AST_TRANSPORT_TCP; + } else if (!strncasecmp(pre2.transport, "tls", 3)) { + transport = AST_TRANSPORT_TLS; + } else if (!strncasecmp(pre2.transport, "udp", 3)) { + transport = AST_TRANSPORT_UDP; + } else { + transport = AST_TRANSPORT_UDP; + ast_log(LOG_NOTICE, "'%.3s' is not a valid transport type on line %d of sip.conf. defaulting to udp.\n", pre2.transport, lineno); + } + + /* if no port specified, set default for transport */ + if (!port) { + if (transport == AST_TRANSPORT_TLS) { + port = SIP_STANDARD_TLS_PORT; + } else { + port = SIP_STANDARD_PORT; + } + } + + if ((registry = ao2_t_find(sip_registry, config, OBJ_SEARCH_KEY, "check for existing registry"))) { + ao2_t_ref(registry, -1, "throw away found registry"); + return 0; + } + + if (!(registry = ao2_t_alloc(sizeof(*registry), sip_registry_destroy, "allocate a registry struct"))) { + ast_log(LOG_ERROR, "Out of memory. Can't allocate SIP registry entry\n"); + return -1; + } + + if (ast_string_field_init(registry, 256)) { + ao2_t_ref(registry, -1, "failed to string_field_init, drop reg"); + return -1; + } + + registry->transport = transport; + registry->port = port; + registry->domainport = domainport; + registry->expire = -1; + registry->timeout = -1; + registry->callid_valid = FALSE; + registry->ocseq = SIP_INITIAL_CSEQ; + registry->refresh = registry->expiry = registry->configured_expiry = (host1.expiry ? atoi(ast_strip_quoted(host1.expiry, "\"", "\"")) : sip_config.default_expiry); + + ast_string_field_set(registry, config, config); + + /* copy into sip_registry object */ + ast_string_field_set(registry, exten, ast_strip_quoted(S_OR(host2.exten, "s"), "\"", "\"")); + ast_string_field_set(registry, username, ast_strip_quoted(S_OR(user2.user, ""), "\"", "\"")); + ast_string_field_set(registry, hostname, ast_strip_quoted(S_OR(host3.host, ""), "\"", "\"")); + ast_string_field_set(registry, authuser, ast_strip_quoted(S_OR(user3.authuser, ""), "\"", "\"")); + ast_string_field_set(registry, secret, ast_strip_quoted(S_OR(user1.secret, ""), "\"", "\"")); + ast_string_field_set(registry, peername, ast_strip_quoted(S_OR(pre1.peername, ""), "\"", "\"")); + ast_string_field_set(registry, domain, ast_strip_quoted(S_OR(user2.domain, ""), "\"", "\"")); + + /* set default expiry if necessary */ + if (registry->refresh && !registry->expiry && !registry->configured_expiry) { + registry->refresh = registry->expiry = registry->configured_expiry = sip_config.default_expiry; + } + + ao2_t_link(sip_registry, registry, "link reg to sip_registry"); + ao2_t_ref(registry, -1, "unref the reg pointer"); + + 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->pvt) { + 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_pvt_unlink(registry->pvt); + ao2_t_cleanup(registry->pvt, "remove registry->pvt from registry traversal"); + + registry->pvt = NULL; + } + + AST_SCHED_DEL_UNREF(sip_sched_context, registry->expire, ao2_t_ref(registry, -1, "Stop scheduled reregister timeout")); + AST_SCHED_DEL_UNREF(sip_sched_context, registry->timeout, ao2_t_ref(registry, -1, "Stop scheduled register timeout")); + + if (registry->dnsmgr) { + ast_dnsmgr_release(registry->dnsmgr); + registry->dnsmgr = NULL; + + ao2_t_ref(registry, -1, "reg ptr unref from dnsmgr"); + } + + ao2_unlock(registry); + ao2_t_ref(registry, -1, "sip_registry_unlink_all action"); + + return 0; +} + +/* Destroy registry object created with the register= statement in static configuration */ +void sip_registry_destroy(void *data) +{ + struct sip_registry *registry = data; + + /* Really delete */ + ast_debug(3, "Destroying registry entry for %s@%s\n", registry->username, registry->hostname); + + if (registry->pvt) { + /* Clear registry before destroying to ensure we don't get reentered trying to grab the registry lock */ + ao2_t_replace(registry->pvt->registry, NULL, "destroy registry->pvt->registry"); + ast_debug(3, "Destroying active SIP dialog for registry %s@%s\n", registry->username, registry->hostname); + + sip_pvt_unlink(registry->pvt); + ao2_t_cleanup(registry->pvt, "unref registry->pvt"); + } + + ast_string_field_free_memory(registry); +} + +static int sip_registry_unlink(void *obj, void *arg, int flags) +{ + struct sip_registry *registry = obj; + + ao2_t_ref(registry, +1, "sip_registry_unlink action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_registry_unlink, registry) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(registry, -1, "Failed to schedule sip_registry_unlink action"); + } + + return CMP_MATCH; +} + +void sip_registry_destroy_all(void) +{ + ao2_t_callback(sip_registry, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, sip_registry_unlink, NULL, "remove all SIP registry"); +} + +/* Build SIP Call-ID value for a REGISTER transaction */ +void sip_registry_build_callid(struct sip_registry *registry, const struct ast_sockaddr *ourip, const char *fromdomain) +{ + char buf[33]; + const char *host = S_OR(fromdomain, ast_sockaddr_stringify_host_remote(ourip)); + + ast_string_field_build(registry, callid, "%s@%s", sip_generate_random_string(buf, sizeof(buf)), host); +} + +/* Build SIP From tag value for REGISTER */ +void sip_registry_build_localtag(struct sip_registry *registry) +{ + ast_string_field_build(registry, localtag, "as%08lx", (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) +{ + /* if we are here, we know that we need to reregister. */ + struct sip_registry *registry = (struct sip_registry *) data; + + if (registry->pvt && registry->pvt->recordhistory) { + sip_history_append(registry->pvt, "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 */ + if (sip_debug) { + ast_log(LOG_NOTICE, "Re-registration for %s@%s\n", registry->username, registry->hostname); + } + + registry->expire = -1; + registry->expiry = registry->configured_expiry; + + switch (registry->state) { + case SIP_REGISTRY_UNREGISTERED: + case SIP_REGISTRY_REGSENT: + case SIP_REGISTRY_AUTHSENT: + break; + case SIP_REGISTRY_REJECTED: + case SIP_REGISTRY_NOAUTH: + 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, SIP_REGISTER, NULL, NULL); + ao2_t_ref(registry, -1, "Scheduled reregister timeout complete"); + + return 0; +} + +void sip_registry_dnsmgr_lookup(struct ast_sockaddr *oldaddr, struct ast_sockaddr *newaddr, void *data) +{ + struct sip_registry *registry = data; + const char *oldhost, *newhost; + + /* This shouldn't happen, but just in case */ + if (ast_sockaddr_isnull(newaddr)) { + ast_debug(1, "Empty sockaddr change...ignoring!\n"); + return; + } + + if (!ast_sockaddr_port(newaddr)) { + ast_sockaddr_set_port(newaddr, registry->port); + } + + oldhost = ast_strdupa(ast_sockaddr_stringify(oldaddr)); + newhost = ast_strdupa(ast_sockaddr_stringify(newaddr)); + + ast_debug(1, "Changing registry %s from %s to %s\n", S_OR(registry->peername, registry->hostname), oldhost, newhost); + ast_sockaddr_copy(®istry->us, newaddr); +} + +/* Send all known registrations */ +void sip_registry_send_all(void) +{ + int ms; + int gap; + struct ao2_iterator iter; + struct sip_registry *registry; + + if (!ao2_container_count(sip_registry)) { + return; + } + + gap = sip_config.default_expiry * 1000 / ao2_container_count(sip_registry); + + if (gap > 100) { + gap = 100; + } + + ms = gap; + iter = ao2_iterator_init(sip_registry, 0); + + while ((registry = ao2_t_iterator_next(&iter, "sip_registry_send_all iter"))) { + ao2_lock(registry); + + ms += gap; + sip_registry_sched_send(registry, ms); + + ao2_unlock(registry); + ao2_t_ref(registry, -1, "sip_registry_send_all iter"); + } + + ao2_iterator_destroy(&iter); +} + +/* Run by the sched thread. */ +static int __sip_registry_sched_send(const void *data) +{ + struct sip_sched_data *sched_data = (void *) data; + struct sip_registry *registry = sched_data->registry; + int ms = sched_data->ms; + + ast_free(sched_data); + AST_SCHED_DEL_UNREF(sip_sched_context, registry->expire, ao2_t_ref(registry, -1, "Stop scheduled reregister timeout")); + + ao2_t_ref(registry, +1, "Schedule reregister timeout"); + registry->expire = ast_sched_add(sip_sched_context, ms, sip_registry_send, registry); + + if (registry->expire < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(registry, -1, "Failed to schedule reregister timeout"); + } + + ao2_t_ref(registry, -1, "Start reregister timeout action"); + + return 0; +} + +void sip_registry_sched_send(struct sip_registry *registry, int ms) +{ + 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->ms = ms; + + ao2_t_ref(registry, +1, "Start reregister timeout action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_registry_sched_send, sched_data) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(registry, -1, "Failed to schedule start reregister timeout action"); + 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_REGSENT: + case SIP_REGISTRY_AUTHSENT: + 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 = -1; + ao2_t_ref(registry, -1, "Scheduled register timeout completed early"); + 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 r->call == NULL. + * Otherwise destroy it, as we have a timeout so we don't want it. */ + if (registry->pvt) { + /* Unlink us, destroy old call. Locking is not relevant here because all this happens + in the single SIP manager thread. */ + ao2_lock(registry->pvt); + sip_pvt_set_need_destroy(registry->pvt, "registration timeout"); + + /* Pretend to ACK anything just in case */ + sip_packet_pretend_ack(registry->pvt); + ao2_unlock(registry->pvt); + + /* decouple the two objects */ + /* pvt->registry == registry, so registry has 2 refs, and the unref won't take the object away */ + ao2_t_replace(registry->pvt->registry, NULL, "pvt->registry unreffed"); + ao2_t_cleanup(registry->pvt, "unrefing r->call"); + + registry->pvt = NULL; + } + /* If we have a limit, stop registration and give up */ + registry->timeout = -1; + + if (sip_config.regattempts_max && registry->attempts >= sip_config.regattempts_max) { + /* 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, SIP_REGISTER, NULL, NULL); + + 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, "Scheduled register timeout complete"); + + 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, ao2_t_ref(registry, -1, "Stop scheduled register timeout")); + + ao2_t_ref(registry, +1, "Schedule register timeout"); + registry->timeout = ast_sched_add(sip_sched_context, sip_config.reg_timeout * 1000, sip_registry_timeout, registry); + + if (registry->timeout < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(registry, -1, "Failed to schedule register timeout"); + } + + ast_debug(1, "Scheduled a registration timeout for %s id #%d \n", registry->hostname, registry->timeout); + ao2_t_ref(registry, -1, "Start register timeout action"); + + return 0; +} + +void sip_registry_start_timeout(struct sip_registry *registry) +{ + ao2_t_ref(registry, +1, "Start register timeout action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_registry_start_timeout, registry) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(registry, -1, "Failed to schedule start register timeout action"); + } +} + +/* 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, ao2_t_ref(registry, -1, "Stop scheduled register timeout")); + ao2_t_ref(registry, -1, "Stop register timeout action"); + + return 0; +} + +void sip_registry_stop_timeout(struct sip_registry *registry) +{ + ao2_t_ref(registry, +1, "Stop register timeout action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_registry_stop_timeout, registry) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_ref(registry, -1, "Failed to schedule stop register timeout action"); + } +} diff -durN asterisk-22.2.0.orig/channels/sip/request.c asterisk-22.2.0/channels/sip/request.c --- asterisk-22.2.0.orig/channels/sip/request.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/request.c 2025-02-18 17:14:46.825902191 +1300 @@ -0,0 +1,2795 @@ +/* + * 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/pvt.h" +#include "include/sdp.h" +#include "include/utils.h" +#include "include/config.h" +#include "include/handlers.h" +#include "include/message.h" + +enum { + SIP_REQUEST_MATCH, + 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 200ok responses. */ +}; + +/* arguments used for Request/Response to matching */ +struct sip_match_request_args { + int method; + const char *callid; + const char *totag; + const char *fromtag; + uint32_t seqno; + int resp; /* Set if this method is a Response */ + const char *ruri; /* Set if the method is a Request */ + const char *viabranch; + const char *viasentby; + int authentication_present; /* Set this if the Authentication header is present in the Request. */ +}; + +static int sip_request_match_pvt(struct sip_pvt *pvt, struct sip_match_request_args *arg); + +AST_THREADSTORAGE(sip_content_buf); + +/* Initialize SIP request */ +int sip_request_alloc(struct sip_request *req, int method, const char *uri) +{ + /* Initialize a request */ + memset(req, 0, sizeof(*req)); + + if (!(req->data = ast_str_create(SIP_MIN_PACKET_SIZE))) { + return -1; + } + + if (!(req->content = ast_str_create(SIP_MIN_PACKET_SIZE))) { + ast_free(req->data); + req->data = NULL; + + return -1; + } + + req->method = method; + req->header[0] = 0; + + ast_str_set(&req->data, 0, "%s %s SIP/2.0\r\n", sip_methods[method].name, uri); + req->headers++; + + return 0; +} + +/* Deinitialize SIP response/request */ +void sip_request_free(struct sip_request *req) +{ + if (req->data) { + ast_free(req->data); + req->data = NULL; + } + + if (req->content) { + ast_free(req->content); + req->content = NULL; + } +} + +/* Initiate new SIP request to peer/user */ +void sip_request_init(struct sip_request *req, struct sip_pvt *pvt, int method, const char *explicit_uri) +{ + struct ast_str *invite = ast_str_create(256); + struct ast_str *from = ast_str_create(256); + struct ast_str *to = ast_str_create(256); + char tmp_name[SIP_BUFFER_SIZE / 2]; + char tmp_user[SIP_BUFFER_SIZE / 2]; + const char *user = NULL; + const char *name = NULL; + const char *domain = NULL; + const char *urioptions = ""; + int ourport; + struct ast_party_id connected_id; + int res; + + if (ast_test_flag(&pvt->flags[0], SIP_USEREQPHONE)) { + const char *username = pvt->username; /* being a string field, cannot be NULL */ + + /* Test pvt->username against allowed characters in AST_DIGIT_ANY If it matches the allowed characters list, then + * sipuser = ";user=phone" If not, then sipuser = "". + is allowed in first position in a tel: uri */ + if (*username == '+') { + username++; + } + + for (; *username; username++) { + if (!strchr(AST_DIGIT_ANYNUM, *username)) { + break; + } + } + + /* If we have only digits, add ;user=phone to the uri */ + if (!*username) { + urioptions = ";user=phone"; + } + } + + ast_string_field_build(pvt, lastmsg, "Init: %s", sip_methods[method].name); + + if (ast_strlen_zero(pvt->fromdomain)) { + domain = ast_sockaddr_stringify_host_remote(&pvt->ourip); + } + if (pvt->owner) { + connected_id = ast_channel_connected_effective_id(pvt->owner); + + 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 = ""; + user = "anonymous"; + domain = "anonymous.invalid"; + } + } + + /* Hey, it's a NOTIFY! See if they've configured a mwi_from. + * XXX Right now, this logic works because the only place that mwi_from + * is set on the sip_pvt is in sip_peer_send_mwi. If things changed, then + * we might end up putting the mwi_from setting into other types of NOTIFY + * messages as well. + */ + if (method == SIP_NOTIFY && !ast_strlen_zero(pvt->mwi_from)) { + user = pvt->mwi_from; + } + + if (ast_strlen_zero(user)) { + user = sip_config.callerid; + } + + /* Allow user to be overridden */ + if (!ast_strlen_zero(pvt->fromuser)) { + user = pvt->fromuser; + } else { /* Save for any further attempts */ + ast_string_field_set(pvt, fromuser, user); + } + + /* Allow user to be overridden */ + if (!ast_strlen_zero(pvt->fromname)) { + name = pvt->fromname; + } else { /* Save for any further attempts */ + ast_string_field_set(pvt, fromname, name); + } + + /* Allow domain to be overridden */ + if (!ast_strlen_zero(pvt->fromdomain)) { + domain = pvt->fromdomain; + } else { /* Save for any further attempts */ + ast_string_field_set(pvt, fromdomain, domain); + } + + if (sip_config.pedanticsipchecking) { + ast_uri_encode(user, tmp_user, sizeof(tmp_user), ast_uri_sip_user); + user = tmp_user; + } + + ast_string_field_set(pvt, exten, user); + ourport = pvt->fromdomainport && (pvt->fromdomainport != SIP_STANDARD_PORT) ? pvt->fromdomainport : ast_sockaddr_port(&pvt->ourip); + + if (!sip_standard_port(pvt->socket.type, ourport)) { + res = ast_str_set(&from, 0, ";tag=%s", user, domain, ourport, pvt->tag); + } else { + res = ast_str_set(&from, 0, ";tag=%s", user, domain, pvt->tag); + } + + if (res == AST_DYNSTR_BUILD_FAILED) { + /* We don't have an escape path from here... */ + ast_log(LOG_ERROR, "The From header was truncated in call '%s'. This call setup will fail.\n", pvt->callid); + + /* Make sure that the field contains something non-broken. See https://issues.asterisk.org/jira/browse/ASTERISK-26069 */ + ast_str_set(&from, 3, "<>"); + } + + /* If a caller id name was specified, prefix a display name, if there is enough room. */ + if (!ast_strlen_zero(name)) { + size_t fromlen = ast_str_strlen(from); + size_t namelen; + + if (sip_config.pedanticsipchecking) { + ast_escape_quoted(name, tmp_name, sizeof(tmp_name)); + name = tmp_name; + } + + namelen = strlen(name); + res = ast_str_make_space(&from, namelen + fromlen + 4); + + if (res == 0) { + /* needed again, as ast_str_make_space coud've changed the pointer */ + char *tmp = ast_str_buffer(from); + + memmove(tmp + namelen + 3, tmp, fromlen + 1); + memcpy(tmp + 1, name, namelen); + + tmp[0] = '"'; + tmp[namelen + 1] = '"'; + tmp[namelen + 2] = ' '; + } + } + + if (!ast_strlen_zero(explicit_uri)) { + ast_str_set(&invite, 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(pvt->fullcontact)) { + /* If we have full contact, trust it */ + ast_str_append(&invite, 0, "%s", pvt->fullcontact); + } else { + /* Otherwise, use the username while waiting for registration */ + ast_str_append(&invite, 0, "sip:"); + + if (!ast_strlen_zero(pvt->username)) { + name = pvt->username; + + if (sip_config.pedanticsipchecking) { + ast_uri_encode(name, tmp_name, sizeof(tmp_name), ast_uri_sip_user); + name = tmp_name; + } + + ast_str_append(&invite, 0, "%s@", name); + } + + ast_str_append(&invite, 0, "%s", pvt->tohost); + + if (pvt->portinuri) { + ast_str_append(&invite, 0, ":%d", ast_sockaddr_port(&pvt->sa)); + } + + ast_str_append(&invite, 0, "%s", urioptions); + } + } + + /* If custom URI options have been provided, append them */ + if (pvt->options && !ast_strlen_zero(pvt->options->uri_options)) { + ast_str_append(&invite, 0, ";%s", pvt->options->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(pvt, uri, ast_str_buffer(invite)); + + if (!ast_strlen_zero(pvt->todnid)) { + /* \todo Need to add back the VXML URL here at some point, possibly use build_string for all this junk */ + if (!strchr(pvt->todnid, '@')) { + /* We have no domain in the dnid */ + res = ast_str_set(&to, 0, "%s%s", pvt->todnid, pvt->tohost, ast_strlen_zero(pvt->theirtag) ? "" : ";tag=", pvt->theirtag); + } else { + res = ast_str_set(&to, 0, "%s%s", pvt->todnid, ast_strlen_zero(pvt->theirtag) ? "" : ";tag=", pvt->theirtag); + } + } else { + if (method == SIP_NOTIFY && !ast_strlen_zero(pvt->theirtag)) { + /* If this is a NOTIFY, use the From: tag in the subscribe (RFC 3265) */ + res = ast_str_set(&to, 0, "<%s%s>;tag=%s", strncasecmp(pvt->uri, "sip:", 4) ? "sip:" : "", pvt->uri, pvt->theirtag); + } else { + res = ast_str_set(&to, 0, "<%s>", pvt->uri); + } + } + + if (res == AST_DYNSTR_BUILD_FAILED) { + /* We don't have an escape path from here... */ + ast_log(LOG_ERROR, "The To header was truncated in call '%s'. This call setup will fail.\n", pvt->callid); + ast_str_set(&to, 3, "<>"); + } + + sip_request_alloc(req, method, pvt->uri); + + sip_request_add_header(req, "Via", pvt->via); + sip_request_add_max_forwards(req, pvt); + + /* 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(req, &pvt->route, 0); + + sip_request_add_header(req, "From", ast_str_buffer(from)); + sip_request_add_header(req, "To", ast_str_buffer(to)); + + sip_pvt_build_contact(pvt, req, FALSE); + + sip_request_add_header(req, "Contact", pvt->our_contact); + sip_request_add_header(req, "Call-ID", pvt->callid); + + sip_request_build_header(req, "CSeq", "%u %s", ++pvt->ocseq, sip_methods[method].name); + + if (!ast_strlen_zero(sip_config.useragent)) { + sip_request_add_header(req, "User-Agent", sip_config.useragent); + } + + ast_free(from); + ast_free(to); + ast_free(invite); +} + +/* Initialize a SIP request message (not the initial one in a dialog) */ +int sip_request_prepare(struct sip_request *req, struct sip_pvt *pvt, int method, uint32_t seqno, int newbranch) +{ + const char *uri; + char *to, *from, newto[SIP_BUFFER_SIZE]; + int is_strict = FALSE; /* Strict routing flag */ + int is_outbound = ast_test_flag(&pvt->flags[0], SIP_OUTGOING); /* Session direction */ + + ast_string_field_build(pvt, lastmsg, "Tx: %s", sip_methods[method].name); + + if (!seqno) { + pvt->ocseq++; + seqno = pvt->ocseq; + } + + /* A CANCEL must have the same branch as the INVITE that it is canceling. */ + if (method == SIP_CANCEL) { + pvt->branch = pvt->invite_branch; + sip_pvt_build_via(pvt); + } else if (newbranch && (method == SIP_INVITE)) { + pvt->branch ^= ast_random(); + pvt->invite_branch = pvt->branch; + sip_pvt_build_via(pvt); + } else if (newbranch) { + pvt->branch ^= ast_random(); + sip_pvt_build_via(pvt); + } + + /* Check for strict or loose router */ + if (sip_route_is_strict(&pvt->route)) { + is_strict = TRUE; + + if (sip_debug) { + ast_debug(1, "Strict routing enforced for session %s\n", pvt->callid); + } + } + + if (method == SIP_CANCEL) { + uri = SIP_REQUEST_PART(&pvt->initreq, rlpart2); /* Use original URI */ + } else if (method == SIP_ACK) { + /* Use URI from Contact: in 200 OK (if INVITE) (we only have the contacturi on INVITEs) */ + if (!ast_strlen_zero(pvt->okcontacturi)) { + uri = is_strict ? sip_route_first_uri(&pvt->route) : pvt->okcontacturi; + } else { + uri = SIP_REQUEST_PART(&pvt->initreq, rlpart2); + } + } else if (!ast_strlen_zero(pvt->okcontacturi)) { + /* Use for BYE or REINVITE */ + uri = is_strict ? sip_route_first_uri(&pvt->route) : pvt->okcontacturi; + } else if (!ast_strlen_zero(pvt->uri)) { + uri = pvt->uri; + } else { + char *contact; + + /* We have no URI, use To: or From: header as URI (depending on direction) */ + contact = ast_strdupa(sip_request_get_header(&pvt->initreq, is_outbound ? "To" : "From")); + uri = sip_uri_remove_parameters(sip_get_in_brackets(contact)); + } + + sip_request_alloc(req, method, uri); + sip_request_add_header(req, "Via", pvt->via); + + /* 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(&pvt->route) && + !(method == SIP_CANCEL || (method == SIP_ACK && (pvt->invitestate == SIP_INVITE_COMPLETED || pvt->invitestate == SIP_INVITE_CANCELLED)))) { + if (pvt->socket.type != AST_TRANSPORT_UDP && pvt->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(&pvt->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_UPDATE && (pvt->invitestate == SIP_INVITE_PROCEEDING || pvt->invitestate == SIP_INVITE_EARLY_MEDIA)) { + /* Calling sip_pvt_set_destination 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_pvt_set_destination(pvt, sip_route_first_uri(&pvt->route)); + } + + sip_request_add_route(req, &pvt->route, is_strict); + } + + sip_request_add_max_forwards(req, pvt); + + to = ast_strdupa(sip_request_get_header(&pvt->initreq, "To")); + from = ast_strdupa(sip_request_get_header(&pvt->initreq, "From")); + + /* 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_CANCEL) { + /* 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 (is_outbound && !ast_strlen_zero(pvt->theirtag)) { + snprintf(newto, sizeof(newto), "%s;tag=%s", to, pvt->theirtag); + } else if (!is_outbound) { + snprintf(newto, sizeof(newto), "%s;tag=%s", to, pvt->tag); + } else { + snprintf(newto, sizeof(newto), "%s", to); + } + + to = newto; + } + + if (is_outbound) { + sip_request_add_header(req, "From", from); + sip_request_add_header(req, "To", to); + } else { + sip_request_add_header(req, "From", to); + sip_request_add_header(req, "To", from); + } + + /* Do not add Contact for MESSAGE, BYE and Cancel requests */ + if (method != SIP_BYE && method != SIP_CANCEL && method != SIP_MESSAGE) { + sip_request_add_header(req, "Contact", pvt->our_contact); + } + + sip_request_copy_header(req, &pvt->initreq, "Call-ID"); + sip_request_build_header(req, "CSeq", "%u %s", seqno, sip_methods[method].name); + + if (!ast_strlen_zero(sip_config.useragent)) { + sip_request_add_header(req, "User-Agent", sip_config.useragent); + } + + if (!ast_strlen_zero(pvt->url)) { + sip_request_add_header(req, "Access-URL", pvt->url); + ast_string_field_set(pvt, 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 (pvt->stimer && pvt->stimer->active && pvt->stimer->peer_active && (method == SIP_INVITE || method == SIP_UPDATE)) { + sip_request_build_header(req, "Session-Expires", "%d;refresher=%s", pvt->stimer->interval, + pvt->stimer->refresher == SIP_STIMER_REFRESHER_US ? "uac" : "uas"); + + sip_request_build_header(req, "Min-SE", "%d", sip_stimer_get_expiry(pvt, FALSE)); + } + + return 0; +} + +/* Parse a SIP message this function is used both on incoming and outgoing packets */ +int sip_request_parse(struct sip_request *req) +{ + char *data; + ptrdiff_t *header = req->header; + int i = 0; + unsigned int lim = SIP_MAX_HEADERS - 1; + unsigned int skipping_headers = FALSE; + ptrdiff_t current_header = 0; + char *previous_header = ""; + + req->header[0] = 0; + req->headers = -1; /* mark that we are working on the header */ + + for (data = ast_str_buffer(req->data); *data; data++) { + if (*data == '\r') { /* remove \r */ + *data = '\0'; + } else if (*data == '\n') { /* end of this line */ + *data = '\0'; + + current_header = (data + 1) - ast_str_buffer(req->data); + previous_header = ast_str_buffer(req->data) + header[i]; + + if (skipping_headers) { + /* check to see if this line is blank; if so, turn off the skipping flag, so the next line will be + * processed as a body line */ + if (ast_strlen_zero(previous_header)) { + skipping_headers = FALSE; + } + + header[i] = current_header; /* record start of next line */ + continue; + } + + if (sip_debug) { + ast_debug(4, "%7s %2d [%3d]: %s\n", req->headers < 0 ? "Header" : "Body", i, (int) strlen(previous_header), previous_header); + } + + if (ast_strlen_zero(previous_header) && req->headers < 0) { + req->headers = i; /* record number of header lines */ + header = req->line; /* start working on the body */ + + i = 0; + lim = SIP_MAX_LINES - 1; + } else { /* move to next line, check for overflows */ + if (i++ == lim) { + /* if we're processing headers, then skip any remaining headers and move on to processing the + * body, otherwise we're done */ + if (req->headers != -1) { + break; + } else { + req->headers = i; + header = req->line; + + i = 0; + lim = SIP_MAX_LINES - 1; + + skipping_headers = TRUE; + } + } + } + + header[i] = current_header; /* record start of next line */ + } + } + + /* Check for last header or body line without CRLF. The RFC for SDP requires CRLF, but since some devices send without, we'll + * be generous in what we accept. However, if we've already reached the maximum number of lines for portion of the message we + * were parsing, we can't accept any more, so just ignore it. */ + previous_header = ast_str_buffer(req->data) + header[i]; + + if (i < lim && !ast_strlen_zero(previous_header)) { + if (sip_debug) { + ast_debug(4, "%7s %2d [%3d]: %s\n", req->headers < 0 ? "Header" : "Body", + i, (int) strlen(previous_header), previous_header ); + } + + i++; + } + + /* update count of header or body lines */ + if (req->headers >= 0) { /* we are in the body */ + req->lines = i; + } else { /* no body */ + req->headers = i; + req->lines = 0; + + /* req->data->used will be a NULL byte */ + req->line[0] = ast_str_strlen(req->data); + } + + if (*data) { + ast_log(LOG_WARNING, "Too many lines, skipping <%s>\n", data); + } + + /* Split up the first line parts */ + return sip_request_parse_firstline(req); +} + +/* Parse first line of incoming SIP request */ +int sip_request_parse_firstline(struct sip_request *req) +{ + char *rlpart1 = ast_str_buffer(req->data); /* there shouldn't be any */ + char *rlpart2; + + rlpart1 = ast_skip_blanks(rlpart1); + + if (!*rlpart1) { + return -1; + } + + req->rlpart1 = rlpart1 - ast_str_buffer(req->data); /* method or protocol */ + rlpart2 = ast_skip_nonblanks(rlpart1); + + if (*rlpart2) { + *rlpart2++ = '\0'; + } + + /* Get URI or status code */ + rlpart2 = ast_skip_blanks(rlpart2); + + if (!*rlpart2) { + return -1; + } + + ast_trim_blanks(rlpart2); + + if (!strcasecmp(rlpart1, "SIP/2.0")) { /* We have a response */ + if (strlen(rlpart2) < 3) { /* status code is 3 digits */ + return -1; + } + + req->rlpart2 = rlpart2 - ast_str_buffer(req->data); + } else { /* We have a request */ + if (*rlpart2 == '<') { /* The spec says it must not be in <> ! */ + ast_debug(3, "Oops. Bogus uri in <> %s\n", rlpart2); + rlpart2++; + + if (!*rlpart2) { + return -1; + } + } + + req->rlpart2 = rlpart2 - ast_str_buffer(req->data); /* URI */ + rlpart2 = ast_skip_nonblanks(rlpart2); + + if (*rlpart2) { + *rlpart2++ = '\0'; + } + + rlpart2 = ast_skip_blanks(rlpart2); + + if (strcasecmp(rlpart2, "SIP/2.0")) { + ast_debug(3, "Skipping packet - Bad request protocol %s\n", rlpart2); + return -1; + } + } + + return 0; +} + +/* copy SIP request (mostly used to save request for responses) */ +void sip_request_copy(struct sip_request *to_req, const struct sip_request *from_req) +{ + /* XXX this function can encounter memory allocation errors, perhaps it should return a value */ + struct ast_str *data = to_req->data; + struct ast_str *content = to_req->content; + + /* copy the entire request then restore the original data and content members from the dst request */ + *to_req = *from_req; + + to_req->data = data; + to_req->content = content; + + /* copy the data into the dst request */ + if (!to_req->data && !(to_req->data = ast_str_create(ast_str_strlen(from_req->data) + 1))) { + return; + } + + ast_str_copy_string(&to_req->data, from_req->data); + + /* copy the content into the dst request (if it exists) */ + if (from_req->content) { + if (!to_req->content && !(to_req->content = ast_str_create(ast_str_strlen(from_req->content) + 1))) { + return; + } + + ast_str_copy_string(&to_req->content, from_req->content); + } +} + +/* 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_pvt *sip_request_find_pvt(struct sip_request *req, struct ast_sockaddr *addr, const int method) +{ + char totag[128], fromtag[128]; + const char *callid = sip_request_get_header(req, "Call-ID"); + const char *from = sip_request_get_header(req, "From"); + const char *to = sip_request_get_header(req, "To"); + const char *cseq = sip_request_get_header(req, "Cseq"); + struct sip_pvt *pvt; + uint32_t seqno; + + /* Call-ID, to, from and Cseq are required by RFC 3261. (Max-forwards and via too - ignored now). sip_request_get_header + * always returns non-NULL so we must use ast_strlen_zero() */ + if (ast_strlen_zero(callid) || ast_strlen_zero(to) || + ast_strlen_zero(from) || ast_strlen_zero(cseq) || (sscanf(cseq, "%30u", &seqno) != 1)) { + /* RFC 3261 section 24.4.1. Send a 400 Bad Request if the request is malformed. */ + if (method != SIP_RESPONSE && method != SIP_ACK) { + sip_send_response_using_temp(callid, addr, TRUE, method, req, "400 Bad Request"); + } + + return NULL; /* Invalid packet */ + } + + if (sip_config.pedanticsipchecking) { + /* 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 pedanticsipchecking in sip.conf */ + if (sip_request_get_tag(req, "To", totag, sizeof(totag))) { + req->has_to_tag = TRUE; /* Used in sip_handle_request/response */ + } + + sip_request_get_tag(req, "From", fromtag, sizeof(fromtag)); + + ast_debug(5, "Looking for call-id: %s (checking %s) from-tag: %s, to-tag: %s\n", + callid, req->method == SIP_RESPONSE ? "To" : "From", fromtag, totag); + + /* All messages must always have From: tag */ + if (ast_strlen_zero(fromtag)) { + ast_debug(5, "%s request has no from tag, dropping callid: %s from: %s\n", sip_methods[req->method].name, callid, from ); + return NULL; + } + + /* reject requests that must always have a To: tag */ + if (ast_strlen_zero(totag) && (req->method == SIP_ACK || req->method == SIP_BYE || req->method == SIP_INFO)) { + if (req->method != SIP_ACK) { + sip_send_response_using_temp(callid, addr, TRUE, method, req, "481 Call Leg/Transaction Does Not Exist"); + } + + ast_debug(5, "%s must have a to tag. dropping callid: %s from: %s\n", sip_methods[req->method].name, callid, from ); + return NULL; + } + } + + /* match on callid only for REGISTERs */ + if (!sip_config.pedanticsipchecking || req->method == SIP_REGISTER) { + struct sip_pvt tmp_pvt = {.callid = callid}; + + if ((pvt = ao2_t_find(sip_pvts, &tmp_pvt, OBJ_POINTER, "sip_request_find_pvt in dialogs"))) { + /* Found the call */ + return pvt; + } + } else { /* in pedantic mode! -- do the fancy search */ + struct sip_pvt tmp_pvt = {.callid = callid}; + /* if a Outbound forked Request is detected, this pvt will point to the dialog the Request is forking off of. */ + struct sip_pvt *fork_pvt = NULL; + struct sip_match_request_args args = {0}; + struct ao2_iterator *iter; + struct sip_via *via = NULL; + int found; + + args.method = req->method; + args.callid = NULL; /* we already matched this. */ + args.totag = totag; + args.fromtag = fromtag; + args.seqno = seqno; + + /* get via header information. */ + args.ruri = SIP_REQUEST_PART(req, rlpart2); + via = sip_via_parse(sip_request_get_header(req, "Via")); + + if (via) { + args.viasentby = via->sent_by; + args.viabranch = via->branch; + } + + /* determine if this is a Request with authentication credentials. */ + if (!ast_strlen_zero(sip_request_get_header(req, "Authorization")) || + !ast_strlen_zero(sip_request_get_header(req, "Proxy-Authorization"))) { + args.authentication_present = TRUE; + } + + /* if it is a response, get the response code */ + if (req->method == SIP_RESPONSE) { + const char *rlpart2 = ast_skip_blanks(SIP_REQUEST_PART(req, rlpart2)); + int resp; + + if (!ast_strlen_zero(rlpart2) && (sscanf(rlpart2, "%30d", &resp) == 1)) { + args.resp = resp; + } + } + + iter = ao2_t_callback(sip_pvts, OBJ_POINTER | OBJ_MULTIPLE, sip_pvt_cmp, &tmp_pvt, "pedantic ao2_find in dialogs"); + + /* Iterate a list of dialogs already matched by Call-ID */ + while (iter && (pvt = ao2_iterator_next(iter))) { + ao2_lock(pvt); + found = sip_request_match_pvt(pvt, &args); + ao2_unlock(pvt); + + switch (found) { + case SIP_REQUEST_MATCH: + ao2_lock(pvt); + + if (args.method != SIP_RESPONSE && args.authentication_present && strcmp(args.fromtag, pvt->theirtag)) { + /* If we have a request that uses athentication and the fromtag is different from that in the + * original call dialog, update the fromtag in the saved call dialog */ + ast_string_field_set(pvt, theirtag, args.fromtag); + } + + ao2_unlock(pvt); + ao2_iterator_destroy(iter); + + ao2_t_cleanup(fork_pvt, "unref fork_pvt"); + sip_via_free(via); + + return pvt; /* return pvt 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(callid, addr, TRUE, method, req, "482 Loop Detected"); + + ao2_t_ref(pvt, -1, "pvt did not match incoming SIP msg, unref from search."); + ao2_iterator_destroy(iter); + + ao2_t_cleanup(fork_pvt, "unref fork_pvt"); + sip_via_free(via); + + return NULL; + case SIP_REQUEST_FORKED: + ao2_t_cleanup(fork_pvt, "throwing way pvt to fork off of."); + fork_pvt = ao2_t_bump(pvt, "this pvt has a forked request, save this off to copy information into new dialog\n"); + /* fall through */ + case SIP_REQUEST_NOT_MATCH: + default: + ao2_t_ref(pvt, -1, "pvt did not match incoming SIP msg, unref from search"); + break; + } + } + + if (iter) { + ao2_iterator_destroy(iter); + } + + /* Handle any possible forked requests. This must be done only after transaction matching is complete. */ + if (fork_pvt) { + /* XXX right now we only support handling forked INVITE Requests. Any other forked request type must be + * added here. */ + if (fork_pvt->method == SIP_INVITE) { + sip_pvt_init_forked_invite(req, args.totag, fork_pvt, addr); + + ao2_t_cleanup(fork_pvt, "throwing way old forked pvt"); + sip_via_free(via); + + return NULL; + } + + ao2_t_cleanup(fork_pvt, "throwing way pvt to fork off of"); + fork_pvt = NULL; + } + + sip_via_free(via); + } + + /* End of pedantic mode Request/Reponse to Dialog matching. See if the method is capable of creating a dialog */ + if (sip_methods[method].can_create) { + struct sip_pvt *pvt = NULL; + ast_callid logger_callid = 0; + + if (method == SIP_INVITE) { + logger_callid = ast_create_callid(); + } + + /* Ok, time to create a new SIP dialog object, a pvt */ + if (!(pvt = sip_pvt_alloc(callid, addr, TRUE, method, req, 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_pvt_alloc. Without a dialog we can't retransmit and handle ACKs and + * all that, but at least send an error message. */ + sip_send_response_using_temp(callid, addr, TRUE, method, req, "500 Internal Server Error"); + ast_debug(4, "Failed allocating SIP dialog, sending 500 Server internal error and giving up\n"); + } + + return pvt; /* can be NULL */ + } else if (method == SIP_PING) { + /* A method we do not support, let's take it on the volley */ + sip_send_response_using_temp(callid, addr, TRUE, method, req, "501 Method Not Implemented"); + ast_debug(2, "Got a request with unsupported SIP method.\n"); + } else if (method != SIP_RESPONSE && method != SIP_ACK) { + /* This is a request outside of a dialog that we don't know about */ + sip_send_response_using_temp(callid, addr, TRUE, method, req, "481 Call Leg/Transaction Does Not Exist"); + ast_debug(2, "That's odd... Got a request in unknown dialog. Callid %s\n", callid ? callid : ""); + } + + /* We do not respond to responses for dialogs that we don't know about, we just drop the session quickly */ + if (method == SIP_RESPONSE) { + ast_debug(2, "That's odd... Got a response on a call we don't know about. Callid %s\n", callid ? callid : ""); + } + + return NULL; +} + +/* Match a incoming Request/Response to a dialog */ +static int sip_request_match_pvt(struct sip_pvt *pvt, struct sip_match_request_args *arg) +{ + const char *init_ruri = NULL; + + if (pvt->initreq.headers) { + init_ruri = SIP_REQUEST_PART(&pvt->initreq, rlpart2); + } + + /* Match Tags and call-id to Dialog */ + if (!ast_strlen_zero(arg->callid) && strcmp(pvt->callid, arg->callid)) { + /* call-id does not match. */ + return SIP_REQUEST_NOT_MATCH; + } + + if (arg->method == SIP_RESPONSE) { + /* Verify fromtag of response matches the tag we gave them. */ + if (strcmp(arg->fromtag, pvt->tag)) { + /* fromtag from response does not match our tag */ + return SIP_REQUEST_NOT_MATCH; + } + + /* Verify totag 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(pvt->theirtag) && ast_test_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED)) { + if (ast_strlen_zero(arg->totag)) { + /* missing totag when they already gave us one earlier */ + return SIP_REQUEST_NOT_MATCH; + } + + /* compare the totag of response with the tag we have stored for them */ + if (strcmp(arg->totag, pvt->theirtag)) { + /* totag did not match what we had stored for them. */ + char invite_branch[32] = {0}; + + if (pvt->invite_branch) { + snprintf(invite_branch, sizeof(invite_branch), "z9hG4bK%08x", (unsigned) pvt->invite_branch); + } + + /* Forked Request Detection. If this is a 200 OK response and the totags 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 ((arg->resp == 200) && + !ast_strlen_zero(invite_branch) && !ast_strlen_zero(arg->viabranch) && !strcmp(invite_branch, arg->viabranch)) { + return SIP_REQUEST_FORKED; + } + + /* The totag did not match the one we had stored, and this is not a Forked Request. */ + return SIP_REQUEST_NOT_MATCH; + } + } + } else { + /* Verify the fromtag 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 (!arg->authentication_present && strcmp(arg->fromtag, pvt->theirtag)) { + /* their tag does not match the one was have stored for them */ + return SIP_REQUEST_NOT_MATCH; + } + + /* Verify if totag is present in Request, that it matches what we gave them as our tag earlier */ + if (!ast_strlen_zero(arg->totag) && (strcmp(arg->totag, pvt->tag))) { + /* totag 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 totag is established after the dialog is confirmed, this is not necessary. + * + * CRITERIA required for initial transaction matching. + * 1. Is a Request + * 2. Callid and theirtag match (this is done in the dialog matching block) + * 3. totag is NOT present + * 4. CSeq matchs our initial transaction's cseq number + * 5. pvt has init via branch parameter stored */ + + /* Must be a Request, and must not have a totag, 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 (arg->method != SIP_RESPONSE && ast_strlen_zero(arg->totag) && + pvt->init_icseq == arg->seqno && !ast_strlen_zero(pvt->initviabranch) && init_ruri) { + /* 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(arg->viabranch) || strcmp(arg->viabranch, pvt->initviabranch) || + ast_strlen_zero(arg->viasentby) || strcmp(arg->viasentby, pvt->initviasentby)) { + /* At this point, this request does not match this Dialog.if methods are different this is just a mismatch */ + if (pvt->method != arg->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_uri_cmp(init_ruri, arg->ruri)) { + /* 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 totag 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; + } + } /* end of Request Via check */ + + /* 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 totag 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 (arg->method != SIP_RESPONSE && ast_strlen_zero(arg->totag) && + arg->authentication_present && sip_uri_cmp(init_ruri, arg->ruri)) { + /* 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; +} + +const char *sip_request_get_header_full(const struct sip_request *req, const char *name, int *start) +{ + /* Technically you can place arbitrary whitespace both before and after the ':' in a header, although RFC3261 clearly says + * you shouldn't before, and place just one afterwards. If you shouldn't do it, what absolute idiot decided it was a good + * idea to say you can do it, and if you can do it, why in the hell would. you say you shouldn't. */ + int i, len = strlen(name); + + for (i = *start; i < req->headers; i++) { + const char *header = SIP_REQUEST_PART(req, header[i]); + + if (!strncasecmp(header, name, len)) { + /* skip name */ + const char *sep = header + len; + + /* HCOLON has optional SP/HTAB; skip past those */ + while (*sep == ' ' || *sep == '\t') { + sep++; + } + + if (*sep == ':') { + *start = i + 1; + return ast_skip_blanks(sep + 1); + } + } + } + + /* 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 *req, const char *name) +{ + int start = 0; + + return sip_request_get_header_full(req, name, &start); +} + +/* Get tag from packet */ +const char *sip_request_get_tag(const struct sip_request *req, const char *header, char *buf, int buflen) +{ + const char *tag; + + if (!buf) { + return NULL; + } + + buf[0] = '\0'; /* reset the buffer */ + + tag = sip_request_get_header(req, header); + tag = strcasestr(tag, ";tag="); + + if (tag) { + tag += 5; + ast_copy_string(buf, tag, buflen); + + return strsep(&buf, ";"); + } + + return NULL; +} + +/* Get a specific line from the message content */ +char *sip_request_get_content_line(struct sip_request *req, char *name, char delimiter) +{ + int i, len = strlen(name); + const char *line; + + for (i = 0; i < req->lines; i++) { + line = SIP_REQUEST_PART(req, line[i]); + + if (!strncasecmp(line, name, len) && line[len] == delimiter) { + return ast_skip_blanks(line + len + 1); + } + } + + return ""; +} + +/* Get message body content */ +char *sip_request_get_content(struct sip_request *req, int start, int end) +{ + struct ast_str *str; + + if (!(str = ast_str_thread_get(&sip_content_buf, 128))) { + return NULL; + } + + ast_str_reset(str); + + while (start < req->lines && start <= end) { + if (ast_str_append(&str, 0, "%s\n", SIP_REQUEST_PART(req, line[start++])) < 0) { + return NULL; + } + } + + return ast_str_buffer(str); +} + +/* find the content boundary */ +int sip_request_find_boundary(struct sip_request *req, const char *boundary, int start, int *done) +{ + char *line; + int len = strlen(boundary); + + for (*done = FALSE; start < req->lines; start++) { + line = SIP_REQUEST_PART(req, line[start]); + + if (strncmp(line, "--", 2)) { + continue; + } + + if (!strncmp(boundary, line + 2, len)) { + if (!strcmp(line + 2 + len, "--")) { + *done = TRUE; + } else if (strcmp(line + 2 + len, "")) { + continue; + } + + return start; + } + } + + 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 *req) +{ + const char *type = sip_request_get_header(req, "Content-Type"); + char *boundary; + const char *line; + int start, done; + + if (ast_strlen_zero(type) || strncasecmp(type, "multipart/mixed", 15)) { + return type; + } + + if ((boundary = strcasestr(type, ";boundary="))) { + boundary += 10; + } else if ((boundary = strcasestr(type, "; boundary="))) { + boundary += 11; + } else { + return ""; + } + + boundary = ast_strdupa(boundary); + boundary = strsep(&boundary, ";"); + + if ((start = sip_request_find_boundary(req, boundary, 0, &done)) == -1) { + return ""; + } + + for (++start; start < req->lines; start++) { + line = SIP_REQUEST_PART(req, line[start]); + + if (!strncasecmp(line, "Content-Type:", 13)) { + return ast_skip_blanks(line + 13); + } else if (!strcasecmp(line, "")) { + break; + } + } + + return ""; +} + +/* Send SIP Request to the other part of the dialog */ +int sip_request_send(struct sip_pvt *pvt, struct sip_request *req, int reliable, uint32_t seqno) +{ + int res; + + /* If we have an outbound proxy, reset peer address. Only do this once. */ + if (pvt->outboundproxy) { + pvt->sa = pvt->outboundproxy->addr; + } + + sip_request_end_content(req); + + if (!req->lines) { + /* Add extra empty return. sip_request_add_header() reserves 4 bytes so cannot be truncated */ + ast_str_append(&req->data, 0, "\r\n"); + } + + if (sip_debug_test_pvt(pvt)) { + if (ast_test_flag(&pvt->flags[0], SIP_NAT_FORCE_RPORT)) { + ast_verbose("%sTransmitting (NAT) to %s:\n%s\n---\n", reliable ? "Reliably " : "", + ast_sockaddr_stringify(&pvt->recv), ast_str_buffer(req->data)); + } else { + ast_verbose("%sTransmitting (no NAT) to %s:\n%s\n---\n", reliable ? "Reliably " : "", + ast_sockaddr_stringify(&pvt->sa), ast_str_buffer(req->data)); + } + } + + if (pvt->recordhistory) { + struct sip_request tmp_req = {.rlpart1 = 0}; + + sip_request_copy(&tmp_req, req); + sip_request_parse(&tmp_req); + + sip_history_append(pvt, reliable ? "TxReqRel" : "TxReq", "%s / %s - %s", + ast_str_buffer(tmp_req.data), sip_request_get_header(&tmp_req, "CSeq"), sip_methods[tmp_req.method].name); + sip_request_free(&tmp_req); + } + + if (reliable) { + res = sip_packet_send_reliable(pvt, seqno, FALSE, req->data, reliable == SIP_SEND_CRITICAL, req->method); + } else { + res = sip_packet_send(pvt, req->data); + } + + sip_request_free(req); + + return res; +} + +/* Determine whether a SIP message contains an SDP in its body Also updates req->sdp_start and req->sdp_count to indicate where + * the SDP lives in the message body. */ +int sip_request_find_sdp(struct sip_request *req) +{ + const char *content_type; + const char *content_length; + const char *search; + char *boundary; + unsigned int i; + int boundary_quoted = FALSE; + int found_application_sdp = FALSE; + int found_end_of_headers = FALSE; + + content_length = sip_request_get_header(req, "Content-Length"); + + if (!ast_strlen_zero(content_length)) { + int len; + + if (sscanf(content_length, "%30u", &len) != 1) { + ast_log(LOG_WARNING, "Invalid Content-Length: %s\n", content_length); + return FALSE; + } + + /* Content-Length of zero means there can't possibly be an SDP here, even if the Content-Type says there is */ + if (!len) { + return FALSE; + } + } + + content_type = sip_request_get_header(req, "Content-Type"); + + /* if the body contains only SDP, this is easy */ + if (!strncasecmp(content_type, "application/sdp", 15)) { + req->sdp_start = 0; + req->sdp_count = req->lines; + + return req->lines > 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 ((search = strcasestr(content_type, ";boundary="))) { + search += 10; + } else if ((search = strcasestr(content_type, "; boundary="))) { + search += 11; + } else { + return FALSE; + } + + if (ast_strlen_zero(search)) { + return FALSE; + } + + /* If the boundary is quoted with ", remove quote */ + if (*search == '\"') { + search++; + boundary_quoted = TRUE; + } + + /* make a duplicate of the string, with two extra characters at the beginning */ + boundary = ast_strdupa(search - 2); + boundary[0] = boundary[1] = '-'; + + /* Remove final quote */ + if (boundary_quoted) { + boundary[strlen(boundary) - 1] = '\0'; + } + + /* search for the boundary marker, the empty line delimiting headers from sdp part and the end boundry if it exists */ + for (i = 0; i < req->lines; i++) { + const char *line = SIP_REQUEST_PART(req, line[i]); + + if (!strncasecmp(line, boundary, strlen(boundary))){ + if (found_application_sdp && found_end_of_headers) { + req->sdp_count = (i - 1) - req->sdp_start; + return TRUE; + } + + found_application_sdp = FALSE; + } + + if (!strcasecmp(line, "Content-Type: application/sdp")) { + found_application_sdp = TRUE; + } + + if (ast_strlen_zero(line)) { + if (found_application_sdp && !found_end_of_headers) { + req->sdp_start = i; + found_end_of_headers = TRUE; + } + } + } + + if (found_application_sdp && found_end_of_headers) { + req->sdp_count = i - req->sdp_start; + return TRUE; + } + + return FALSE; +} + +/* Add header to SIP message */ +void sip_request_add_header(struct sip_request *req, const char *name, const char *value) +{ + if (req->headers == SIP_MAX_HEADERS) { + ast_log(LOG_WARNING, "Out of SIP header space\n"); + return; + } + + if (req->lines) { + ast_log(LOG_WARNING, "Can't add more headers when lines have been added\n"); + return; + } + + ast_str_append(&req->data, 0, "%s: %s\r\n", name, value); + req->header[req->headers] = ast_str_strlen(req->data); + + req->headers++; +} + +void sip_request_build_header(struct sip_request *req, const char *name, const char *fmt, ...) +{ + va_list ap; + char value[SIP_BUFFER_SIZE * 5]; + + va_start(ap, fmt); + vsnprintf(value, sizeof(value), fmt, ap); + va_end(ap); + + sip_request_add_header(req, name, value); +} + +/* Add date header to SIP message */ +void sip_request_add_date(struct sip_request *req) +{ + char date[SIP_BUFFER_SIZE]; + struct tm tm; + time_t now = time(NULL); + + gmtime_r(&now, &tm); + strftime(date, sizeof(date), "%a, %d %b %Y %T GMT", &tm); + sip_request_add_header(req, "Date", date); +} + +/* Add Expires header to SIP message */ +void sip_request_add_expires(struct sip_request *req, int expires) +{ + sip_request_build_header(req, "Expires", "%d", expires); +} + +/* if pvt->owner exists, it must be locked Add Remote-Party-ID header to SIP message */ +void sip_request_add_rpid(struct sip_request *req, struct sip_pvt *pvt) +{ + struct ast_str *rpid = ast_str_alloca(SIP_BUFFER_SIZE); + char *lid_num, *lid_name; + int lid_pres, lid_source; + const char *fromdomain, *privacy, *screen, *callback_num; + struct ast_party_id connected_id; + char encoded[SIP_BUFFER_SIZE]; + + if (!ast_test_flag(&pvt->flags[0], SIP_SENDRPID) || !pvt->owner) { + return; + } + + lid_source = ast_channel_connected(pvt->owner)->source; + connected_id = ast_channel_connected_effective_id(pvt->owner); + + lid_num = S_COR(connected_id.number.valid, connected_id.number.str, NULL); + lid_name = S_COR(connected_id.name.valid, connected_id.name.str, NULL); + + if (!lid_num && !lid_name) { + return; + } + + lid_pres = ast_party_id_presentation(&connected_id); + + if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED && + ast_test_flag(&pvt->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; + } + + fromdomain = pvt->fromdomain; + + if (!fromdomain || + ((ast_test_flag(&pvt->flags[1], SIP_TRUST_ID_OUTBOUND) == SIP_TRUST_ID_OUTBOUND_YES) && !strcmp("anonymous.invalid", fromdomain))) { + /* 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 */ + fromdomain = ast_sockaddr_stringify_host_remote(&pvt->ourip); + } + + if (!ast_strlen_zero(lid_name)) { + if (ast_test_flag(&pvt->flags[1], SIP_CISCO_USECALLMANAGER)) { + if (!strcmp(lid_name, "Conference") && lid_source == AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE) { + /* Phone will translate \2004 into the localised version of Conference and enable ConfList/ConfDetails support */ + lid_name = "\2004"; + } else if (!strcmp(lid_name, "Park") && lid_source == AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL) { + lid_name = "\2005"; + } + } + + ast_escape_quoted(lid_name, encoded, sizeof(encoded)); + ast_str_append(&rpid, -1, "\"%s\" ", encoded); + } + + ast_str_append(&rpid, -1, "owner, "CISCO_CALLBACK_NUMBER"); + + if (!ast_strlen_zero(callback_num)) { + ast_uri_encode(callback_num, encoded, sizeof(encoded), ast_uri_sip_user); + ast_str_append(&rpid, -1, ";x-cisco-callback-number=%s", encoded); + } + + ast_str_append(&rpid, -1, ">"); + + if (ast_test_flag(&pvt->flags[0], SIP_SENDRPID_PAI)) { + sip_request_add_header(req, "P-Asserted-Identity", ast_str_buffer(rpid)); + + /* trust_id_outbound = yes - Always give full information even if it's private, but append a privacy header + * When private data is included */ + if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + sip_request_add_header(req, "Privacy", "id"); + } + } else { + ast_str_append(&rpid, -1, ";party=%s", pvt->outgoing_call ? "calling" : "called"); + + if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + privacy = "full"; + } else { + privacy = "off"; + } + + if (lid_pres & AST_PRES_USER_NUMBER_PASSED_SCREEN) { + screen = "yes"; + } else { + screen = "no"; + } + + ast_str_append(&rpid, -1, ";privacy=%s;screen=%s", privacy, screen); + sip_request_add_header(req, "Remote-Party-ID", ast_str_buffer(rpid)); + } +} + +void sip_request_add_call_info(struct sip_request *req, struct sip_pvt *pvt) +{ + struct ast_str *call_info = ast_str_alloca(SIP_BUFFER_SIZE); + + if (!ast_test_flag(&pvt->flags[1], SIP_CISCO_USECALLMANAGER)) { + return; + } + + ast_str_set(&call_info, 0, "; orientation=%s; security=", pvt->outgoing_call ? "from" : "to"); + + if (pvt->socket.type & AST_TRANSPORT_TLS) { + if (pvt->srtp) { + 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(&pvt->flags[0], SIP_OUTGOING) && pvt->owner) { + const char *huntpilot = pbx_builtin_getvar_helper(pvt->owner, "CISCO_HUNTPILOT"); + + 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(&pvt->ourip)); + } + } + } + + sip_request_add_header(req, "Call-Info", ast_str_buffer(call_info)); +} + +/* Add "Supported" header to sip message. Since some options may + * be disabled in the config, the sip_pvt must be inspected to determine what + * is supported for this dialog. */ +void sip_request_add_supported(struct sip_request *req, struct sip_pvt *pvt) +{ + struct ast_str *supported = ast_str_alloca(SIP_BUFFER_SIZE); + + ast_str_append(&supported, -1, "replaces"); + + if (sip_stimer_get_mode(pvt, FALSE) != SIP_STIMER_MODE_REFUSE) { + ast_str_append(&supported, -1, ",timer"); + } + + if (ast_test_flag(&pvt->flags[0], SIP_USEPATH)) { + ast_str_append(&supported, -1, ",path"); + } + + if (ast_test_flag(&pvt->flags[1], SIP_CISCO_USECALLMANAGER)) { + ast_str_append(&supported, -1, ",X-cisco-sis-10.0.0"); + } + + sip_request_add_header(req, "Supported", ast_str_buffer(supported)); +} + +void sip_request_add_require(struct sip_request *req) +{ + struct ast_str *require = ast_str_alloca(SIP_BUFFER_SIZE); + int i; + + if (!req->reqsipoptions) { + return; + } + + for (i = 0; sip_options[i].id != SIP_OPT_UNKNOWN; i++) { + if (!(req->reqsipoptions & sip_options[i].id)) { + continue; + } + + if (ast_str_strlen(require) > 0) { + ast_str_append(&require, 0, ","); + } + + ast_str_append(&require, 0, "%s", sip_options[i].name); + } + + if (ast_str_strlen(require) > 0) { + sip_request_add_header(req, "Require", ast_str_buffer(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 *req, struct sip_pvt *pvt) +{ + struct ast_party_id diverting_from; + const char *reason, *quote; + char diversion[SIP_BUFFER_SIZE * 2]; + char encoded_user[SIP_BUFFER_SIZE / 2]; + + /* We skip this entirely if the configuration doesn't allow diversion headers */ + if (!sip_config.send_diversion) { + return; + } + + if (!pvt->owner) { + return; + } + + diverting_from = ast_channel_redirecting_effective_from(pvt->owner); + + if (!diverting_from.number.valid || ast_strlen_zero(diverting_from.number.str)) { + return; + } + + if (sip_config.pedanticsipchecking) { + ast_uri_encode(diverting_from.number.str, encoded_user, sizeof(encoded_user), ast_uri_sip_user); + } else { + ast_copy_string(encoded_user, diverting_from.number.str, sizeof(encoded_user)); + } + + reason = sip_reason2str(&ast_channel_redirecting(pvt->owner)->reason); + /* Reason is either already quoted or it is a token to not need quotes added. */ + quote = *reason == '\"' || sip_is_token(reason) ? "" : "\""; + + /* We at least have a number to place in the Diversion header, which is enough */ + if (!diverting_from.name.valid || ast_strlen_zero(diverting_from.name.str)) { + snprintf(diversion, sizeof(diversion), ";reason=%s%s%s", + encoded_user, ast_sockaddr_stringify_host_remote(&pvt->ourip), quote, reason, quote); + } else { + char escaped_name[SIP_BUFFER_SIZE / 2]; + + if (sip_config.pedanticsipchecking) { + ast_escape_quoted(diverting_from.name.str, escaped_name, sizeof(escaped_name)); + } else { + ast_copy_string(escaped_name, diverting_from.name.str, sizeof(escaped_name)); + } + + snprintf(diversion, sizeof(diversion), "\"%s\" ;reason=%s%s%s", + escaped_name, encoded_user, ast_sockaddr_stringify_host_remote(&pvt->ourip), quote, reason, quote); + } + + if (ast_test_flag(&pvt->flags[1], SIP_CISCO_USECALLMANAGER)) { + int presentation = ast_party_id_presentation(&diverting_from); + int len = strlen(diversion); + const char *privacy; + const char *screen; + + 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"; + } + + snprintf(diversion + len, sizeof(diversion) - len, ";privacy=%s;screen=%s", privacy, screen); + } + + sip_request_add_header(req, "Diversion", diversion); +} + +void sip_request_add_join(struct sip_request *req, struct sip_pvt *pvt) +{ + if (!ast_test_flag(&pvt->flags[2], SIP_RELAY_NEAREND) && !ast_test_flag(&pvt->flags[2], SIP_RELAY_FAREND)) { + return; + } + + if (ast_strlen_zero(pvt->join_callid) || ast_strlen_zero(pvt->join_tag) || ast_strlen_zero(pvt->join_theirtag)) { + return; + } + + sip_request_build_header(req, "Join", "%s;from-tag=%s;to-tag=%s", pvt->join_callid, pvt->join_tag, pvt->join_theirtag); +} + +/* pvt assumed to be locked while calling this function Add 'Max-Forwards' header to SIP message */ +void sip_request_add_max_forwards(struct sip_request *req, struct sip_pvt *pvt) +{ + sip_request_build_header(req, "Max-Forwards", "%d", pvt->maxforwards); +} + +/* Add route header into request per learned route */ +void sip_request_add_route(struct sip_request *req, 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(req, "Route", ast_str_buffer(path)); + } + + ast_free(path); + } +} + +/* Add 'Content-Length' header and content to SIP message */ +void sip_request_end_content(struct sip_request *req) +{ + if (req->lines) { + ast_log(LOG_WARNING, "sip_request_end_content() called on a message that has already been finalized\n"); + return; + } + + sip_request_build_header(req, "Content-Length", "%zu", ast_str_strlen(req->content)); + + if (ast_str_strlen(req->content)) { + ast_str_append(&req->data, 0, "\r\n%s", ast_str_buffer(req->content)); + } + + req->lines = ast_str_strlen(req->content) > 0; +} + +/* Add content (not header) to SIP message */ +void sip_request_add_content(struct sip_request *req, const char *line) +{ + if (req->lines) { + ast_log(LOG_WARNING, "Can't add more content when the content has been finalized\n"); + return; + } + + ast_str_append(&req->content, 0, "%s", line); +} + +void sip_request_add_sdp(struct sip_request *resp, struct sip_pvt *pvt, int oldsdp, int add_audio, int add_t38) +{ + sip_sdp_generate(pvt, resp, oldsdp, add_audio, add_t38); +} + +/* add XML encoded media control with update */ +void sip_request_add_vidupdate(struct sip_request *req) +{ + static const char *xml = "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + sip_request_add_header(req, "Content-Type", "application/media_control+xml"); + sip_request_add_content(req, xml); +} + +/* Add text body to SIP message */ +void sip_request_add_text(struct sip_request *req, struct sip_pvt *pvt) +{ + const char *content_type = NULL; + struct sip_msg_header *header; + + /* Add any additional MESSAGE headers. */ + AST_LIST_TRAVERSE(&pvt->msg_headers, header, next) { + if (!strcasecmp(header->name, "Content-Type")) { + /* Save content type */ + content_type = header->value; + } else { + sip_request_add_header(req, 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(req, "Content-Type", content_type); + sip_request_add_content(req, pvt->msg_body); /* XXX Convert \n's to \r\n's XXX */ +} + +/* Copy one header field from one request to another */ +void sip_request_copy_header(struct sip_request *req, const struct sip_request *orig, const char *name) +{ + const char *value = sip_request_get_header(orig, name); + + if (!ast_strlen_zero(value)) { /* Add what we're responding to */ + sip_request_add_header(req, name, value); + return; + } + + ast_log(LOG_NOTICE, "No header '%s' present to copy\n", name); +} + +/* 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 .*/ +void sip_request_copy_via(struct sip_pvt *pvt, struct sip_request *req, const struct sip_request *orig_req, const char *name) +{ + int copied = 0; + int start = 0; + char newvia[SIP_BUFFER_SIZE * 4]; + + for (;;) { + const char *via = sip_request_get_header_full(orig_req, name, &start); + + if (ast_strlen_zero(via)) { + break; + } + + if (!copied) { /* Only check for empty rport in topmost via header */ + char *first, *rest, *rport; + + /* Only work on first value */ + first = ast_strdupa(via); + + if ((rest = strchr(first, ','))) { + *rest++ = '\0'; + } + + /* Find ;rport; (empty request) */ + rport = strstr(first, ";rport"); + + if (rport && *(rport + 6) == '=') { + rport = NULL; /* We already have a parameter to rport */ + } + + if (ast_test_flag(&pvt->flags[0], SIP_NAT_FORCE_RPORT) || (rport && ast_test_flag(&pvt->flags[0], SIP_NAT_RPORT_PRESENT))) { + if ((rport = strstr(first, ";rport"))) { + /* We need to add received port - rport */ + char *end; + + if ((end = strchr(rport + 1, ';'))) { + memmove(rport, end, strlen(end) + 1); + } else { + *rport = '\0'; + } + } + + /* Add rport to first VIA header if requested */ + snprintf(newvia, sizeof(newvia), "%s;received=%s;rport=%d%s%s", + first, ast_sockaddr_stringify_addr_remote(&pvt->recv), + ast_sockaddr_port(&pvt->recv), rest ? "," : "", rest ? rest : ""); + } else { + /* We should *always* add a received to the topmost via */ + snprintf(newvia, sizeof(newvia), "%s;received=%s%s%s", + first, ast_sockaddr_stringify_addr_remote(&pvt->recv), rest ? "," : "", rest ? rest : ""); + } + + via = newvia; /* the header to copy */ + } /* else add the following via headers untouched */ + + sip_request_add_header(req, name, via); + copied++; + } + + if (!copied) { + ast_log(LOG_NOTICE, "No header '%s' present to copy\n", name); + } +} + +/* 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 dialogue. We reinvite so that the audio stream (RTP) go directly between the SIP UAs. SIP Signalling stays with * in the path. + * If t38version is TRUE, we send T38 SDP for re-invite from audio/video to T38 UDPTL transmission on the channel. + * If oldsdp 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_pvt *pvt, int t38version, int oldsdp) +{ + struct sip_request req; + + if (t38version) { + /* Force media to go through us for T.38. */ + memset(&pvt->redirip, 0, sizeof(pvt->redirip)); + } + + if (pvt->rtp) { + if (t38version) { + /* Silence RTCP while audio RTP is inactive */ + ast_rtp_instance_set_prop(pvt->rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_DISABLED); + + if (pvt->owner) { + /* Prevent audio RTCP reads */ + ast_channel_set_fd(pvt->owner, SIP_AUDIO_RTCP_FD, -1); + } + } else if (ast_sockaddr_isnull(&pvt->redirip)) { + /* Enable RTCP since it will be inactive if we're coming back with this reinvite */ + ast_rtp_instance_set_prop(pvt->rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_STANDARD); + + if (pvt->owner) { + /* Enable audio RTCP reads */ + ast_channel_set_fd(pvt->owner, SIP_AUDIO_RTCP_FD, ast_rtp_instance_fd(pvt->rtp, 1)); + } + } + } + + sip_request_prepare(&req, pvt, ast_test_flag(&pvt->flags[0], SIP_REINVITE_UPDATE) ? SIP_UPDATE : SIP_INVITE, 0, TRUE); + + sip_request_add_header(&req, "Allow", SIP_ALLOWED_METHODS); + sip_request_add_supported(&req, pvt); + + if (ast_test_flag(&pvt->flags[0], SIP_SENDRPID)) { + sip_request_add_rpid(&req, pvt); + } + + if (pvt->recordhistory) { + sip_history_append(pvt, "ReInv", "Re-invite sent"); + } + + sip_pvt_free_offered_media(pvt); + sip_pvt_try_suggested_codec(pvt); + + if (t38version) { + sip_request_add_sdp(&req, pvt, oldsdp, FALSE, TRUE); + } else { + sip_request_add_sdp(&req, pvt, oldsdp, TRUE, FALSE); + } + + /* Use this as the basis */ + sip_pvt_initial_request(pvt, &req); + pvt->lastinvite = pvt->ocseq; + + ast_set_flag(&pvt->flags[0], SIP_OUTGOING); /* Change direction of this dialog */ + pvt->ongoing_reinvite = TRUE; + + return sip_request_send(pvt, &req, SIP_SEND_CRITICAL, pvt->ocseq); +} + +/* Build REFER/INVITE/OPTIONS/NOTIFY/SUBSCRIBE message and transmit it */ +int sip_send_invite(struct sip_pvt *pvt, int method, int sdp, int init, const char *explicit_uri) +{ + struct sip_request req; + struct ast_variable *var; + + if (init) { /* Bump branch even on initial requests */ + pvt->branch ^= ast_random(); + pvt->invite_branch = pvt->branch; + + sip_pvt_build_via(pvt); + } + + if (init > 1) { + sip_request_init(&req, pvt, method, explicit_uri); + } else { + /* If init=1, we should not generate a new branch. If it's 0, we need a new branch. */ + sip_request_prepare(&req, pvt, method, 0, init ? FALSE : TRUE); + } + + if (pvt->options && pvt->options->auth) { + sip_request_add_header(&req, pvt->options->authheader, pvt->options->auth); + } + + sip_request_add_date(&req); + + if (method == SIP_REFER && pvt->refer) { /* Call transfer */ + if (!ast_strlen_zero(pvt->refer->require)) { + sip_request_add_header(&req, "Require", pvt->refer->require); + } + + if (!ast_strlen_zero(pvt->refer->refer_to)) { + sip_request_add_header(&req, "Refer-To", pvt->refer->refer_to); + } + + if (!ast_strlen_zero(pvt->refer->referred_by)) { + sip_request_add_header(&req, "Referred-By", pvt->refer->referred_by); + } + + if (!ast_strlen_zero(pvt->refer->content_id)) { + sip_request_add_header(&req, "Content-Id", pvt->refer->content_id); + } + + if (!ast_strlen_zero(pvt->refer->content_type)) { + sip_request_add_header(&req, "Content-Type", pvt->refer->content_type); + } + + if (ast_str_strlen(pvt->refer->content)) { + sip_request_add_content(&req, ast_str_buffer(pvt->refer->content)); + } + + sip_request_add_expires(&req, pvt->expiry); + } else if (method == SIP_SUBSCRIBE) { + if (pvt->subscribed == SIP_SUBSCRIBED_MESSAGE_SUMMARY) { + sip_request_add_header(&req, "Event", "message-summary"); + sip_request_add_header(&req, "Accept", "application/simple-message-summary"); + } + + sip_request_add_expires(&req, pvt->expiry); + } + + /* 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 (pvt->options && !ast_strlen_zero(pvt->options->replaces)) { + sip_request_add_header(&req, "Replaces", pvt->options->replaces); + sip_request_add_header(&req, "Require", "replaces"); + } + + /* Add Session-Timers related headers if not already there */ + if (ast_strlen_zero(sip_request_get_header(&req, "Session-Expires")) && + (method == SIP_INVITE || method == SIP_UPDATE) && (sip_stimer_get_mode(pvt, FALSE) == SIP_STIMER_MODE_ORIGINATE + || (sip_stimer_get_mode(pvt, FALSE) == SIP_STIMER_MODE_ACCEPT && sip_stimer_get_expiry(pvt, FALSE) != sip_config.min_se))) { + if (!pvt->stimer->interval) { + pvt->stimer->interval = sip_stimer_get_expiry(pvt, TRUE); + } + + pvt->stimer->active = TRUE; + + if (sip_stimer_get_mode(pvt, FALSE) == SIP_STIMER_MODE_ORIGINATE) { + sip_request_build_header(&req, "Session-Expires", "%d", pvt->stimer->interval); + } + + sip_request_build_header(&req, "Min-SE", "%d", sip_stimer_get_expiry(pvt, FALSE)); + } + + sip_request_add_header(&req, "Allow", SIP_ALLOWED_METHODS); + sip_request_add_supported(&req, pvt); + + if (pvt->owner && ((pvt->options && pvt->options->addsipheaders) || (pvt->refer && sip_config.refer_addheaders))) { + struct ast_channel *chan = pvt->owner; /* The owner channel */ + const struct ast_var_t *var; + + ast_channel_lock(chan); + + AST_LIST_TRAVERSE(ast_channel_varshead(chan), var, entries) { + char *name, *value, *end; + + /* SIPADDHEADER: Add SIP header to outgoing call */ + if (strncmp(ast_var_name(var), "SIPADDHEADER", 12)) { + continue; + } + + name = ast_strdupa(ast_var_value(var)); + + /* Strip of the starting " (if it's there) */ + if (*name == '"') { + name++; + } + + if ((value = strchr(name, ':'))) { + *value++ = '\0'; + value = ast_skip_blanks(value); /* Skip white space */ + + /* Strip the ending " (if it's there) */ + end = value + strlen(value) - 1; + + if (*end == '"') { + *end = '\0'; + } + + sip_request_add_header(&req, name, value); + + if (sip_debug) { + ast_debug(1, "Adding SIP Header \"%s\" with content \"%s\"\n", name, value); + } + } + } + + ast_channel_unlock(chan); + } + + if ((method == SIP_INVITE || method == SIP_UPDATE) && ast_test_flag(&pvt->flags[0], SIP_SENDRPID)) { + sip_request_add_rpid(&req, pvt); + } + + if (method == SIP_INVITE) { + sip_request_add_diversion(&req, pvt); + sip_request_add_join(&req, pvt); + sip_request_add_call_info(&req, pvt); + } + + if (sdp) { + sip_pvt_free_offered_media(pvt); + + if (pvt->udptl && pvt->t38.state == SIP_T38_LOCAL_REINVITE) { + ast_debug(1, "T38 is in state %u on channel %s\n", pvt->t38.state, pvt->owner ? ast_channel_name(pvt->owner) : ""); + sip_request_add_sdp(&req, pvt, FALSE, FALSE, TRUE); + } else if (pvt->rtp) { + sip_pvt_try_suggested_codec(pvt); + sip_request_add_sdp(&req, pvt, FALSE, TRUE, FALSE); + } + } else if (method == SIP_NOTIFY && pvt->notify) { + for (var = pvt->notify->headers; var; var = var->next) { + sip_request_add_header(&req, var->name, var->value); + } + + if (ast_str_strlen(pvt->notify->content)) { + sip_request_add_content(&req, ast_str_buffer(pvt->notify->content)); + } + } + + if (!pvt->initreq.headers) { + sip_pvt_initial_request(pvt, &req); + } + + if (method == SIP_INVITE || method == SIP_SUBSCRIBE) { + pvt->lastinvite = pvt->ocseq; + } + + return sip_request_send(pvt, &req, init ? SIP_SEND_CRITICAL : SIP_SEND_RELIABLE, pvt->ocseq); +} + +/* Transmit register to SIP proxy or UA. auth = NULL on the initial registration */ +int sip_send_register(struct sip_registry *registry, int method, const char *auth, const char *authheader) +{ + struct sip_request req; + char from[SIP_BUFFER_SIZE], to[SIP_BUFFER_SIZE], uri[SIP_BUFFER_SIZE]; + struct sip_pvt *pvt; + struct sip_peer *peer = NULL; + int res; + int port = 0; + + /* exit if we are already in process with this registrar ?*/ + if (registry == NULL || (auth == NULL && (registry->state == SIP_REGISTRY_REGSENT || registry->state == SIP_REGISTRY_AUTHSENT))) { + if (registry) { + ast_log(LOG_NOTICE, "Strange, trying to register %s@%s when registration already pending\n", registry->username, registry->hostname); + } + + return 0; + } + + if (registry->dnsmgr == NULL) { + char transport[MAXHOSTNAMELEN]; + + peer = sip_peer_find(registry->hostname, NULL, TRUE, FALSE, 0); + registry->us.ss.ss_family = sip_transport_family(registry->transport); /* Filter address family */ + + /* 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->pvt 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->pvt, peer)) { + ao2_t_ref(registry, +1, "add reg ref for dnsmgr"); + + snprintf(transport, sizeof(transport), "_%s._%s",sip_srv_service(registry->transport), sip_srv_protocol(registry->transport)); /* have to use static sip_get_transport function */ + ast_dnsmgr_lookup_cb(peer ? peer->tohost : registry->hostname, ®istry->us, ®istry->dnsmgr, sip_config.srvlookup ? transport : NULL, sip_registry_dnsmgr_lookup, registry); + + if (!registry->dnsmgr) { + /*dnsmgr refresh disabled, no reference added! */ + ao2_t_ref(registry, -1, "remove reg ref, dnsmgr disabled"); + } + } + + if (peer) { + ao2_t_cleanup(peer, "removing peer ref for dnsmgr_lookup"); + } + } + + if (registry->pvt) { /* We have a registration */ + if (!auth) { + ast_log(LOG_WARNING, "Already have a REGISTER going on to %s@%s?? \n", registry->username, registry->hostname); + return 0; + } + + pvt = ao2_t_bump(registry->pvt, "getting a copy of the r->call dialog in sip_send_register"); + ast_string_field_set(pvt, theirtag, NULL); /* forget their old tag, so we don't match tags when getting response */ + } else { + /* Build callid for registration if we haven't registered before */ + if (!registry->callid_valid) { + sip_registry_build_callid(registry, &sip_internip, sip_config.fromdomain); + sip_registry_build_localtag(registry); + + registry->callid_valid = TRUE; + } + + /* Allocate SIP dialog for registration */ + if (!(pvt = sip_pvt_alloc(registry->callid, NULL, FALSE, SIP_REGISTER, NULL, 0))) { + ast_log(LOG_WARNING, "Unable to allocate registration transaction (memory or socket error)\n"); + return 0; + } + + /* reset tag to consistent value from registry */ + ast_string_field_set(pvt, tag, registry->localtag); + + if (pvt->recordhistory) { + sip_history_append(pvt, "RegistryInit", "Account: %s@%s", registry->username, registry->hostname); + } + + pvt->socket.type = registry->transport; + + /* Use port number specified if no SRV record was found */ + if (!ast_sockaddr_isnull(®istry->us)) { + if (!ast_sockaddr_port(®istry->us) && registry->port) { + ast_sockaddr_set_port(®istry->us, 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->peername, registry->hostname), NULL, TRUE, FALSE, 0))) { + if (ast_sockaddr_cmp(&peer->addr, ®istry->us)) { + sip_peer_dnsmgr_lookup(&peer->addr, ®istry->us, peer); + } + + ao2_t_cleanup(peer, "unref after sip_peer_find"); + peer = NULL; + } + } + + /* Find address to hostname */ + if (sip_pvt_build(pvt, S_OR(registry->peername, registry->hostname), ®istry->us, FALSE)) { + /* we have what we hope is a temporary network error, probably DNS. We need to reschedule a registration try */ + sip_pvt_unlink(pvt); + ao2_t_cleanup(pvt, "unref dialog after unlink_all"); + + pvt = NULL; + + ast_log(LOG_WARNING, "Probably a DNS error for registration to %s@%s, trying REGISTER again (after %d seconds)\n", + registry->username, registry->hostname, sip_config.reg_timeout); + + sip_registry_start_timeout(registry); + registry->attempts++; + + return 0; + } + + /* Copy back Call-ID in case sip_pvt_build changed it */ + ast_string_field_set(registry, callid, pvt->callid); + + if (!registry->dnsmgr && registry->port) { + ast_sockaddr_set_port(&pvt->sa, registry->port); + ast_sockaddr_set_port(&pvt->recv, registry->port); + } + + if (!ast_strlen_zero(pvt->fromdomain)) { + port = pvt->fromdomainport ? pvt->fromdomainport : SIP_STANDARD_PORT; + } else if (!ast_strlen_zero(registry->domain)) { + port = registry->domainport ? registry->domainport : SIP_STANDARD_PORT; + } else { + port = ast_sockaddr_port(&pvt->sa); + } + + ast_set_flag(&pvt->flags[0], SIP_OUTGOING); /* Registration is outgoing call */ + registry->pvt = ao2_t_bump(pvt, "copying dialog into registry registry->pvt"); /* Save pointer to SIP dialog */ + + pvt->registry = ao2_t_bump(registry, "sip_send_register: addref to pvt->registry in sip_send_register"); /* Add pointer to registry in packet */ + + if (!ast_strlen_zero(registry->secret)) { /* Secret (password) */ + ast_string_field_set(pvt, peersecret, registry->secret); + } + + if (!ast_strlen_zero(registry->md5secret)) { + ast_string_field_set(pvt, peermd5secret, registry->md5secret); + } + + /* User name in this realm - if authuser is set, use that, otherwise use username */ + if (!ast_strlen_zero(registry->authuser)) { + ast_string_field_set(pvt, peername, registry->authuser); + ast_string_field_set(pvt, authname, registry->authuser); + } else if (!ast_strlen_zero(registry->username)) { + ast_string_field_set(pvt, peername, registry->username); + ast_string_field_set(pvt, authname, registry->username); + ast_string_field_set(pvt, fromuser, registry->username); + } + + if (!ast_strlen_zero(registry->username)) { + ast_string_field_set(pvt, username, registry->username); + } + + /* Save extension in packet */ + if (!ast_strlen_zero(registry->exten)) { + ast_string_field_set(pvt, exten, registry->exten); + } + + /* Set transport so the correct contact is built */ + sip_socket_set_transport(&pvt->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_pvt_set_ouraddrfor(pvt, &pvt->sa, &pvt->ourip); + } + + /* set up a timeout */ + if (auth == NULL) { + sip_registry_start_timeout(registry); + } + + snprintf(from, sizeof(from), ";tag=%s", registry->username, S_OR(registry->domain, sip_sanitized_host(pvt->tohost)), pvt->tag); + + if (!ast_strlen_zero(pvt->theirtag)) { + snprintf(to, sizeof(to), ";tag=%s", registry->username, S_OR(registry->domain, sip_sanitized_host(pvt->tohost)), pvt->theirtag); + } else { + snprintf(to, sizeof(to), "", registry->username, S_OR(registry->domain, sip_sanitized_host(pvt->tohost))); + } + + /* Fromdomain is what we are registering to, regardless of actual host name from SRV */ + if (port && port != SIP_STANDARD_PORT) { + snprintf(uri, sizeof(uri), "sip:%s:%d", S_OR(pvt->fromdomain, S_OR(registry->domain, sip_sanitized_host(registry->hostname))), port); + } else { + snprintf(uri, sizeof(uri), "sip:%s", S_OR(pvt->fromdomain, S_OR(registry->domain, sip_sanitized_host(registry->hostname)))); + } + + ast_string_field_set(pvt, uri, uri); + pvt->branch ^= ast_random(); + + sip_request_alloc(&req, method, uri); + + sip_pvt_build_via(pvt); + sip_request_add_header(&req, "Via", pvt->via); + + sip_request_add_max_forwards(&req, pvt); + + sip_request_add_header(&req, "From", from); + sip_request_add_header(&req, "To", to); + + sip_request_add_header(&req, "Call-ID", pvt->callid); + sip_request_build_header(&req, "CSeq", "%u %s", ++registry->ocseq, sip_methods[method].name); + + pvt->ocseq = registry->ocseq; + sip_request_add_supported(&req, pvt); + + if (!ast_strlen_zero(sip_config.useragent)) { + sip_request_add_header(&req, "User-Agent", sip_config.useragent); + } + + if (auth) { /* Add auth header */ + sip_request_add_header(&req, authheader, auth); + } else if (!ast_strlen_zero(registry->nonce)) { + char digest[1024]; + + /* We have auth data to reuse, build a digest 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. */ + if (sip_debug) { + ast_debug(1, "Re-using Auth data for %s@%s\n", registry->username, registry->hostname); + } + + ast_string_field_set(pvt, realm, registry->realm); + ast_string_field_set(pvt, nonce, registry->nonce); + ast_string_field_set(pvt, domain, registry->authdomain); + ast_string_field_set(pvt, opaque, registry->opaque); + ast_string_field_set(pvt, qop, registry->qop); + + pvt->noncecount = ++registry->noncecount; + memset(digest, 0, sizeof(digest)); + + if (!sip_pvt_build_reply_digest(pvt, method, digest, sizeof(digest))) { + sip_request_add_header(&req, "Authorization", digest); + } else { + ast_log(LOG_NOTICE, "No authorization available for authentication of registration to %s@%s\n", registry->username, registry->hostname); + } + } + + sip_request_add_expires(&req, registry->expiry); + + sip_pvt_build_contact(pvt, &req, FALSE); + sip_request_add_header(&req, "Contact", pvt->our_contact); + + sip_pvt_initial_request(pvt, &req); + + if (sip_debug_test_pvt(pvt)) { + ast_verb(3, "REGISTER %d headers, %d lines\n", pvt->initreq.headers, pvt->initreq.lines); + } + + registry->state = auth ? SIP_REGISTRY_AUTHSENT : SIP_REGISTRY_REGSENT; + registry->attempts++; /* Another attempt */ + + ast_debug(4, "REGISTER attempt %d to %s@%s\n", registry->attempts, registry->username, registry->hostname); + + res = sip_request_send(pvt, &req, SIP_SEND_CRITICAL, pvt->ocseq); + ao2_t_cleanup(pvt, "p is finished here at the end of sip_send_register"); + + return res; +} + +/* Used in the SUBSCRIBE notification subsystem (RFC3265) */ +int sip_send_notify_with_extension_state(struct sip_pvt *pvt, struct sip_extension_state_data *state_data, int timeout) +{ + struct ast_str *content = ast_str_alloca(4096); + char *from, *to; + struct sip_request req; + const char *event, *subtype; + + /* If the subscription has not yet been accepted do not send a NOTIFY */ + if (!ast_test_flag(&pvt->flags[1], SIP_DIALOG_ESTABLISHED)) { + return 0; + } + + switch (pvt->subscribed) { + case SIP_SUBSCRIBED_DIALOG_INFO_XML: + event = "dialog"; + subtype = "application/dialog-info+xml"; + break; + case SIP_SUBSCRIBED_PIDF_XML: + event = "presence"; + subtype = "application/pidf+xml"; + break; + default: + ast_log(LOG_WARNING, "Unknown pvt->subscribed: %d\n", pvt->subscribed); + return 0; + } + + from = ast_strdupa(sip_request_get_header(&pvt->initreq, "From")); + from = sip_uri_remove_parameters(sip_get_in_brackets(from)); + + if (strncasecmp(from, "sip:", 4) && strncasecmp(from, "sips:", 5)) { + ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", from); + return -1; + } + + to = ast_strdupa(sip_request_get_header(&pvt->initreq, "To")); + to = sip_uri_remove_parameters(sip_get_in_brackets(to)); + + if (strncasecmp(to, "sip:", 4) && strncasecmp(to, "sips:", 5)) { + ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", to); + return -1; + } + + sip_request_prepare(&req, pvt, SIP_NOTIFY, 0, TRUE); + + switch (state_data->exten_state) { + case AST_EXTENSION_DEACTIVATED: + if (timeout) { + sip_request_add_header(&req, "Subscription-State", "terminated;reason=timeout"); + } else { + sip_request_add_header(&req, "Subscription-State", "terminated;reason=probation"); + sip_request_add_header(&req, "Retry-After", "60"); + } + + break; + case AST_EXTENSION_REMOVED: + sip_request_add_header(&req, "Subscription-State", "terminated;reason=noresource"); + break; + default: + if (pvt->expiry > 0 || ast_test_flag(&pvt->flags[2], SIP_SUBSCRIPTIONSTATE_ACTIVE)) { + sip_request_add_header(&req, "Subscription-State", "active"); + } else { /* Expired */ + sip_request_add_header(&req, "Subscription-State", "terminated;reason=timeout"); + } + } + + sip_request_add_header(&req, "Event", event); + sip_request_add_header(&req, "Content-Type", subtype); + + switch (pvt->subscribed) { + case SIP_SUBSCRIBED_DIALOG_INFO_XML: + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n", pvt->dialogver, to); + + if (state_data->exten_state > 0 && (state_data->exten_state & AST_EXTENSION_RINGING) && sip_config.notifyringing) { + /* Twice the extension length should be enough for XML encoding */ + char local_display[AST_MAX_EXTENSION * 2]; + char remote_display[AST_MAX_EXTENSION * 2]; + char *local_target = ast_strdupa(to); + /* It may seem odd to base the remote_target 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 */ + char *remote_target = ast_strdupa(to); + + ast_xml_escape(pvt->exten, local_display, sizeof(local_display)); + ast_xml_escape(pvt->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.notifycid) { + struct ast_channel *chan; + + if ((chan = sip_find_ringing_channel(state_data->device_state_info))) { + char name[AST_MAX_EXTENSION], number[AST_MAX_EXTENSION]; + + ast_channel_lock(chan); + + if (!ast_channel_caller(chan)->id.number.valid || + (ast_channel_caller(chan)->id.number.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED) { + local_target = "anonymous@anonymous.invalid"; + } else { + snprintf(number, sizeof(number), "sip:%s@%s", ast_channel_caller(chan)->id.number.str, pvt->fromdomain); + local_target = ast_strdupa(number); + } + + if (!ast_channel_caller(chan)->id.name.valid || + (ast_channel_caller(chan)->id.name.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED) { + local_display[0] = '\0'; + } else { + ast_xml_escape(ast_channel_caller(chan)->id.name.str, local_display, sizeof(local_display)); + } + + if (!ast_channel_connected(chan)->id.number.valid || + (ast_channel_connected(chan)->id.number.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED) { + remote_target = "anonymous@anonymous.invalid"; + } else { + snprintf(name, sizeof(name), "sip:%s@%s", ast_channel_connected(chan)->id.number.str, pvt->fromdomain); + remote_target = ast_strdupa(name); + } + + if (!ast_channel_connected(chan)->id.name.valid || + (ast_channel_connected(chan)->id.name.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED) { + remote_display[0] = '\0'; + } else { + ast_xml_escape(ast_channel_caller(chan)->id.name.str, remote_display, sizeof(remote_display)); + } + + ast_channel_unlock(chan); + chan = ast_channel_unref(chan); + } + + /* 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.pedanticsipchecking) { + ast_str_append(&content, 0, "\n", + pvt->exten, pvt->callid, pvt->theirtag, pvt->tag); + } else { + ast_str_append(&content, 0, "\n", pvt->exten, pvt->callid); + } + + /* 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_target, remote_target); + ast_str_append(&content, 0, "%s\n\n", + local_display, local_target, local_target); + } else { + ast_str_append(&content, 0, "\n", pvt->exten); + } + } else { + ast_str_append(&content, 0, "\n", pvt->exten); + } + + if (state_data->exten_state & AST_EXTENSION_RINGING && sip_config.notifyringing) { + ast_str_append(&content, 0, "early\n"); + } else if (state_data->exten_state & (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD) || + state_data->presence_state == AST_PRESENCE_DND) { + ast_str_append(&content, 0, "confirmed\n"); + } else { + ast_str_append(&content, 0, "terminated\n"); + } + + if (state_data->exten_state == AST_EXTENSION_ONHOLD) { + ast_str_append(&content, 0, "\n", to); + } + + ast_str_append(&content, 0, "\n\n"); + break; + case SIP_SUBSCRIBED_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_data->exten_state & AST_EXTENSION_RINGING && sip_config.notifyringing) { + ast_str_append(&content, 0, "\n"); + } else if (state_data->exten_state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD)) { + ast_str_append(&content, 0, "\n"); + } else if (state_data->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", pvt->dialogver); + + if (state_data->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(&req, ast_str_buffer(content)); + + pvt->dialogver++; + pvt->pendinginvite = pvt->ocseq; /* Remember that we have a pending NOTIFY in order not to confuse the NOTIFY subsystem */ + + /* Send as SIP_SEND_CRITICAL as we may never receive a 200 OK Response which clears pvt->pendinginvite. + * sip_pvt_extension_state_update() uses pvt->pendinginvite 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(pvt, &req, SIP_SEND_CRITICAL, pvt->ocseq); +} + +/* Notify user of messages waiting in voicemail (RFC3842) */ +int sip_send_notify_with_mwi(struct sip_pvt *pvt, int newmsgs, int oldmsgs, const char *vmexten) +{ + struct sip_request req; + struct ast_str *mwi = ast_str_alloca(SIP_BUFFER_SIZE * 2); + int ourport = pvt->fromdomainport && pvt->fromdomainport != SIP_STANDARD_PORT ? pvt->fromdomainport : ast_sockaddr_port(&pvt->ourip); + const char *domain; + const char *user = S_OR(vmexten, sip_config.vmexten); + + sip_request_init(&req, pvt, SIP_NOTIFY, NULL); + + sip_request_add_header(&req, "Event", "message-summary"); + sip_request_add_header(&req, "Content-Type", "application/simple-message-summary"); + + ast_str_append(&mwi, 0, "Messages-Waiting: %s\r\n", newmsgs ? "yes" : "no"); + + /* domain initialization occurs here because sip_request_init changes ast_sockaddr_stringify string. */ + domain = S_OR(pvt->fromdomain, ast_sockaddr_stringify_host_remote(&pvt->ourip)); + + if (!sip_standard_port(pvt->socket.type, ourport)) { + ast_str_append(&mwi, 0, "Message-Account: sip:%s@%s:%d;transport=%s\r\n", user, domain, ourport, ast_transport2str(pvt->socket.type)); + } else { + ast_str_append(&mwi, 0, "Message-Account: sip:%s@%s;transport=%s\r\n", user, domain, ast_transport2str(pvt->socket.type)); + } + + /* Cisco has a bug in the SIP stack where it can't accept the (0/0) notification. */ + ast_str_append(&mwi, 0, "Voice-Message: %d/%d%s\r\n", + newmsgs, oldmsgs, (ast_test_flag(&pvt->flags[1], SIP_CISCO_USECALLMANAGER) ? "" : " (0/0)")); + + if (pvt->subscribed) { + if (pvt->expiry) { + sip_request_add_header(&req, "Subscription-State", "active"); + } else { /* Expired */ + sip_request_add_header(&req, "Subscription-State", "terminated;reason=timeout"); + } + } + + sip_request_add_content(&req, ast_str_buffer(mwi)); + + if (!pvt->initreq.headers) { + sip_pvt_initial_request(pvt, &req); + } + + return sip_request_send(pvt, &req, SIP_SEND_RELIABLE, pvt->ocseq); +} + +/* Notify a transferring party of the status of transfer (RFC3515) */ +int sip_send_notify_with_sipfrag(struct sip_pvt *pvt, int cseq, char *msg, int terminate) +{ + struct sip_request req; + char sipfrag[SIP_BUFFER_SIZE]; + + sip_request_prepare(&req, pvt, SIP_NOTIFY, 0, TRUE); + sip_request_build_header(&req, "Event", "refer;id=%d", cseq); + + sip_request_add_header(&req, "Subscription-state", terminate ? "terminated;reason=noresource" : "active"); + sip_request_add_header(&req, "Content-Type", "message/sipfrag;version=2.0"); + sip_request_add_header(&req, "Allow", SIP_ALLOWED_METHODS); + + sip_request_add_supported(&req, pvt); + + snprintf(sipfrag, sizeof(sipfrag), "SIP/2.0 %s\r\n", msg); + sip_request_add_content(&req, sipfrag); + + if (!pvt->initreq.headers) { + sip_pvt_initial_request(pvt, &req); + } + + return sip_request_send(pvt, &req, SIP_SEND_RELIABLE, pvt->ocseq); +} + +/* Transmit SIP REFER message (initiated by the transfer() dialplan application this is currently broken as we have no way of + * telling the dialplan engine whether a transfer succeeds or fails. */ +int sip_send_refer(struct sip_pvt *pvt, const char *dest) +{ + char *uri; + int use_tls = FALSE; + + if (sip_debug) { + ast_debug(1, "SIP transfer of %s to %s\n", pvt->callid, dest); + } + + /* Are we transfering an inbound or outbound call ? */ + if (ast_test_flag(&pvt->flags[0], SIP_OUTGOING)) { + uri = ast_strdupa(sip_request_get_header(&pvt->initreq, "To")); + } else { + uri = ast_strdupa(sip_request_get_header(&pvt->initreq, "From")); + } + + uri = sip_get_in_brackets(uri); + ast_string_field_set(pvt, 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"); + } + + /* save in case we get 407 challenge */ + sip_refer_alloc(pvt); + + ast_string_field_build(pvt->refer, refer_to, "", use_tls ? "s" : "", dest); + ast_string_field_set(pvt->refer, referred_by, pvt->our_contact); + + pvt->refer->status = SIP_REFER_SENT; /* Set refer status */ + + return sip_send_invite(pvt, SIP_REFER, FALSE, SIP_INIT_NONE, NULL); +} + +/* Send an out-of-dialog SIP REFER message with content */ +int sip_send_refer_with_content(struct sip_pvt *pvt, const char *content_type, const char *content) +{ + /* Refer is outgoing call */ + ast_set_flag(&pvt->flags[0], SIP_OUTGOING); + sip_refer_alloc(pvt); + pvt->refer->status = SIP_REFER_SENT; + + ast_string_field_set(pvt->refer, require, "norefersub"); + ast_string_field_set(pvt->refer, referred_by, pvt->our_contact); + ast_string_field_build(pvt->refer, content_id, "%08lx", ast_random()); + ast_string_field_build(pvt->refer, refer_to, "cid:%s", pvt->refer->content_id); + ast_string_field_set(pvt->refer, content_type, content_type); + + ast_str_set(&pvt->refer->content, 0, "%s", content); + + sip_pvt_sched_destroy(pvt, SIP_TIMEOUT); + sip_pvt_set_already_gone(pvt); + + return sip_send_invite(pvt, SIP_REFER, FALSE, SIP_INIT_REQUEST, NULL); +} + +/* Send SIP INFO with video update request */ +int sip_send_info_with_vidupdate(struct sip_pvt *pvt) +{ + struct sip_request req; + + sip_request_prepare(&req, pvt, SIP_INFO, 0, TRUE); + sip_request_add_vidupdate(&req); + + return sip_request_send(pvt, &req, SIP_SEND_RELIABLE, pvt->ocseq); +} + +/* Transmit generic SIP request */ +int sip_send_request(struct sip_pvt *pvt, int method, uint32_t seqno, int reliable, int newbranch) +{ + struct sip_request resp; + + sip_request_prepare(&resp, pvt, method, seqno, newbranch); + + if (method == SIP_CANCEL && pvt->answered_elsewhere) { + sip_request_add_header(&resp, "Reason", "SIP;cause=200;text=\"Call completed elsewhere\""); + } + + if (method == SIP_ACK) { + pvt->invitestate = SIP_INVITE_CONFIRMED; + + if (ast_test_flag(&pvt->flags[2], SIP_SDP_ACK)) { + sip_request_add_sdp(&resp, pvt, FALSE, TRUE, FALSE); + } + } + + return sip_request_send(pvt, &resp, reliable, seqno ? seqno : pvt->ocseq); +} + +/* Transmit SIP request, auth added */ +int sip_send_request_with_auth(struct sip_pvt *pvt, int method, uint32_t seqno, int reliable, int newbranch) +{ + struct sip_request resp; + + sip_request_prepare(&resp, pvt, method, seqno, newbranch); + + if (!ast_strlen_zero(pvt->realm)) { + char digest[1024]; + + memset(digest, 0, sizeof(digest)); + + if (!sip_pvt_build_reply_digest(pvt, method, digest, sizeof(digest))) { + char *respheader; + + sip_auth_headers(pvt->options ? pvt->options->auth_type : SIP_PROXY_AUTH, NULL, &respheader); + sip_request_add_header(&resp, respheader, digest); + } else { + ast_log(LOG_WARNING, "No authentication available for call %s\n", pvt->callid); + } + } + + switch (method) { + case SIP_BYE: + /* + * We are hanging up. If we know a cause for that, send it in + * clear text to make debugging easier. + */ + if (ast_test_flag(&pvt->flags[1], SIP_Q850_REASON) && pvt->hangupcause) { + sip_request_build_header(&resp, "Reason", "Q.850;cause=%d", pvt->hangupcause & 0x7f); + } + + break; + case SIP_MESSAGE: + sip_request_add_text(&resp, pvt); + break; + default: + break; + } + + return sip_request_send(pvt, &resp, reliable, seqno ? seqno : pvt->ocseq); +} + +/* Transmit with SIP MESSAGE method */ +int sip_send_message(struct sip_pvt *pvt, int init, int auth) +{ + struct sip_request req; + + if (init) { + sip_request_init(&req, pvt, SIP_MESSAGE, NULL); + sip_pvt_initial_request(pvt, &req); + } else { + sip_request_prepare(&req, pvt, SIP_MESSAGE, 0, TRUE); + } + + if (auth) { + return sip_send_request_with_auth(pvt, SIP_MESSAGE, pvt->ocseq, SIP_SEND_RELIABLE, FALSE); + } else { + sip_request_add_text(&req, pvt); + + return sip_request_send(pvt, &req, SIP_SEND_RELIABLE, pvt->ocseq); + } +} diff -durN asterisk-22.2.0.orig/channels/sip/response.c asterisk-22.2.0/channels/sip/response.c --- asterisk-22.2.0.orig/channels/sip/response.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/response.c 2025-02-18 17:14:46.825902191 +1300 @@ -0,0 +1,717 @@ +/* + * 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/pvt.h" +#include "include/utils.h" +#include "include/config.h" + +static int sip_response_alloc(struct sip_request *resp, const char *msg); +static int __sip_send_response(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, int reliable); +static void sip_response_temp_pvt_free(void *data); + +AST_THREADSTORAGE_CUSTOM(sip_response_temp_pvt_buf, NULL, sip_response_temp_pvt_free); + +/* Initialize SIP response, based on SIP request */ +static int sip_response_alloc(struct sip_request *resp, const char *msg) +{ + /* Initialize a response */ + memset(resp, 0, sizeof(*resp)); + resp->method = SIP_RESPONSE; + + if (!(resp->data = ast_str_create(SIP_MIN_PACKET_SIZE))) { + return -1; + } + + if (!(resp->content = ast_str_create(SIP_MIN_PACKET_SIZE))) { + ast_free(resp->data); + resp->data = NULL; + + return -1; + } + + resp->header[0] = 0; + ast_str_set(&resp->data, 0, "SIP/2.0 %s\r\n", msg); + resp->headers++; + + return 0; +} + +/* Transmit response on SIP request*/ +int sip_response_send(struct sip_pvt *pvt, struct sip_request *resp, int reliable, uint32_t seqno) +{ + int res; + + sip_request_end_content(resp); + + if (!resp->lines) { + /* Add extra empty return. sip_request_add_header() reserves 4 bytes so cannot be truncated */ + ast_str_append(&resp->data, 0, "\r\n"); + } + + if (sip_debug_test_pvt(pvt)) { + const struct ast_sockaddr *dst = sip_pvt_real_dst(pvt); + + ast_verbose("\n<--- %sTransmitting (%s) to %s --->\n%s\n<------------>\n", + reliable ? "Reliably " : "", sip_nat_mode2str(&pvt->flags[0]), ast_sockaddr_stringify(dst), ast_str_buffer(resp->data)); + } + + if (pvt->recordhistory) { + struct sip_request tmp_resp = {.rlpart1 = 0}; + + sip_request_copy(&tmp_resp, resp); + sip_request_parse(&tmp_resp); + + sip_history_append(pvt, reliable ? "TxRespRel" : "TxResp", "%s / %s - %s", + ast_str_buffer(tmp_resp.data), sip_request_get_header(&tmp_resp, "CSeq"), + tmp_resp.method == SIP_RESPONSE ? SIP_REQUEST_PART(&tmp_resp, rlpart2) : sip_methods[tmp_resp.method].name); + + sip_request_free(&tmp_resp); + } + + /* If we are sending a final response to an INVITE, stop retransmitting provisional responses */ + if (pvt->initreq.method == SIP_INVITE && reliable == SIP_SEND_CRITICAL) { + sip_pvt_cancel_provisional_keepalive(pvt); + } + + if (reliable) { + res = sip_packet_send_reliable(pvt, seqno, TRUE, resp->data, reliable == SIP_SEND_CRITICAL, resp->method); + } else { + res = sip_packet_send(pvt, resp->data); + } + + sip_request_free(resp); + + if (res > 0) { + return 0; + } + + return res; +} + +/* Test if this response needs a contact header */ +int sip_response_needs_contact(const char *msg, int method) +{ + /* 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 (method) { + /* 1xx, 2xx, 3xx, 485 */ + case SIP_INVITE: + case SIP_UPDATE: + case SIP_SUBSCRIBE: + case SIP_NOTIFY: + if ((msg[0] >= '1' && msg[0] <= '3') || !strncmp(msg, "485", 3)) { + return TRUE; + } + + break; + /* 2xx, 3xx, 485 */ + case SIP_REGISTER: + case SIP_OPTIONS: + if (msg[0] == '2' || msg[0] == '3' || !strncmp(msg, "485", 3)) { + return TRUE; + } + + break; + /* 3xx, 485 */ + case SIP_BYE: + case SIP_PRACK: + case SIP_MESSAGE: + case SIP_PUBLISH: + if (msg[0] == '3' || !strncmp(msg, "485", 3)) { + return TRUE; + } + + break; + /* 2xx, 3xx, 4xx, 5xx, 6xx */ + case SIP_REFER: + if (msg[0] >= '2' && msg[0] <= '6') { + return TRUE; + } + + break; + /* contact will not be included for everything else */ + case SIP_ACK: + case SIP_CANCEL: + case SIP_INFO: + case SIP_PING: + default: + return FALSE; + } + + return FALSE; +} + +/* Prepare SIP response packet */ +int sip_response_prepare(struct sip_request *resp, struct sip_pvt *pvt, const char *msg, const struct sip_request *req) +{ + char newto[SIP_BUFFER_SIZE]; + const char *to; + + sip_response_alloc(resp, msg); + sip_request_copy_via(pvt, resp, req, "Via"); + + if (msg[0] == '1' || msg[0] == '2') { + const char *rroute; + int start = 0; + + for (;;) { + rroute = sip_request_get_header_full(req, "Record-Route", &start); + + if (ast_strlen_zero(rroute)) { + break; + } + + /* Add what we're responding to */ + sip_request_add_header(resp, "Record-Route", rroute); + } + } + + sip_request_copy_header(resp, req, "From"); + to = sip_request_get_header(req, "To"); + + if (!strcasestr(to, "tag=") && strncmp(msg, "100", 3)) { + /* 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(pvt->theirtag) && ast_test_flag(&pvt->flags[0], SIP_OUTGOING)) { + snprintf(newto, sizeof(newto), "%s;tag=%s", to, pvt->theirtag); + } else if (pvt->tag && !ast_test_flag(&pvt->flags[0], SIP_OUTGOING)) { + snprintf(newto, sizeof(newto), "%s;tag=%s", to, pvt->tag); + } else { + ast_copy_string(newto, to, sizeof(newto)); + } + + to = newto; + } + + sip_request_add_header(resp, "To", to); + sip_request_copy_header(resp, req, "Call-ID"); + sip_request_copy_header(resp, req, "CSeq"); + + if (!ast_strlen_zero(sip_config.useragent)) { + sip_request_add_header(resp, "Server", sip_config.useragent); + } + + sip_request_add_header(resp, "Allow", SIP_ALLOWED_METHODS); + sip_request_add_supported(resp, pvt); + + /* If this is an invite, add Session-Timers related headers if the feature is active for this session */ + if (pvt->method == SIP_INVITE && pvt->stimer && pvt->stimer->active) { + sip_request_build_header(resp, "Session-Expires", "%d;refresher=%s", pvt->stimer->interval, + pvt->stimer->refresher == SIP_STIMER_REFRESHER_US ? "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 (pvt->stimer->refresher == SIP_STIMER_REFRESHER_THEM || + (pvt->stimer->refresher == SIP_STIMER_REFRESHER_US && pvt->stimer->peer_active)) { + resp->reqsipoptions |= SIP_OPT_TIMER; + } + } + + if (msg[0] == '2' && (pvt->method == SIP_SUBSCRIBE || pvt->method == SIP_REGISTER || pvt->method == SIP_PUBLISH)) { + /* For registration responses, we also need expiry and contact info */ + sip_request_add_expires(resp, pvt->expiry); + + if (pvt->expiry) { /* Only add contact if we have an expiry time */ + const char *contact = pvt->method == SIP_SUBSCRIBE ? pvt->our_contact : pvt->fullcontact; + char *brackets = strchr(contact, '<'); + + /* Not when we unregister */ + sip_request_build_header(resp, "Contact", "%s%s%s;expires=%d", + brackets ? "" : "<", contact, brackets ? "" : ">", pvt->expiry); + } + + if (pvt->method == SIP_REGISTER && ast_test_flag(&pvt->flags[0], SIP_USEPATH)) { + sip_request_copy_header(resp, req, "Path"); + } + } else if (!ast_strlen_zero(pvt->our_contact) && sip_response_needs_contact(msg, pvt->method)) { + sip_request_add_header(resp, "Contact", pvt->our_contact); + } + + if (!ast_strlen_zero(pvt->url)) { + sip_request_add_header(resp, "Access-URL", pvt->url); + ast_string_field_set(pvt, 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_pvt_process_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). */ + pvt->sa = pvt->recv; + + if (sip_pvt_process_via(pvt, req)) { + ast_log(LOG_WARNING, "error processing via header, will send response to originating address\n"); + } + + return 0; +} + +/* Base transmit response function */ +static int __sip_send_response(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, int reliable) +{ + struct sip_request resp; + uint32_t seqno = 0; + + if (reliable && (sscanf(sip_request_get_header(req, "CSeq"), "%30u ", &seqno) != 1)) { + ast_log(LOG_WARNING, "Unable to determine sequence number from '%s'\n", sip_request_get_header(req, "CSeq")); + return -1; + } + + sip_response_prepare(&resp, pvt, msg, req); + + if (ast_test_flag(&pvt->flags[0], SIP_SENDRPID) && + ast_test_flag(&pvt->flags[1], SIP_CONNECTLINEUPDATE_PEND) && (!strncmp(msg, "180", 3) || !strncmp(msg, "183", 3))) { + ast_clear_flag(&pvt->flags[1], SIP_CONNECTLINEUPDATE_PEND); + sip_request_add_rpid(&resp, pvt); + } + + if (pvt->method == SIP_INVITE) { + sip_request_add_call_info(&resp, pvt); + } + + /* If we are sending a 302 Redirect we can add a diversion header if the redirect information is set */ + if (!strncmp(msg, "302", 3)) { + sip_request_add_diversion(&resp, pvt); + } + + /* If we are cancelling an incoming invite for some reason, add information about the reason why we are doing this in + * clear text */ + if (pvt->method == SIP_INVITE && msg[0] != '1') { + if (ast_test_flag(&pvt->flags[1], SIP_Q850_REASON)) { + int hangupcause = 0; + int respcode; + + if (pvt->owner && ast_channel_hangupcause(pvt->owner)) { + hangupcause = ast_channel_hangupcause(pvt->owner); + } else if (pvt->hangupcause) { + hangupcause = pvt->hangupcause; + } else if (sscanf(msg, "%30d ", &respcode) == 1) { + hangupcause = sip_hangup2cause(respcode); + } + + if (hangupcause) { + sip_request_build_header(&resp, "Reason", "Q.850;cause=%i", hangupcause & 0x7f); + } + } + } + + return sip_response_send(pvt, &resp, reliable, seqno); +} + +/* Transmit response, no retransmits */ +int sip_send_response(struct sip_pvt *pvt, const char *msg, const struct sip_request *req) +{ + return __sip_send_response(pvt, msg, req, SIP_SEND_UNRELIABLE); +} + +static void sip_response_temp_pvt_free(void *data) +{ + struct sip_pvt *pvt = data; + + ast_string_field_free_memory(pvt); + ast_free(pvt); +} + +/* Transmit response, no retransmits, using a temporary pvt structure */ +int sip_send_response_using_temp(ast_string_field callid, struct ast_sockaddr *addr, int useglobal_nat, const int method, + const struct sip_request *req, const char *msg) +{ + struct sip_pvt *pvt = NULL; + + if (!(pvt = ast_threadstorage_get(&sip_response_temp_pvt_buf, sizeof(*pvt)))) { + ast_log(LOG_ERROR, "Failed to get temporary pvt\n"); + return -1; + } + + if (ast_string_field_init(pvt, 64)) { + ast_free(pvt); + return -1; + } + + /* XXX the 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? */ + + /* Initialize the bare minimum */ + pvt->method = method; + + if (!addr) { + ast_sockaddr_copy(&pvt->ourip, &sip_internip); + } else { + ast_sockaddr_copy(&pvt->sa, addr); + sip_pvt_set_ouraddrfor(pvt, &pvt->sa, &pvt->ourip); + } + + pvt->branch = ast_random(); + sip_pvt_build_our_tag(pvt); + pvt->ocseq = SIP_INITIAL_CSEQ; + + if (useglobal_nat && addr) { + ast_copy_flags(&pvt->flags[0], &sip_config.flags[0], SIP_NAT_FORCE_RPORT); + ast_copy_flags(&pvt->flags[2], &sip_config.flags[2], SIP_NAT_AUTO_RPORT); + + ast_sockaddr_copy(&pvt->recv, addr); + sip_pvt_check_via(pvt, req); + } + + ast_string_field_set(pvt, fromdomain, sip_config.fromdomain); + pvt->fromdomainport = sip_config.fromdomainport; + + sip_pvt_build_via(pvt); + ast_string_field_set(pvt, callid, callid); + + sip_socket_copy_data(&pvt->socket, &req->socket); + + /* Use this temporary pvt structure to send the message */ + __sip_send_response(pvt, msg, req, SIP_SEND_UNRELIABLE); + + /* Free the string fields, but not the pool space */ + ast_string_field_init(pvt, 0); + + return 0; +} + +/* Transmit response, no retransmits */ +int sip_send_response_with_unsupported(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, const char *unsupported) +{ + struct sip_request resp; + + sip_response_prepare(&resp, pvt, msg, req); + sip_request_add_date(&resp); + + sip_request_add_header(&resp, "Unsupported", unsupported); + + return sip_response_send(pvt, &resp, SIP_SEND_UNRELIABLE, 0); +} + +/* Transmit 422 response with Min-SE header (Session-Timers) */ +int sip_send_response_with_min_se(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, int min_se) +{ + struct sip_request resp; + + sip_response_prepare(&resp, pvt, msg, req); + sip_request_add_date(&resp); + + sip_request_build_header(&resp, "Min-SE", "%d", min_se); + + return sip_response_send(pvt, &resp, 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_pvt *pvt, const char *msg, const struct sip_request *req) +{ + return __sip_send_response(pvt, msg, req, req->ignore ? SIP_SEND_UNRELIABLE : SIP_SEND_CRITICAL); +} + +/* Append Retry-After header field when transmitting response */ +int sip_send_response_with_retry_after(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, int retry) +{ + struct sip_request resp; + + sip_response_prepare(&resp, pvt, msg, req); + sip_request_build_header(&resp, "Retry-After", "%d", retry); + + return sip_response_send(pvt, &resp, SIP_SEND_UNRELIABLE, 0); +} + +/* Add date before transmitting response */ +int sip_send_response_with_date(struct sip_pvt *pvt, const char *msg, const struct sip_request *req) +{ + struct sip_request resp; + + sip_response_prepare(&resp, pvt, msg, req); + sip_request_add_date(&resp); + + return sip_response_send(pvt, &resp, SIP_SEND_UNRELIABLE, 0); +} + +/* Append Accept header, content length before transmitting response */ +int sip_send_response_with_allow(struct sip_pvt *pvt, const char *msg, const struct sip_request *req) +{ + struct sip_request resp; + + sip_response_prepare(&resp, pvt, msg, req); + sip_request_add_header(&resp, "Accept", "application/sdp"); + + return sip_response_send(pvt, &resp, SIP_SEND_UNRELIABLE, 0); +} + +/* Append Min-Expires header, content length before transmitting response */ +int sip_send_response_with_min_expires(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, int min_expires) +{ + struct sip_request resp; + + sip_response_prepare(&resp, pvt, msg, req); + sip_request_build_header(&resp, "Min-Expires", "%d", min_expires); + + return sip_response_send(pvt, &resp, SIP_SEND_UNRELIABLE, 0); +} + +/* Respond with authorization request */ +int sip_send_response_with_auth(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, const char *nonce, + int reliable, const char *header, int stale) +{ + struct sip_request resp; + char digest[512]; + uint32_t seqno = 0; + + if (reliable && (sscanf(sip_request_get_header(req, "CSeq"), "%30u ", &seqno) != 1)) { + ast_log(LOG_WARNING, "Unable to determine sequence number from '%s'\n", sip_request_get_header(req, "CSeq")); + return -1; + } + + /* Choose Realm */ + sip_pvt_set_realm(pvt, req); + + /* Stale means that they sent us correct authentication, but + based it on an old challenge (nonce) */ + sip_response_prepare(&resp, pvt, msg, req); + + snprintf(digest, sizeof(digest), "Digest algorithm=MD5, realm=\"%s\", nonce=\"%s\"%s", pvt->realm, nonce, stale ? ", stale=true" : ""); + sip_request_add_header(&resp, header, digest); + + sip_history_append(pvt, "AuthChal", "Auth challenge sent for %s - nc %d", pvt->username, pvt->noncecount); + + return sip_response_send(pvt, &resp, reliable, seqno); +} + +void sip_send_response_with_auth_fail(struct sip_pvt *pvt, struct sip_request *req, int res, int reliable) +{ + const char *msg; + + switch (res) { + case SIP_AUTH_SECRET_FAILED: + case SIP_AUTH_USERNAME_MISMATCH: + case SIP_AUTH_NOT_FOUND: + case SIP_AUTH_UNKNOWN_DOMAIN: + case SIP_AUTH_PEER_NOT_DYNAMIC: + case SIP_AUTH_BAD_TRANSPORT: + case SIP_AUTH_ACL_FAILED: + ast_log(LOG_NOTICE, "Failed to authenticate device %s for %s, code = %d\n", + sip_request_get_header(req, "From"), sip_methods[pvt->method].name, res); + msg = "403 Forbidden"; + + break; + case SIP_AUTH_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(req, "From"), sip_methods[pvt->method].name, res); + msg = "480 Temporarily Unavailable"; + + break; + case SIP_AUTH_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(req, "From"), sip_methods[pvt->method].name, res); + msg = "503 Service Unavailable"; + + break; + case SIP_AUTH_SUCCESSFUL: + case SIP_AUTH_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(req, "From"), sip_methods[pvt->method].name, res); + msg = "503 Service Unavailable"; + } + + if (reliable == SIP_SEND_RELIABLE) { + sip_send_response_reliable(pvt, msg, req); + } else if (reliable == SIP_SEND_UNRELIABLE) { + sip_send_response(pvt, msg, req); + } +} + +/* Send a fake 401 Unauthorized response when the administrator wants to hide the names of local devices from fishers */ +void sip_send_response_with_fake_auth(struct sip_pvt *pvt, struct sip_request *req) +{ + /* We have to emulate EXACTLY what we'd get with a good peer * and a bad password, or else we leak information. */ + const char *msg = "401 Unauthorized"; + const char *reqheader = "Authorization"; + const char *respheader = "WWW-Authenticate"; + char *authtoken; + struct sip_digest_keys keys[] = { + [SIP_DIGEST_NONCE] = {"nonce=", ""}, + [SIP_DIGEST_LAST] = {NULL, NULL} + }; + + authtoken = ast_strdupa(sip_request_get_header(req, reqheader)); + + if (req->ignore && !ast_strlen_zero(pvt->nonce) && ast_strlen_zero(authtoken)) { + /* This is a retransmitted invite/register/etc, don't reconstruct authentication information */ + sip_send_response_with_auth(pvt, msg, req, pvt->nonce, SIP_SEND_UNRELIABLE, respheader, FALSE); + + /* Schedule auto destroy in 32 seconds (according to RFC 3261) */ + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + return; + } else if (ast_strlen_zero(pvt->nonce) || ast_strlen_zero(authtoken)) { + /* We have no auth, so issue challenge and request authentication */ + sip_pvt_build_nonce(pvt, TRUE); + sip_send_response_with_auth(pvt, msg, req, pvt->nonce, SIP_SEND_UNRELIABLE, respheader, FALSE); + + /* Schedule auto destroy in 32 seconds */ + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + return; + } + + sip_parse_digest(authtoken, keys); + + /* Verify nonce from request matches our nonce. If not, send 401 with new nonce */ + if (strcasecmp(pvt->nonce, keys[SIP_DIGEST_NONCE].value)) { + if (!req->ignore) { + sip_pvt_build_nonce(pvt, TRUE); + } + + sip_send_response_with_auth(pvt, msg, req, pvt->nonce, SIP_SEND_UNRELIABLE, respheader, FALSE); + /* Schedule auto destroy in 32 seconds */ + sip_pvt_sched_destroy(pvt, SIP_DEFAULT_TIMEOUT); + } else { + __sip_send_response(pvt, "403 Forbidden", &pvt->initreq, SIP_SEND_UNRELIABLE); + } +} + +/* Respond with an optionind response */ +int sip_send_response_with_optionsind(struct sip_pvt *pvt, const struct sip_request *req) +{ + struct sip_request resp; + + sip_response_prepare(&resp, pvt, "200 OK", req); + + sip_request_add_header(&resp, "Content-Type", "application/x-cisco-remotecc-response+xml"); + sip_request_add_date(&resp); + + sip_request_add_content(&resp, "\n"); + sip_request_add_content(&resp, "\n"); + sip_request_add_content(&resp, "\n"); + sip_request_add_content(&resp, "200\n"); + sip_request_add_content(&resp, "\n"); + sip_request_add_content(&resp, "\n"); + sip_request_add_content(&resp, "\n"); + sip_request_add_content(&resp, "\n"); + sip_request_add_content(&resp, "\n"); + sip_request_add_content(&resp, "\n"); + sip_request_add_content(&resp, "\n"); + sip_request_add_content(&resp, "\n"); + sip_request_add_content(&resp, "\n"); + sip_request_add_content(&resp, "\n"); + sip_request_add_content(&resp, "\n"); + sip_request_add_content(&resp, "\n"); + + return sip_response_send(pvt, &resp, SIP_SEND_UNRELIABLE, 0); +} + +int sip_send_response_provisional(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, int with_sdp) +{ + int res; + + if (!(res = with_sdp ? sip_send_response_with_sdp(pvt, msg, req, SIP_SEND_UNRELIABLE, FALSE, FALSE) : sip_send_response(pvt, msg, req))) { + pvt->last_provisional = msg; + sip_pvt_update_provisional_keepalive(pvt, with_sdp); + } + + return res; +} + +/* Used for 200 OK and 183 early media */ +int sip_send_response_with_t38_sdp(struct sip_pvt *pvt, char *msg, struct sip_request *req, int retrans) +{ + struct sip_request resp; + uint32_t seqno; + + if (sscanf(sip_request_get_header(req, "CSeq"), "%30u ", &seqno) != 1) { + ast_log(LOG_WARNING, "Unable to get seqno from '%s'\n", sip_request_get_header(req, "CSeq")); + return -1; + } + + sip_response_prepare(&resp, pvt, msg, req); + + if (pvt->udptl) { + sip_request_add_sdp(&resp, pvt, 0, 0, 1); + } else { + ast_log(LOG_ERROR, "Can't add SDP to response, since we have no UDPTL session allocated. Call-ID %s\n", pvt->callid); + } + + if (retrans && !pvt->pendinginvite) { + pvt->pendinginvite = seqno; /* Buggy clients sends ACK on RINGING too */ + } + + return sip_response_send(pvt, &resp, retrans, seqno); +} + +/* Used for 200 OK and 183 early media */ +int sip_send_response_with_sdp(struct sip_pvt *pvt, const char *msg, const struct sip_request *req, int reliable, int oldsdp, int rpid) +{ + struct sip_request resp; + uint32_t seqno; + + if (sscanf(sip_request_get_header(req, "CSeq"), "%30u ", &seqno) != 1) { + ast_log(LOG_WARNING, "Unable to get seqno from '%s'\n", sip_request_get_header(req, "CSeq")); + return -1; + } + + sip_response_prepare(&resp, pvt, msg, req); + + if (rpid == TRUE) { + sip_request_add_rpid(&resp, pvt); + sip_request_add_call_info(&resp, pvt); + } + + if (pvt->rtp) { + ast_rtp_instance_activate(pvt->rtp); + sip_pvt_try_suggested_codec(pvt); + + if (pvt->t38.state == SIP_T38_ENABLED) { + sip_request_add_sdp(&resp, pvt, oldsdp, TRUE, TRUE); + } else { + sip_request_add_sdp(&resp, pvt, oldsdp, TRUE, FALSE); + } + } else { + ast_log(LOG_ERROR, "Can't add SDP to response, since we have no RTP session allocated. Call-ID %s\n", pvt->callid); + } + + if (reliable && !pvt->pendinginvite) { + pvt->pendinginvite = seqno; /* Buggy clients sends ACK on RINGING too */ + } + + sip_request_add_require(&resp); + + return sip_response_send(pvt, &resp, reliable, seqno); +} diff -durN asterisk-22.2.0.orig/channels/sip/route.c asterisk-22.2.0/channels/sip/route.c --- asterisk-22.2.0.orig/channels/sip/route.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/route.c 2025-02-18 17:14:46.825902191 +1300 @@ -0,0 +1,205 @@ +/* + * 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/pvt.h" +#include "include/utils.h" + +const char *sip_route_add(struct sip_route *route, const char *uri, size_t len, int inserthead) +{ + struct sip_route_hop *hop; + + if (!uri || len < 1 || uri[0] == '\0') { + return NULL; + } + + /* Expand len to include null terminator */ + len++; + + if (!(hop = ast_calloc(1, sizeof(*hop) + len))) { + return NULL; + } + + ast_copy_string(hop->uri, uri, len); + + if (inserthead) { + AST_LIST_INSERT_HEAD(&route->list, hop, next); + route->type = SIP_ROUTE_INVALIDATED; + } else { + if (sip_route_empty(route)) { + route->type = SIP_ROUTE_INVALIDATED; + } + + AST_LIST_INSERT_TAIL(&route->list, hop, next); + } + + return hop->uri; +} + +void sip_route_parse(struct sip_route *route, const char *header, int inserthead) +{ + if (!route) { + ast_log(LOG_ERROR, "non-null route required"); + return; + } + + while (!ast_strlen_zero(header)) { + const char *hop, *uri, *end; + + if (!(uri = strchr(header, '<'))) { + break; + } + + uri += 1; + + if (!(end = strchr(uri, '>'))) { + break; + } + + end -= 1; + + if ((header = strchr(end + 2, ','))) { + header += 1; + } + + if ((hop = sip_route_add(route, uri, end - uri, inserthead))) { + ast_debug(2, "parsing hop: <%s>\n", hop); + } + } +} + +void sip_route_copy(struct sip_route *dst, const struct sip_route *src) +{ + struct sip_route_hop *hop; + + /* make sure dst is empty */ + sip_route_clear(dst); + + AST_LIST_TRAVERSE(&src->list, hop, next) { + const char *uri = sip_route_add(dst, hop->uri, strlen(hop->uri), FALSE); + + if (uri) { + ast_debug(2, "copied hop: <%s>\n", uri); + } + } + + dst->type = src->type; +} + +void sip_route_clear(struct sip_route *route) +{ + struct sip_route_hop *hop; + + while ((hop = AST_LIST_REMOVE_HEAD(&route->list, next))) { + ast_free(hop); + } + + route->type = SIP_ROUTE_LOOSE; +} + +void sip_route_dump(const struct sip_route *route) +{ + if (sip_route_empty(route)) { + ast_verb(3, "sip_route_dump: no route/path\n"); + } else { + struct sip_route_hop *hop; + + AST_LIST_TRAVERSE(&route->list, hop, next) { + ast_verb(3, "sip_route_dump: route/path hop: <%s>\n", hop->uri); + } + } +} + +struct ast_str *sip_route_list(const struct sip_route *route, int formatcli, int skip) +{ + struct sip_route_hop *hop; + const char *comma; + struct ast_str *path; + int i = 0; + + if ((path = ast_str_create(64)) == NULL) { + return NULL; + } + + comma = formatcli ? ", " : ","; + + AST_LIST_TRAVERSE(&route->list, hop, next) { + if (i >= skip) { + ast_str_append(&path, 0, "%s<%s>", ast_str_strlen(path) ? comma : "", hop->uri); + } + + i++; + } + + if (formatcli && !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->list); +} + +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->list); + int ret = hop && (strstr(hop->uri, ";lr") == NULL); + + route->type = ret ? SIP_ROUTE_STRICT : SIP_ROUTE_LOOSE; + + return ret; + } + + 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->list); + + return hop ? hop->uri : NULL; +} diff -durN asterisk-22.2.0.orig/channels/sip/rtp_glue.c asterisk-22.2.0/channels/sip/rtp_glue.c --- asterisk-22.2.0.orig/channels/sip/rtp_glue.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/rtp_glue.c 2025-02-18 17:14:46.826902165 +1300 @@ -0,0 +1,344 @@ +/* + * 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/pvt.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 *chan, struct ast_format_cap *result) +{ + ast_format_cap_append_from_cap(result, ast_channel_nativeformats(chan), AST_MEDIA_TYPE_UNKNOWN); +} + +int sip_rtp_allow_any_remote(struct ast_channel *chan, struct ast_rtp_instance *instance, const char *rtptype) +{ + struct sip_pvt *pvt; + struct ast_acl_list *acl = NULL; + int res = 1; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + return 0; + } + + ao2_lock(pvt); + + if (pvt->relatedpeer && pvt->relatedpeer->directmediaacl) { + acl = ast_duplicate_acl_list(pvt->relatedpeer->directmediaacl); + } + + ao2_unlock(pvt); + + if (!acl) { + return res; + } + + if (ast_test_flag(&pvt->flags[0], SIP_DIRECT_MEDIA)) { + struct ast_sockaddr us = {{0}}, them = {{0}}; + + ast_rtp_instance_get_requested_target_address(instance, &them); + ast_rtp_instance_get_local_address(instance, &us); + + if (ast_apply_acl(acl, &them, "SIP Direct Media ACL: ") == AST_SENSE_DENY) { + const char *us_addr = ast_strdupa(ast_sockaddr_stringify(&us)); + const char *them_addr = ast_strdupa(ast_sockaddr_stringify(&them)); + + ast_debug(3, "Reinvite %s to %s denied by directmedia ACL on %s\n", rtptype, them_addr, us_addr); + res = 0; + } + } + + ast_free_acl_list(acl); + + return res; +} + +int sip_rtp_allow_remote(struct ast_channel *chan, struct ast_rtp_instance *instance) +{ + return sip_rtp_allow_any_remote(chan, instance, "audio"); +} + +int sip_vrtp_allow_remote(struct ast_channel *chan, struct ast_rtp_instance *instance) +{ + return sip_rtp_allow_any_remote(chan, instance, "video"); +} + +enum ast_rtp_glue_result sip_rtp_get_info(struct ast_channel *chan, struct ast_rtp_instance **instance) +{ + struct sip_pvt *pvt = NULL; + enum ast_rtp_glue_result res = AST_RTP_GLUE_RESULT_LOCAL; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + return AST_RTP_GLUE_RESULT_FORBID; + } + + ao2_lock(pvt); + + if (!pvt->rtp) { + ao2_unlock(pvt); + return AST_RTP_GLUE_RESULT_FORBID; + } + + ao2_ref(pvt->rtp, +1); + *instance = pvt->rtp; + + if (ast_test_flag(&pvt->flags[0], SIP_DIRECT_MEDIA)) { + res = AST_RTP_GLUE_RESULT_REMOTE; + } else if (ast_test_flag(&pvt->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(&pvt->flags[1], SIP_T38SUPPORT)) { + switch (pvt->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 (pvt->srtp) { + res = AST_RTP_GLUE_RESULT_FORBID; + } + + ao2_unlock(pvt); + + return res; +} + +enum ast_rtp_glue_result sip_vrtp_get_info(struct ast_channel *chan, struct ast_rtp_instance **instance) +{ + struct sip_pvt *pvt = NULL; + enum ast_rtp_glue_result res = AST_RTP_GLUE_RESULT_FORBID; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + return AST_RTP_GLUE_RESULT_FORBID; + } + + ao2_lock(pvt); + + if (!pvt->vrtp) { + ao2_unlock(pvt); + return AST_RTP_GLUE_RESULT_FORBID; + } + + ao2_ref(pvt->vrtp, +1); + *instance = pvt->vrtp; + + if (ast_test_flag(&pvt->flags[0], SIP_DIRECT_MEDIA)) { + res = AST_RTP_GLUE_RESULT_REMOTE; + } + + ao2_unlock(pvt); + + return res; +} + +enum ast_rtp_glue_result sip_trtp_get_info(struct ast_channel *chan, struct ast_rtp_instance **instance) +{ + struct sip_pvt *pvt = NULL; + enum ast_rtp_glue_result res = AST_RTP_GLUE_RESULT_FORBID; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + return AST_RTP_GLUE_RESULT_FORBID; + } + + ao2_lock(pvt); + + if (!pvt->trtp) { + ao2_unlock(pvt); + return AST_RTP_GLUE_RESULT_FORBID; + } + + ao2_ref(pvt->trtp, +1); + *instance = pvt->trtp; + + if (ast_test_flag(&pvt->flags[0], SIP_DIRECT_MEDIA)) { + res = AST_RTP_GLUE_RESULT_REMOTE; + } + + ao2_unlock(pvt); + + return res; +} + +int sip_rtp_update_peer(struct ast_channel *chan, struct ast_rtp_instance *instance, struct ast_rtp_instance *vinstance, + struct ast_rtp_instance *tinstance, const struct ast_format_cap *cap, int nat_active) +{ + struct sip_pvt *pvt; + int changed = FALSE; + + if (!(pvt = ast_channel_tech_pvt(chan))) { + return -1; + } + + ao2_lock(pvt); + + if (pvt->owner != chan) { + /* 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(chan)); + ao2_unlock(pvt); + + return 0; + } + + /* Disable early RTP bridge */ + if ((instance || vinstance || tinstance) && !ast_channel_is_bridged(chan) && !sip_config.directrtpsetup) { + ao2_unlock(pvt); + return 0; + } + + if (pvt->alreadygone) { + /* If we're destroyed, don't bother */ + ao2_unlock(pvt); + 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(&pvt->flags[0], SIP_DIRECT_MEDIA_NAT)) { + ao2_unlock(pvt); + return 0; + } + + if (instance) { + changed |= ast_rtp_instance_get_and_cmp_remote_address(instance, &pvt->redirip); + + if (pvt->rtp) { + /* Prevent audio RTCP reads */ + ast_channel_set_fd(chan, SIP_AUDIO_RTCP_FD, -1); + /* Silence RTCP while audio RTP is inactive */ + ast_rtp_instance_set_prop(pvt->rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_DISABLED); + } + } else if (!ast_sockaddr_isnull(&pvt->redirip)) { + memset(&pvt->redirip, 0, sizeof(pvt->redirip)); + changed = TRUE; + } + + if (vinstance) { + changed |= ast_rtp_instance_get_and_cmp_remote_address(vinstance, &pvt->vredirip); + + if (pvt->vrtp) { + /* Prevent video RTCP reads */ + ast_channel_set_fd(chan, SIP_VIDEO_RTCP_FD, -1); + /* Silence RTCP while video RTP is inactive */ + ast_rtp_instance_set_prop(pvt->vrtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_DISABLED); + } + } else if (!ast_sockaddr_isnull(&pvt->vredirip)) { + memset(&pvt->vredirip, 0, sizeof(pvt->vredirip)); + changed = TRUE; + + if (pvt->vrtp) { + /* Enable RTCP since it will be inactive if we're coming back from a reinvite */ + ast_rtp_instance_set_prop(pvt->vrtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_STANDARD); + /* Enable video RTCP reads */ + ast_channel_set_fd(chan, SIP_VIDEO_RTCP_FD, ast_rtp_instance_fd(pvt->vrtp, 1)); + } + } + + if (tinstance) { + changed |= ast_rtp_instance_get_and_cmp_remote_address(tinstance, &pvt->tredirip); + } else if (!ast_sockaddr_isnull(&pvt->tredirip)) { + memset(&pvt->tredirip, 0, sizeof(pvt->tredirip)); + changed = TRUE; + } + + if (cap && ast_format_cap_count(cap) && !ast_format_cap_identical(cap, pvt->redircaps)) { + ast_format_cap_remove_by_type(pvt->redircaps, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(pvt->redircaps, cap, AST_MEDIA_TYPE_UNKNOWN); + + changed = TRUE; + } + + if (ast_test_flag(&pvt->flags[1], SIP_DIRECT_MEDIA_OUTGOING) && !pvt->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(&pvt->flags[1], SIP_DIRECT_MEDIA_OUTGOING); + ao2_unlock(pvt); + + return 0; + } + + if (changed && !ast_test_flag(&pvt->flags[0], SIP_GOTREFER) && !ast_test_flag(&pvt->flags[0], SIP_DEFER_BYE_ON_TRANSFER)) { + if (ast_channel_state(chan) != AST_STATE_UP) { /* We are in early state */ + if (pvt->recordhistory) { + sip_history_append(pvt, "ExtInv", "Initial invite sent with remote bridge proposal."); + } + + ast_debug(1, "Early remote bridge setting SIP '%s' - Sending media to %s\n", + pvt->callid, ast_sockaddr_stringify(instance ? &pvt->redirip : &pvt->ourip)); + } else if (!pvt->pendinginvite) { /* 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", + pvt->callid, ast_sockaddr_stringify(instance ? &pvt->redirip : &pvt->ourip)); + sip_send_reinvite_with_sdp(pvt, FALSE, FALSE); + } else if (!ast_test_flag(&pvt->flags[0], SIP_PENDINGBYE)) { + ast_debug(3, "Deferring reinvite on SIP '%s' - It's audio will be redirected to IP %s\n", + pvt->callid, ast_sockaddr_stringify(instance ? &pvt->redirip : &pvt->ourip)); + /* We have a pending Invite. Send re-invite when we're done with the invite */ + ast_set_flag(&pvt->flags[0], SIP_NEEDREINVITE); + } + } + + /* Reset lastrtprx timer */ + pvt->lastrtprx = pvt->lastrtptx = time(NULL); + ao2_unlock(pvt); + + return 0; +} diff -durN asterisk-22.2.0.orig/channels/sip/sdp.c asterisk-22.2.0/channels/sip/sdp.c --- asterisk-22.2.0.orig/channels/sip/sdp.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/sdp.c 2025-02-18 17:14:46.827902138 +1300 @@ -0,0 +1,2621 @@ +/* + * 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/pvt.h" +#include "include/sdp.h" +#include "include/utils.h" +#include "include/config.h" + +#define SIP_SDP_MAX_RTPMAP_CODECS 32 /* Maximum number of codecs allowed in received SDP */ + +static int sip_sdp_has_media_attr(int start, struct sip_request *req, const char *attr); +static char *sip_sdp_get_crypto_attr(struct ast_sdp_srtp *srtp, int default_taglen_32); +static void sip_sdp_remove_unused_crypto(struct ast_sdp_srtp **srtp); +static const char *sip_sdp_get_iterate(int *start, struct sip_request *req, const char *name); +static char sip_sdp_get_line(int *start, int stop, struct sip_request *req, const char **value); + +static int sip_sdp_parse_o(const char *o, struct sip_pvt *pvt); +static int sip_sdp_parse_c(const char *c, struct ast_sockaddr *addr); +static int sip_sdp_parse_a_sendonly(const char *a, int *sendonly); +static int sip_sdp_parse_crypto(struct sip_pvt *pvt, struct ast_rtp_instance *rtp, struct ast_sdp_srtp **srtp, const char *a); +static int sip_sdp_parse_a_ice(const char *a, struct sip_pvt *pvt, struct ast_rtp_instance *instance, int rtcp_mux_offered); +static int sip_sdp_parse_a_rtcp_mux(const char *a, struct sip_pvt *pvt, int *requested); +static int sip_sdp_parse_a_audio(const char *a, struct sip_pvt *pvt, struct ast_rtp_codecs *newaudiortp, int *audio_codec, int *rtpmap_codecs); +static int sip_sdp_parse_a_video(const char *a, struct sip_pvt *pvt, struct ast_rtp_codecs *newvideortp, int *video_codec, int *rtpmap_codecs); +static int sip_sdp_parse_a_text(const char *a, struct sip_pvt *pvt, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, + int *red_data_pt, int *rtpmap_codecs); +static int sip_sdp_parse_a_image(const char *a, struct sip_pvt *pvt); + +static void sip_sdp_add_audio_codec(const struct sip_pvt *pvt, struct ast_format *format, struct ast_str **m_buf, + struct ast_str **a_buf, int debug, int *min_packet_size, int *max_packet_size); +static void sip_sdp_add_video_codec(const struct sip_pvt *pvt, struct ast_format *format, + struct ast_str **m_buf, struct ast_str **a_buf, int debug, int *min_packet_size); +static void sip_sdp_add_text_codec(const struct sip_pvt *pvt, struct ast_format *format, + struct ast_str **m_buf, struct ast_str **a_buf, int debug, int *min_packet_size); +static void sip_sdp_add_noncodec(const struct sip_pvt *pvt, int format, struct ast_str **m_buf, struct ast_str **a_buf, int debug); +static void sip_sdp_add_ice(struct ast_rtp_instance *instance, struct ast_str **a_buf); + +static int sip_sdp_has_media_attr(int start, struct sip_request *req, const char *attr) +{ + int next = start; + char type; + const char *value; + + /* We don't care about the return result here */ + sip_sdp_get_iterate(&next, req, "m"); + + while ((type = sip_sdp_get_line(&start, next, req, &value)) != '\0') { + if (type == 'a' && !strcasecmp(value, attr)) { + return TRUE; + } + } + + return FALSE; +} + +static char *sip_sdp_get_crypto_attr(struct ast_sdp_srtp *srtp, int default_taglen_32) +{ + struct ast_sdp_srtp *tmp = srtp; + char *a_crypto; + + if (!tmp) { + return NULL; + } + + if (!(a_crypto = ast_strdup(""))) { + return NULL; + } + + do { + char *copy = a_crypto; + const char *orig_crypto = ast_sdp_srtp_get_attrib(tmp, 0, default_taglen_32); + + if (ast_strlen_zero(orig_crypto)) { + ast_free(copy); + return NULL; + } + + if (ast_asprintf(&a_crypto, "%sa=crypto:%s\r\n", copy, orig_crypto) == -1) { + ast_free(copy); + return NULL; + } + + ast_free(copy); + } while ((tmp = AST_LIST_NEXT(tmp, sdp_srtp_list))); + + return a_crypto; +} + +static void sip_sdp_remove_unused_crypto(struct ast_sdp_srtp **srtp) +{ + struct ast_sdp_srtp *tmp; + + if (!*srtp) { + return; + } + + /* Delete all but the first crypto line */ + tmp = AST_LIST_NEXT(*srtp, sdp_srtp_list); + AST_LIST_NEXT(*srtp, sdp_srtp_list) = NULL; + + if (tmp) { + ast_sdp_srtp_destroy(tmp); + } +} + +/* Lookup 'name' in the SDP starting at the 'start' line. Returns the matching line, and 'start' is updated with the next line number. */ +static const char *sip_sdp_get_iterate(int *start, struct sip_request *req, const char *name) +{ + int len = strlen(name); + const char *line; + + while (*start < (req->sdp_start + req->sdp_count)) { + line = SIP_REQUEST_PART(req, line[(*start)++]); + + if (!strncasecmp(line, name, len) && line[len] == '=') { + return ast_skip_blanks(line + len + 1); + } + } + + /* if the line was not found, ensure that *start points past the SDP */ + (*start)++; + + return ""; +} + +/* Fetches the next valid SDP line between the 'start' line (inclusive) and the 'stop' line (exclusive). Returns the type ('a', 'c', + * ...) and matching line in reference 'start' is updated with the next line number. */ +static char sip_sdp_get_line(int *start, int stop, struct sip_request *req, const char **value) +{ + char type = '\0'; + const char *line = NULL; + + if (stop > (req->sdp_start + req->sdp_count)) { + stop = req->sdp_start + req->sdp_count; + } + + while (*start < stop) { + line = SIP_REQUEST_PART(req, line[(*start)++]); + + if (line[1] == '=') { + type = line[0]; + *value = ast_skip_blanks(line + 2); + + break; + } + } + + return type; +} +/* 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_pvt *pvt, struct sip_request *req, int t38action, int is_offer) +{ + int res = 0; + + /* Iterators for SDP parsing */ + int start = req->sdp_start; + int next = start; + int iter = start; + + /* Temporary vars for SDP parsing */ + char type = '\0'; + const char *value = NULL; + const char *m = NULL; /* SDP media offer */ + const char *nextm = NULL; + int len = -1; + struct sip_offered_media *offered_media; + + /* Host information */ + struct ast_sockaddr sessionsa; + struct ast_sockaddr audiosa; + struct ast_sockaddr videosa; + struct ast_sockaddr textsa; + struct ast_sockaddr imagesa; + struct ast_sockaddr *asa = NULL; /* RTP audio destination IP address */ + struct ast_sockaddr *vsa = NULL; /* RTP video destination IP address */ + struct ast_sockaddr *tsa = NULL; /* RTP text destination IP address */ + struct ast_sockaddr *isa = NULL; /* UDPTL image destination IP address */ + int aport = -1; /* RTP audio destination port number */ + int vport = -1; /* RTP video destination port number */ + int tport = -1; /* RTP text destination port number */ + int udptlport = -1; /* UDPTL image destination port number */ + + /* Peer capability is the capability in the SDP, non codec is RFC2833 DTMF (101) */ + struct ast_format_cap *peercapability = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + struct ast_format_cap *vpeercapability = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + struct ast_format_cap *tpeercapability = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + + int peernoncodeccapability = 0, vpeernoncodeccapability = 0, tpeernoncodeccapability = 0; + + struct ast_rtp_codecs newaudiortp = AST_RTP_CODECS_NULL_INIT; + struct ast_rtp_codecs newvideortp = AST_RTP_CODECS_NULL_INIT; + struct ast_rtp_codecs newtextrtp = AST_RTP_CODECS_NULL_INIT; + struct ast_format_cap *newjointcapability = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); /* Negotiated capability */ + struct ast_format_cap *newpeercapability = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + int newnoncodeccapability; + + const char *codecs; + unsigned int codec; + + /* SRTP */ + int secure_audio = FALSE; + int secure_video = FALSE; + + /* RTCP Multiplexing */ + int remote_rtcp_mux_audio = FALSE; + int remote_rtcp_mux_video = FALSE; + + /* Others */ + int sendonly = -1; + int audio_codec = 255; + int video_codec = 255; + int rtpmap_codecs = 0; + int red_data_pt[10]; /* For T.140 RED */ + int red_num_gen = 0; /* For T.140 RED */ + char red_fmtp[100] = "empty"; /* For T.140 RED */ + int debug = sip_debug_test_pvt(pvt); + + struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + struct ast_format *format; + + /* Initial check */ + if (!pvt->rtp) { + ast_log(LOG_ERROR, "Got SDP but have no RTP session allocated.\n"); + res = -1; + + goto cleanup; + } + + if (!peercapability || !vpeercapability || !tpeercapability || !newpeercapability || !newjointcapability) { + res = -1; + goto cleanup; + } + + if (ast_rtp_codecs_payloads_initialize(&newaudiortp) || + ast_rtp_codecs_payloads_initialize(&newvideortp) || + ast_rtp_codecs_payloads_initialize(&newtextrtp)) { + res = -1; + + goto cleanup; + } + + /* Update our last rtprx when we receive an SDP, too */ + pvt->lastrtprx = pvt->lastrtptx = time(NULL); /* XXX why both ? */ + sip_pvt_free_offered_media(pvt); + + /* Scan for the first media stream (m=) line to limit scanning of globals */ + nextm = sip_sdp_get_iterate(&next, req, "m"); + + if (ast_strlen_zero(nextm)) { + ast_log(LOG_WARNING, "Insufficient information for SDP (m= not found)\n"); + res = -1; + + goto cleanup; + } + + /* Scan session level SDP parameters (lines before first media stream) */ + while ((type = sip_sdp_get_line(&iter, next - 1, req, &value)) != '\0') { + int processed = FALSE; + + switch (type) { + case '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(value, pvt)) { + res = pvt->session_modify == FALSE ? 0 : -1; + + goto cleanup; + } + + processed = TRUE; + break; + case 'c': + if (sip_sdp_parse_c(value, &sessionsa)) { + processed = TRUE; + asa = &sessionsa; + vsa = asa; + tsa = asa; + isa = asa; + } + + break; + case 'a': + if (sip_sdp_parse_a_sendonly(value, &sendonly)) { + processed = TRUE; + } else if (sip_sdp_parse_a_audio(value, pvt, &newaudiortp, &audio_codec, &rtpmap_codecs)) { + processed = TRUE; + } else if (sip_sdp_parse_a_video(value, pvt, &newvideortp, &video_codec, &rtpmap_codecs)) { + processed = TRUE; + } else if (sip_sdp_parse_a_text(value, pvt, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &rtpmap_codecs)) { + processed = TRUE; + } else if (sip_sdp_parse_a_image(value, pvt)) { + processed = TRUE; + } + + if (sip_sdp_parse_a_ice(value, pvt, pvt->rtp, 0)) { + processed = TRUE; + } + + if (sip_sdp_parse_a_ice(value, pvt, pvt->vrtp, 0)) { + processed = TRUE; + } + + if (sip_sdp_parse_a_ice(value, pvt, pvt->trtp, 0)) { + processed = TRUE; + } + + break; + } + + ast_debug(3, "Processing session-level SDP %c=%s... %s\n", type, value, (processed == TRUE) ? "OK." : "UNSUPPORTED OR FAILED."); + } + + /* default: novideo and notext set */ + pvt->novideo = TRUE; + pvt->notext = TRUE; + + /* Scan media stream (m=) specific parameters loop */ + while (!ast_strlen_zero(nextm)) { + int audio = FALSE; + int video = FALSE; + int image = FALSE; + int text = FALSE; + int processed_crypto = FALSE; + int rtcp_mux_offered = FALSE; + char protocol[18] = {0}; + unsigned int port; + unsigned int ports; + + ports = 0; + len = -1; + start = next; + m = nextm; + iter = next; + nextm = sip_sdp_get_iterate(&next, req, "m"); + + if (!(offered_media = ast_calloc(1, sizeof(*offered_media)))) { + ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer list\n"); + res = -1; + + goto cleanup; + } + + AST_LIST_INSERT_TAIL(&pvt->offered_media, offered_media, next); + offered_media->type = SIP_SDP_UNKNOWN; + + /* We need to check for this ahead of time */ + rtcp_mux_offered = sip_sdp_has_media_attr(iter, req, "rtcp-mux"); + + /* Check for 'audio' media offer */ + if (pvt->rtp && strncmp(m, "audio ", 6) == 0) { + if ((sscanf(m, "audio %30u/%30u %17s %n", &port, &ports, protocol, &len) == 3 && len > 0) || + (sscanf(m, "audio %30u %17s %n", &port, protocol, &len) == 2 && len > 0)) { + codecs = m + len; + + /* produce zero-port m-line since it may be needed later length is "m=audio 0 " + protocol + " " + codecs + "\r\n\0" */ + if (!(offered_media->decline_m_line = ast_malloc(10 + strlen(protocol) + 1 + strlen(codecs) + 3))) { + ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); + res = -1; + + goto cleanup; + } + + /* guaranteed to be exactly the right length */ + sprintf(offered_media->decline_m_line, "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_pvt_has_offered_media(pvt, SIP_SDP_AUDIO)) { + ast_log(LOG_WARNING, "Declining non-primary audio stream: %s\n", m); + continue; + } + + /* Check number of ports offered for stream */ + if (ports > 1) { + ast_log(LOG_WARNING, "%u ports offered for audio media, not supported by Asterisk. Will try anyway...\n", ports); + } + + if ((!strcmp(protocol, "RTP/SAVPF") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) && !ast_test_flag(&pvt->flags[2], SIP_USE_AVPF)) { + if (req->method != SIP_RESPONSE) { + ast_log(LOG_NOTICE, "Received SAVPF profle in audio offer but AVPF is not enabled, enabling: %s\n", m); + + secure_audio = TRUE; + ast_set_flag(&pvt->flags[2], SIP_USE_AVPF); + } else { + ast_log(LOG_WARNING, "Received SAVPF profle in audio answer but AVPF is not enabled: %s\n", m); + continue; + } + } else if ((!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVP")) && ast_test_flag(&pvt->flags[2], SIP_USE_AVPF)) { + if (req->method != SIP_RESPONSE) { + ast_log(LOG_NOTICE, "Received SAVP profle in audio offer but AVPF is enabled, disabling: %s\n", m); + + secure_audio = TRUE; + ast_clear_flag(&pvt->flags[2], SIP_USE_AVPF); + } else { + ast_log(LOG_WARNING, "Received SAVP profile in audio offer but AVPF is enabled: %s\n", m); + continue; + } + } else if (!strcmp(protocol, "UDP/TLS/RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) { + secure_audio = TRUE; + processed_crypto = TRUE; + + if (pvt->srtp) { + ast_set_flag(pvt->srtp, 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(&pvt->flags[2], SIP_USE_AVPF)) { + if (req->method != SIP_RESPONSE) { + ast_log(LOG_NOTICE, "Received AVPF profile in audio offer but AVPF is not enabled, enabling: %s\n", m); + ast_set_flag(&pvt->flags[2], SIP_USE_AVPF); + } else { + ast_log(LOG_WARNING, "Received AVP profile in audio answer but AVPF is enabled: %s\n", m); + continue; + } + } else if (!strcmp(protocol, "RTP/AVP") && ast_test_flag(&pvt->flags[2], SIP_USE_AVPF)) { + if (req->method != SIP_RESPONSE) { + ast_log(LOG_NOTICE, "Received AVP profile in audio answer but AVPF is enabled, disabling: %s\n", m); + ast_clear_flag(&pvt->flags[2], SIP_USE_AVPF); + } else { + ast_log(LOG_WARNING, "Received AVP profile in audio answer but AVPF is enabled: %s\n", m); + continue; + } + } else if (strcmp(protocol, "RTP/AVP") && strcmp(protocol, "RTP/AVPF")) { + ast_log(LOG_WARNING, "Unknown RTP profile in audio offer: %s\n", m); + continue; + } + + audio = TRUE; + offered_media->type = SIP_SDP_AUDIO; + aport = port; + + /* Scan through the RTP payload types specified in a "m=" line: */ + for (; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) { + if (sscanf(codecs, "%30u%n", &codec, &len) != 1) { + ast_log(LOG_WARNING, "Invalid syntax in RTP audio format list: %s\n", codecs); + res = -1; + + goto cleanup; + } + + if (debug) { + ast_verb(3, "Found RTP audio format %u\n", codec); + } + + ast_rtp_codecs_payloads_set_m_type(&newaudiortp, NULL, codec); + } + } else { + ast_log(LOG_WARNING, "Rejecting audio media offer due to invalid or unsupported syntax: %s\n", m); + res = -1; + + goto cleanup; + } + /* Check for 'video' media offer */ + } else if (pvt->vrtp && strncmp(m, "video ", 6) == 0) { + if ((sscanf(m, "video %30u/%30u %17s %n", &port, &ports, protocol, &len) == 3 && len > 0) || + (sscanf(m, "video %30u %17s %n", &port, protocol, &len) == 2 && len > 0)) { + codecs = m + len; + + /* produce zero-port m-line since it may be needed later length is "m=video 0 " + protocol + " " + codecs + "\r\n\0" */ + if (!(offered_media->decline_m_line = ast_malloc(10 + strlen(protocol) + 1 + strlen(codecs) + 3))) { + ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); + res = -1; + + goto cleanup; + } + + /* guaranteed to be exactly the right length */ + sprintf(offered_media->decline_m_line, "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 by Asterisk. Will try anyway...\n", ports); + } + + if (sip_pvt_has_offered_media(pvt, SIP_SDP_VIDEO)) { + ast_log(LOG_WARNING, "Declining non-primary video stream: %s\n", m); + continue; + } + + if ((!strcmp(protocol, "RTP/SAVPF") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) && !ast_test_flag(&pvt->flags[2], SIP_USE_AVPF)) { + ast_log(LOG_WARNING, "Received SAVPF profle in video offer but AVPF is not enabled: %s\n", m); + continue; + } else if ((!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVP")) && ast_test_flag(&pvt->flags[2], SIP_USE_AVPF)) { + ast_log(LOG_WARNING, "Received SAVP profile in video offer but AVPF is enabled: %s\n", m); + continue; + } else if (!strcmp(protocol, "UDP/TLS/RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) { + secure_video = TRUE; + processed_crypto = TRUE; + + if (pvt->vsrtp || (pvt->vsrtp = ast_sdp_srtp_alloc())) { + ast_set_flag(pvt->vsrtp, 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(&pvt->flags[2], SIP_USE_AVPF)) { + ast_log(LOG_WARNING, "Received AVPF profile in video offer but AVPF is not enabled: %s\n", m); + continue; + } else if (!strcmp(protocol, "RTP/AVP") && ast_test_flag(&pvt->flags[2], SIP_USE_AVPF)) { + ast_log(LOG_WARNING, "Received AVP profile in video offer but AVPF is enabled: %s\n", m); + continue; + } else if (strcmp(protocol, "RTP/AVP") && strcmp(protocol, "RTP/AVPF")) { + ast_log(LOG_WARNING, "Unknown RTP profile in video offer: %s\n", m); + continue; + } + + video = TRUE; + pvt->novideo = FALSE; + offered_media->type = SIP_SDP_VIDEO; + vport = port; + + /* Scan through the RTP payload types specified in a "m=" line: */ + for (; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) { + if (sscanf(codecs, "%30u%n", &codec, &len) != 1) { + ast_log(LOG_WARNING, "Invalid syntax in RTP video format list: %s\n", codecs); + res = -1; + + goto cleanup; + } + + if (debug) { + ast_verb(3, "Found RTP video format %u\n", codec); + } + + ast_rtp_codecs_payloads_set_m_type(&newvideortp, NULL, codec); + } + } else { + ast_log(LOG_WARNING, "Rejecting video media offer due to invalid or unsupported syntax: %s\n", m); + res = -1; + + goto cleanup; + } + /* Check for 'text' media offer */ + } else if (pvt->trtp && strncmp(m, "text ", 5) == 0) { + if ((sscanf(m, "text %30u/%30u %17s %n", &port, &ports, protocol, &len) == 3 && len > 0) || + (sscanf(m, "text %30u %17s %n", &port, protocol, &len) == 2 && len > 0)) { + codecs = m + len; + + /* produce zero-port m-line since it may be needed later length is "m=text 0 " + protocol + " " + codecs + "\r\n\0" */ + if (!(offered_media->decline_m_line = ast_malloc(9 + strlen(protocol) + 1 + strlen(codecs) + 3))) { + ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); + res = -1; + + goto cleanup; + } + + /* guaranteed to be exactly the right length */ + sprintf(offered_media->decline_m_line, "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 by Asterisk. Will try anyway...\n", ports); + } + + if (!strcmp(protocol, "RTP/AVPF") && !ast_test_flag(&pvt->flags[2], SIP_USE_AVPF)) { + ast_log(LOG_WARNING, "Received AVPF profile in text offer but AVPF is not enabled: %s\n", m); + continue; + } else if (!strcmp(protocol, "RTP/AVP") && ast_test_flag(&pvt->flags[2], SIP_USE_AVPF)) { + ast_log(LOG_WARNING, "Received AVP profile in text offer but AVPF is enabled: %s\n", m); + continue; + } else if (strcmp(protocol, "RTP/AVP") && strcmp(protocol, "RTP/AVPF")) { + ast_log(LOG_WARNING, "Unknown RTP profile in text offer: %s\n", m); + continue; + } + + if (sip_pvt_has_offered_media(pvt, SIP_SDP_TEXT)) { + ast_log(LOG_WARNING, "Declining non-primary text stream: %s\n", m); + continue; + } + + text = TRUE; + pvt->notext = FALSE; + offered_media->type = SIP_SDP_TEXT; + tport = port; + + /* Scan through the RTP payload types specified in a "m=" line: */ + for (; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) { + if (sscanf(codecs, "%30u%n", &codec, &len) != 1) { + ast_log(LOG_WARNING, "Invalid syntax in RTP video format list: %s\n", codecs); + res = -1; + + goto cleanup; + } + + if (debug) { + ast_verb(3, "Found RTP text format %u\n", codec); + } + + ast_rtp_codecs_payloads_set_m_type(&newtextrtp, NULL, codec); + } + } else { + ast_log(LOG_WARNING, "Rejecting text stream offer due to invalid or unsupported syntax: %s\n", m); + res = -1; + + goto cleanup; + } + /* Check for 'image' media offer */ + } else if (strncmp(m, "image ", 6) == 0) { + if (((sscanf(m, "image %30u udptl t38%n", &port, &len) == 1 && len > 0) || + (sscanf(m, "image %30u UDPTL t38%n", &port, &len) == 1 && len > 0))) { + /* produce zero-port m-line since it may be needed later length is "m=image 0 udptl t38" + "\r\n\0" */ + if (!(offered_media->decline_m_line = ast_malloc(22))) { + ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); + res = -1; + goto cleanup; + } + + /* guaranteed to be exactly the right length */ + strcpy(offered_media->decline_m_line, "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_pvt_setup_udptl(pvt)) { + ast_log(LOG_WARNING, "Failed to initialize UDPTL, declining image stream\n"); + continue; + } + + if (sip_pvt_has_offered_media(pvt, SIP_SDP_IMAGE)) { + ast_log(LOG_WARNING, "Declining non-primary image stream: %s\n", m); + continue; + } + + image = TRUE; + + if (debug) { + ast_verb(3, "Got T.38 offer in SDP in dialog %s\n", pvt->callid); + } + + offered_media->type = SIP_SDP_IMAGE; + udptlport = port; + + if (pvt->t38.state != SIP_T38_ENABLED) { + memset(&pvt->t38.their_parms, 0, sizeof(pvt->t38.their_parms)); + + /* default EC to none, the remote end should respond with the EC they want to use */ + ast_udptl_set_error_correction_scheme(pvt->udptl, UDPTL_ERROR_CORRECTION_NONE); + } + } else if (sscanf(m, "image %30u %17s t38%n", &port, protocol, &len) == 2 && len > 0) { + ast_log(LOG_WARNING, "Declining image stream due to unsupported transport: %s\n", m); + + /* produce zero-port m-line since this is guaranteed to be declined length is "m=image 0 strlen(protocol) t38" + "\r\n\0" */ + if (!(offered_media->decline_m_line = ast_malloc(10 + strlen(protocol) + 7))) { + ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); + res = -1; + + goto cleanup; + } + + /* guaranteed to be exactly the right length */ + sprintf(offered_media->decline_m_line, "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", m); + res = -1; + + goto cleanup; + } + } else { + char type[20] = {0}; + + if ((sscanf(m, "%19s %30u/%30u %n", type, &port, &ports, &len) == 3 && len > 0) || + (sscanf(m, "%19s %30u %n", type, &port, &len) == 2 && len > 0)) { + /* produce zero-port m-line since it may be needed later length is "m=" + type + " 0 " + remainder + "\r\n\0" */ + if (!(offered_media->decline_m_line = ast_malloc(2 + strlen(type) + 3 + strlen(m + len) + 3))) { + ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); + res = -1; + + goto cleanup; + } + + /* guaranteed to be long enough */ + sprintf(offered_media->decline_m_line, "m=%s 0 %s\r\n", type, m + len); + continue; + } else { + ast_log(LOG_WARNING, "Unsupported top-level media type in offer: %s\n", m); + res = -1; + + goto cleanup; + } + } + + /* Media stream specific parameters */ + while ((type = sip_sdp_get_line(&iter, next - 1, req, &value)) != '\0') { + int processed = FALSE; + + switch (type) { + case 'c': + if (audio) { + if (sip_sdp_parse_c(value, &audiosa)) { + processed = TRUE; + asa = &audiosa; + } + } else if (video) { + if (sip_sdp_parse_c(value, &videosa)) { + processed = TRUE; + vsa = &videosa; + } + } else if (text) { + if (sip_sdp_parse_c(value, &textsa)) { + processed = TRUE; + tsa = &textsa; + } + } else if (image) { + if (sip_sdp_parse_c(value, &imagesa)) { + processed = TRUE; + isa = &imagesa; + } + } + + break; + case 'a': + /* Audio specific scanning */ + if (audio) { + if (sip_sdp_parse_a_ice(value, pvt, pvt->rtp, rtcp_mux_offered)) { + processed = TRUE; + } else if (sip_sdp_parse_a_sendonly(value, &sendonly)) { + processed = TRUE; + } else if (!processed_crypto && sip_sdp_parse_crypto(pvt, pvt->rtp, &pvt->srtp, value)) { + processed_crypto = TRUE; + processed = TRUE; + + if (secure_audio == FALSE) { + ast_log(AST_LOG_NOTICE, "Processed audio crypto attribute without SAVP specified; accepting anyway\n"); + secure_audio = TRUE; + } + } else if (sip_sdp_parse_a_audio(value, pvt, &newaudiortp, &audio_codec, &rtpmap_codecs)) { + processed = TRUE; + } else if (sip_sdp_parse_a_rtcp_mux(value, pvt, &remote_rtcp_mux_audio)) { + processed = TRUE; + } + /* Video specific scanning */ + } else if (video) { + if (sip_sdp_parse_a_ice(value, pvt, pvt->vrtp, rtcp_mux_offered)) { + processed = TRUE; + } else if (!processed_crypto && sip_sdp_parse_crypto(pvt, pvt->vrtp, &pvt->vsrtp, value)) { + processed_crypto = TRUE; + processed = TRUE; + + if (secure_video == FALSE) { + ast_log(AST_LOG_NOTICE, "Processed video crypto attribute without SAVP specified; accepting anyway\n"); + secure_video = TRUE; + } + } else if (sip_sdp_parse_a_video(value, pvt, &newvideortp, &video_codec, &rtpmap_codecs)) { + processed = TRUE; + } else if (sip_sdp_parse_a_rtcp_mux(value, pvt, &remote_rtcp_mux_video)) { + processed = TRUE; + } + /* Text (T.140) specific scanning */ + } else if (text) { + if (sip_sdp_parse_a_ice(value, pvt, pvt->trtp, rtcp_mux_offered)) { + processed = TRUE; + } else if (sip_sdp_parse_a_text(value, pvt, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &rtpmap_codecs)) { + processed = TRUE; + } else if (!processed_crypto && sip_sdp_parse_crypto(pvt, pvt->trtp, &pvt->tsrtp, value)) { + processed_crypto = TRUE; + processed = TRUE; + } + /* Image (T.38 FAX) specific scanning */ + } else if (image) { + if (sip_sdp_parse_a_image(value, pvt)) { + processed = TRUE; + } + } + + break; + } + + ast_debug(3, "Processing media-level (%s) SDP %c=%s... %s\n", + (audio == TRUE)? "audio" : (video == TRUE)? "video" : (text == TRUE)? "text" : "image", + type, value, (processed == TRUE)? "OK." : "UNSUPPORTED OR FAILED."); + } + + /* Ensure crypto lines are provided where necessary */ + if (audio && secure_audio && !processed_crypto) { + ast_log(LOG_WARNING, "Rejecting secure audio stream without encryption details: %s\n", m); + res = -1; + + goto cleanup; + } else if (video && secure_video && !processed_crypto) { + ast_log(LOG_WARNING, "Rejecting secure video stream without encryption details: %s\n", m); + res = -1; + + goto cleanup; + } + } + + /* Sanity checks */ + if (!asa && !vsa && !tsa && !isa) { + ast_log(LOG_WARNING, "Insufficient information in SDP (c=)...\n"); + res = -1; + + goto cleanup; + } + + if (aport == -1 && vport == -1 && tport == -1 && udptlport == -1) { + ast_log(LOG_WARNING, "Failing due to no acceptable offer found\n"); + res = -1; + goto cleanup; + } + + if (pvt->srtp && pvt->udptl && udptlport != -1) { + ast_debug(1, "Terminating SRTP due to T.38 UDPTL\n"); + ast_sdp_srtp_destroy(pvt->srtp); + pvt->srtp = NULL; + } + + if (secure_audio && !(pvt->srtp && ast_test_flag(pvt->srtp, AST_SRTP_CRYPTO_OFFER_OK))) { + ast_log(LOG_WARNING, "Can't provide secure audio requested in SDP offer\n"); + res = -1; + + goto cleanup; + } + + if (!secure_audio && pvt->srtp) { + ast_log(LOG_WARNING, "Failed to receive SDP offer/answer with required SRTP crypto attributes for audio\n"); + res = -1; + + goto cleanup; + } + + if (secure_video && !(pvt->vsrtp && ast_test_flag(pvt->vsrtp, AST_SRTP_CRYPTO_OFFER_OK))) { + ast_log(LOG_WARNING, "Can't provide secure video requested in SDP offer\n"); + res = -1; + + goto cleanup; + } + + if (!pvt->novideo && !secure_video && pvt->vsrtp) { + ast_log(LOG_WARNING, "Failed to receive SDP offer/answer with required SRTP crypto attributes for video\n"); + res = -1; + + goto cleanup; + } + + if (!(secure_audio || secure_video || (pvt->udptl && udptlport != -1)) && ast_test_flag(&pvt->flags[0], SIP_USE_SRTP)) { + ast_log(LOG_WARNING, "Matched device setup to use SRTP, but request was not!\n"); + res = -1; + + goto cleanup; + } + + /* If multiple crypto suites were sent remove all but the first one. sip_sdp_parse_crypto moved the chosen cipher suite to the head of + * the list */ + sip_sdp_remove_unused_crypto(&pvt->srtp); + sip_sdp_remove_unused_crypto(&pvt->vsrtp); + + if (udptlport == -1) { + sip_pvt_change_t38_state(pvt, 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(&newaudiortp, &newaudiortp, NULL); + ast_rtp_codecs_payloads_xover(&newvideortp, &newvideortp, NULL); + ast_rtp_codecs_payloads_xover(&newtextrtp, &newtextrtp, NULL); + } + + /* Now gather all of the codecs that we are asked for: */ + ast_rtp_codecs_payload_formats(&newaudiortp, peercapability, &peernoncodeccapability); + ast_rtp_codecs_payload_formats(&newvideortp, vpeercapability, &vpeernoncodeccapability); + ast_rtp_codecs_payload_formats(&newtextrtp, tpeercapability, &tpeernoncodeccapability); + + ast_format_cap_append_from_cap(newpeercapability, peercapability, AST_MEDIA_TYPE_AUDIO); + ast_format_cap_append_from_cap(newpeercapability, vpeercapability, AST_MEDIA_TYPE_VIDEO); + ast_format_cap_append_from_cap(newpeercapability, tpeercapability, AST_MEDIA_TYPE_TEXT); + + ast_format_cap_get_compatible(pvt->caps, newpeercapability, newjointcapability); + + if (!ast_format_cap_count(newjointcapability) && udptlport == -1) { + ast_log(LOG_NOTICE, "No compatible codecs, not accepting this offer!\n"); + /* Do NOT Change current setting */ + res = -1; + + goto cleanup; + } + + newnoncodeccapability = pvt->noncodeccapability & peernoncodeccapability; + + if (debug) { + /* shame on whoever coded this.... */ + struct ast_str *cap_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + struct ast_str *peer_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + struct ast_str *vpeer_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + struct ast_str *tpeer_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + struct ast_str *joint_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + struct ast_str *noncodec_cap_buf = ast_str_alloca(SIP_BUFFER_SIZE); + struct ast_str *noncodec_peer_buf = ast_str_alloca(SIP_BUFFER_SIZE); + struct ast_str *noncodec_joint_buf = ast_str_alloca(SIP_BUFFER_SIZE); + + ast_verb(3, "Capabilities: us - %s, peer - audio=%s/video=%s/text=%s, combined - %s\n", + ast_format_cap_get_names(pvt->caps, &cap_buf), + ast_format_cap_get_names(peercapability, &peer_buf), + ast_format_cap_get_names(vpeercapability, &vpeer_buf), + ast_format_cap_get_names(tpeercapability, &tpeer_buf), + ast_format_cap_get_names(newjointcapability, &joint_buf)); + + ast_verb(3, "Non-codec capabilities (dtmf): us - %s, peer - %s, combined - %s\n", + ast_rtp_lookup_mime_multiple2(noncodec_cap_buf, NULL, pvt->noncodeccapability, 0, 0), + ast_rtp_lookup_mime_multiple2(noncodec_peer_buf, NULL, peernoncodeccapability, 0, 0), + ast_rtp_lookup_mime_multiple2(noncodec_joint_buf, NULL, newnoncodeccapability, 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 ((aport != -1 || vport != -1 || tport != -1) && ast_format_cap_count(newjointcapability)) { + /* We are now ready to change the sip session and RTP structures with the offered codecs, since they are acceptable */ + unsigned int framing; + + ast_format_cap_remove_by_type(pvt->jointcaps, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(pvt->jointcaps, newjointcapability, AST_MEDIA_TYPE_UNKNOWN); /* Our joint codec profile for this call */ + ast_format_cap_remove_by_type(pvt->peercaps, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append_from_cap(pvt->peercaps, newpeercapability, AST_MEDIA_TYPE_UNKNOWN); /* The other side's capability in latest offer */ + + pvt->jointnoncodeccapability = newnoncodeccapability; /* DTMF capabilities */ + + format = ast_format_cap_get_format(pvt->jointcaps, 0); + framing = ast_format_cap_get_format_framing(pvt->jointcaps, format); + + /* respond with single most preferred joint codec, limiting the other side's choice */ + if (ast_test_flag(&pvt->flags[1], SIP_PREFERRED_CODEC)) { + ast_format_cap_remove_by_type(pvt->jointcaps, AST_MEDIA_TYPE_UNKNOWN); + ast_format_cap_append(pvt->jointcaps, format, framing); + } + + if (!ast_rtp_codecs_get_framing(&newaudiortp)) { + /* Peer did not force us to use a specific framing, so use our own */ + ast_rtp_codecs_set_framing(&newaudiortp, framing); + } + + ao2_ref(format, -1); + } + + /* Setup audio address and port */ + if (pvt->rtp) { + if (asa && aport > 0) { + /* Start ICE negotiation here, only when it is response, and setting that we are conrolling agent, as we are offerer */ + sip_pvt_set_ice_components(pvt, pvt->rtp, remote_rtcp_mux_audio); + + if (req->method == SIP_RESPONSE) { + sip_pvt_start_ice(pvt->rtp, 1); + } + + ast_sockaddr_set_port(asa, aport); + ast_rtp_instance_set_remote_address(pvt->rtp, asa); + + if (debug) { + ast_verb(3, "Peer audio RTP is at port %s\n", ast_sockaddr_stringify(asa)); + } + + ast_rtp_codecs_payloads_copy(&newaudiortp, ast_rtp_instance_get_codecs(pvt->rtp), pvt->rtp); + /* Ensure RTCP is enabled since it may be inactive if we're coming back from a T.38 session */ + sip_pvt_setup_rtcp(pvt, pvt->rtp, SIP_AUDIO_RTCP_FD, remote_rtcp_mux_audio); + + if (ast_test_flag(&pvt->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { + ast_clear_flag(&pvt->flags[0], SIP_DTMF); + + if (newnoncodeccapability & AST_RTP_DTMF) { + /* XXX Would it be reasonable to drop the DSP at this point? XXX */ + ast_set_flag(&pvt->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(pvt->rtp, AST_RTP_PROPERTY_DTMF, 1); + ast_rtp_instance_set_prop(pvt->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&pvt->flags[0], SIP_RFC2833_COMPENSATE)); + } else { + ast_set_flag(&pvt->flags[0], SIP_DTMF_INBAND); + } + } + } else if (udptlport > 0) { + if (debug) { + ast_verb(3, "Got T.38 Re-invite without audio. Keeping RTP active during T.38 session.\n"); + } + + /* Force media to go through us for T.38. */ + memset(&pvt->redirip, 0, sizeof(pvt->redirip)); + + /* Prevent audio RTCP reads */ + if (pvt->owner) { + ast_channel_set_fd(pvt->owner, SIP_AUDIO_RTCP_FD, -1); + } + + /* Silence RTCP while audio RTP is inactive */ + ast_rtp_instance_set_prop(pvt->rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_DISABLED); + } else { + ast_rtp_instance_stop(pvt->rtp); + + if (debug) { + ast_verb(3, "Peer doesn't provide audio\n"); + } + } + } + + /* Setup video address and port */ + if (pvt->vrtp) { + if (vsa && vport > 0) { + sip_pvt_set_ice_components(pvt, pvt->vrtp, remote_rtcp_mux_video); + sip_pvt_start_ice(pvt->vrtp, (req->method != SIP_RESPONSE) ? 0 : 1); + + ast_sockaddr_set_port(vsa, vport); + ast_rtp_instance_set_remote_address(pvt->vrtp, vsa); + + if (debug) { + ast_verb(3, "Peer video RTP is at port %s\n", + ast_sockaddr_stringify(vsa)); + } + + ast_rtp_codecs_payloads_copy(&newvideortp, ast_rtp_instance_get_codecs(pvt->vrtp), pvt->vrtp); + sip_pvt_setup_rtcp(pvt, pvt->vrtp, SIP_VIDEO_RTCP_FD, remote_rtcp_mux_video); + } else { + ast_rtp_instance_stop(pvt->vrtp); + + if (debug) { + ast_verb(3, "Peer doesn't provide video\n"); + } + } + } + + /* Setup text address and port */ + if (pvt->trtp) { + if (tsa && tport > 0) { + sip_pvt_start_ice(pvt->trtp, (req->method != SIP_RESPONSE) ? FALSE : TRUE); + + ast_sockaddr_set_port(tsa, tport); + ast_rtp_instance_set_remote_address(pvt->trtp, tsa); + + if (debug) { + ast_verb(3, "Peer T.140 RTP is at port %s\n", ast_sockaddr_stringify(tsa)); + } + + if (ast_format_cap_iscompatible_format(pvt->jointcaps, ast_format_t140_red) != AST_FORMAT_CMP_NOT_EQUAL) { + pvt->red = TRUE; + ast_rtp_red_init(pvt->trtp, 300, red_data_pt, 2); + } else { + pvt->red = FALSE; + } + + ast_rtp_codecs_payloads_copy(&newtextrtp, ast_rtp_instance_get_codecs(pvt->trtp), pvt->trtp); + } else { + ast_rtp_instance_stop(pvt->trtp); + + if (debug) { + ast_verb(3, "Peer doesn't provide T.140\n"); + } + } + } + + /* Setup image address and port */ + if (pvt->udptl) { + if (isa && udptlport > 0) { + if (ast_test_flag(&pvt->flags[1], SIP_SYMMETRICRTP) && ast_test_flag(&pvt->flags[1], SIP_UDPTL_DESTINATION)) { + ast_rtp_instance_get_remote_address(pvt->rtp, isa); + + if (!ast_sockaddr_isnull(isa) && debug) { + ast_debug(1, "Peer T.38 UDPTL is set behind NAT and with destination, destination address now %s\n", ast_sockaddr_stringify(isa)); + } + } + + ast_sockaddr_set_port(isa, udptlport); + ast_udptl_set_peer(pvt->udptl, isa); + + if (debug) { + ast_debug(1, "Peer T.38 UDPTL is at port %s\n", ast_sockaddr_stringify(isa)); + } + + /* verify the far max ifp can be calculated. this requires far max datagram to be set. */ + if (!ast_udptl_get_far_max_datagram(pvt->udptl)) { + /* setting to zero will force a default if none was provided by the SDP */ + ast_udptl_set_far_max_datagram(pvt->udptl, 0); + } + + /* Remote party offers T38, we need to update state */ + if (t38action == SIP_SDP_T38_ACCEPT && pvt->t38.state == SIP_T38_LOCAL_REINVITE) { + sip_pvt_change_t38_state(pvt, SIP_T38_ENABLED); + } else if (t38action == SIP_SDP_T38_INITIATE && pvt->owner && pvt->lastinvite) { + sip_pvt_change_t38_state(pvt, 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(&pvt->flags[1], SIP_FAX_DETECT_T38)) { + ast_channel_lock(pvt->owner); + + if (strcmp(ast_channel_exten(pvt->owner), "fax")) { + const char *target_context = ast_channel_context(pvt->owner); + + ast_channel_unlock(pvt->owner); + + if (ast_exists_extension(pvt->owner, target_context, "fax", 1, + S_COR(ast_channel_caller(pvt->owner)->id.number.valid, ast_channel_caller(pvt->owner)->id.number.str, NULL))) { + ast_verb(2, "Redirecting '%s' to fax extension due to peer T.38 re-INVITE\n", ast_channel_name(pvt->owner)); + pbx_builtin_setvar_helper(pvt->owner, "FAXEXTEN", ast_channel_exten(pvt->owner)); + + if (ast_async_goto(pvt->owner, target_context, "fax", 1)) { + ast_log(LOG_NOTICE, "Failed to async goto '%s' into fax of '%s'\n", ast_channel_name(pvt->owner), target_context); + } + } else { + ast_log(LOG_NOTICE, "T.38 re-INVITE detected but no fax extension\n"); + } + } else { + ast_channel_unlock(pvt->owner); + } + } + } + } else { + sip_pvt_change_t38_state(pvt, SIP_T38_DISABLED); + ast_udptl_stop(pvt->udptl); + + if (debug) { + ast_debug(1, "Peer doesn't provide T.38 UDPTL\n"); + } + } + } + + if (aport == -1 && pvt->t38.state != SIP_T38_DISABLED && pvt->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 */ + ast_debug(2, "We're settling with these formats: %s\n", ast_format_cap_get_names(pvt->jointcaps, &codec_buf)); + + if (!pvt->owner) { /* There's no open channel owning us so we can return here. For a re-invite or so, we proceed */ + 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(pvt->jointcaps, AST_MEDIA_TYPE_AUDIO)) { + struct ast_format_cap *caps; + unsigned int framing; + + if (debug) { + struct ast_str *cap_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + struct ast_str *joint_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + + ast_debug(1, "Setting native formats after processing SDP. peer joint formats %s, old nativeformats %s\n", + ast_format_cap_get_names(pvt->jointcaps, &joint_buf), + ast_format_cap_get_names(ast_channel_nativeformats(pvt->owner), &cap_buf)); + } + + if ((caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + format = ast_format_cap_get_format(pvt->jointcaps, 0); + framing = ast_format_cap_get_format_framing(pvt->jointcaps, format); + + ast_format_cap_append(caps, format, framing); + ast_format_cap_append_from_cap(caps, vpeercapability, AST_MEDIA_TYPE_VIDEO); + ast_format_cap_append_from_cap(caps, tpeercapability, AST_MEDIA_TYPE_TEXT); + + ast_channel_nativeformats_set(pvt->owner, caps); + + ao2_ref(caps, -1); + ao2_ref(format, -1); + } + + ast_set_read_format(pvt->owner, ast_channel_readformat(pvt->owner)); + ast_set_write_format(pvt->owner, ast_channel_writeformat(pvt->owner)); + } + + if (ast_test_flag(&pvt->flags[1], SIP_CALL_ONHOLD) && (!ast_sockaddr_isnull(asa) || !ast_sockaddr_isnull(vsa) || !ast_sockaddr_isnull(tsa) || !ast_sockaddr_isnull(isa)) && (!sendonly || sendonly == -1)) { + if (!ast_test_flag(&pvt->flags[2], SIP_DISCARD_REMOTE_HOLD_RETRIEVAL)) { + ast_queue_unhold(pvt->owner); + } + + /* Activate a re-invite */ + ast_queue_frame(pvt->owner, &ast_null_frame); + sip_pvt_change_onhold(pvt, req, FALSE, sendonly); + } else if (((ast_sockaddr_isnull(asa) || ast_sockaddr_is_any(asa)) && (ast_sockaddr_isnull(vsa) || ast_sockaddr_is_any(vsa)) && + (ast_sockaddr_isnull(tsa) || ast_sockaddr_is_any(tsa)) && (ast_sockaddr_isnull(isa) || ast_sockaddr_is_any(isa))) + || (sendonly && sendonly != -1)) { + if (!ast_test_flag(&pvt->flags[2], SIP_DISCARD_REMOTE_HOLD_RETRIEVAL)) { + ast_queue_hold(pvt->owner, pvt->mohsuggest); + } + + if (sendonly) { + ast_rtp_instance_stop(pvt->rtp); + } + + /* RTCP needs to go ahead, even if we're on hold!!! */ + /* Activate a re-invite */ + ast_queue_frame(pvt->owner, &ast_null_frame); + sip_pvt_change_onhold(pvt, req, TRUE, sendonly); + } + +cleanup: + if (res) { + sip_pvt_free_offered_media(pvt); + } + + ast_rtp_codecs_payloads_destroy(&newtextrtp); + ast_rtp_codecs_payloads_destroy(&newvideortp); + ast_rtp_codecs_payloads_destroy(&newaudiortp); + + ao2_cleanup(peercapability); + ao2_cleanup(vpeercapability); + ao2_cleanup(tpeercapability); + ao2_cleanup(newjointcapability); + ao2_cleanup(newpeercapability); + + return res; +} + +static int sip_sdp_parse_o(const char *o, struct sip_pvt *pvt) +{ + const char *o_copy_start; + char *o_copy; + char *token; + int offset; + int64_t sess_version; + char 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. */ + pvt->session_modify = TRUE; + + if (ast_strlen_zero(o)) { + ast_log(LOG_WARNING, "SDP syntax error. SDP without an o= line\n"); + return FALSE; + } + + /* o= */ + o_copy_start = o_copy = ast_strdupa(o); + token = strsep(&o_copy, " "); /* Skip username */ + + if (!o_copy) { + ast_log(LOG_WARNING, "SDP syntax error in o= line username\n"); + return FALSE; + } + + token = strsep(&o_copy, " "); /* sess-id */ + + if (!o_copy) { + ast_log(LOG_WARNING, "SDP syntax error in o= line sess-id\n"); + return FALSE; + } + + token = strsep(&o_copy, " "); /* sess-version */ + + if (!o_copy || !sscanf(token, "%30ld", &sess_version)) { + ast_log(LOG_WARNING, "SDP syntax error in o= line sess-version\n"); + return FALSE; + } + + /* Copy all after sess-version on top of sess-version into unique. is a numeric string such that the tuple + * of , , , , and forms a globally unique identifier for the + * session. I.e. all except the */ + ast_copy_string(unique, o, sizeof(unique)); /* copy all of o= contents */ + offset = (o_copy - o_copy_start); /* after sess-version */ + + if (offset < sizeof(unique)) { + /* copy all after sess-version on top of sess-version */ + int sess_version_start = token - o_copy_start; + + ast_copy_string(unique + sess_version_start, o + offset, sizeof(unique) - sess_version_start); + } + + /* 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 sessionunique_remote 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 (sip_debug_test_pvt(pvt)) { + if (ast_strlen_zero(pvt->sessionunique_remote)) { + ast_verb(3, "Got SDP version %ld and unique parts [%s]\n", sess_version, unique); + } else { + ast_verb(3, "Comparing SDP version %ld -> %ld and unique parts [%s] -> [%s]\n", + pvt->sessionversion_remote, sess_version, pvt->sessionunique_remote, unique); + } + } + + if (ast_test_flag(&pvt->flags[1], SIP_IGNORESDPVERSION) || + sess_version > pvt->sessionversion_remote || strcmp(unique, S_OR(pvt->sessionunique_remote, ""))) { + pvt->sessionversion_remote = sess_version; + ast_string_field_set(pvt, sessionunique_remote, unique); + } else { + if (pvt->t38.state == SIP_T38_LOCAL_REINVITE) { + pvt->sessionversion_remote = sess_version; + ast_string_field_set(pvt, sessionunique_remote, 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", pvt->callid); + } else { + pvt->session_modify = FALSE; + ast_debug(2, "Call %s responded to our reinvite without changing SDP version; ignoring SDP.\n", pvt->callid); + + return FALSE; + } + } + + return TRUE; +} + +static int sip_sdp_parse_c(const char *c, struct ast_sockaddr *addr) +{ + char proto[4], host[258]; + int af; + + /* Check for Media-description-level-address */ + if (sscanf(c, "IN %3s %255s", proto, host) == 2) { + if (!strcmp("IP4", proto)) { + af = AF_INET; + } else if (!strcmp("IP6", proto)) { + af = AF_INET6; + } else { + ast_log(LOG_WARNING, "Unknown protocol '%s'.\n", proto); + return FALSE; + } + + if (ast_sockaddr_resolve_first_af(addr, host, 0, af)) { + ast_log(LOG_WARNING, "Unable to lookup RTP Audio host in c= line, '%s'\n", c); + return FALSE; + } + + return TRUE; + } else { + ast_log(LOG_WARNING, "Invalid host in c= line, '%s'\n", c); + + return FALSE; + } + + return FALSE; +} + +static int sip_sdp_parse_a_sendonly(const char *a, int *sendonly) +{ + int found = FALSE; + + if (!strcasecmp(a, "sendonly")) { + if (*sendonly == -1) { + *sendonly = 1; + } + + found = TRUE; + } else if (!strcasecmp(a, "inactive")) { + if (*sendonly == -1) { + *sendonly = 2; + } + + found = TRUE; + } else if (!strcasecmp(a, "sendrecv")) { + if (*sendonly == -1) { + *sendonly = 0; + } + + found = TRUE; + } + + return found; +} + +static int sip_sdp_parse_crypto(struct sip_pvt *pvt, struct ast_rtp_instance *rtp, struct ast_sdp_srtp **srtp, const char *a) +{ + /* 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 (strncasecmp(a, "crypto:", 7)) { + return FALSE; + } + + /* skip "crypto:" */ + a += 7; + + if (!*srtp) { + if (ast_test_flag(&pvt->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; + } + + if (ast_sdp_crypto_process(rtp, *srtp, a) < 0) { + return FALSE; + } + + return TRUE; +} + +static int sip_sdp_parse_a_ice(const char *a, struct sip_pvt *pvt, struct ast_rtp_instance *instance, int rtcp_mux_offered) +{ + struct ast_rtp_engine_ice *ice; + int found = FALSE; + char ufrag[256], pwd[256], foundation[33], transport[4], address[46], cand_type[6], relay_address[46] = ""; + struct ast_rtp_engine_ice_candidate candidate = {0}; + unsigned int port, relay_port = 0; + + if (!instance || !(ice = ast_rtp_instance_get_ice(instance))) { + return found; + } + + if (sscanf(a, "ice-ufrag: %255s", ufrag) == 1) { + ice->set_authentication(instance, ufrag, NULL); + found = TRUE; + } else if (sscanf(a, "ice-pwd: %255s", pwd) == 1) { + ice->set_authentication(instance, NULL, pwd); + found = TRUE; + } else if (sscanf(a, "candidate: %32s %30u %3s %30u %23s %30u typ %5s %*s %23s %*s %30u", + foundation, &candidate.id, transport, (unsigned *)&candidate.priority, + address, &port, cand_type, relay_address, &relay_port) >= 7) { + + if (rtcp_mux_offered && ast_test_flag(&pvt->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 (!strcasecmp(cand_type, "host")) { + candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_HOST; + } else if (!strcasecmp(cand_type, "srflx")) { + candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_SRFLX; + } else if (!strcasecmp(cand_type, "relay")) { + candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_RELAYED; + } else { + return found; + } + + 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(instance, &candidate); + + found = TRUE; + } else if (!strcasecmp(a, "ice-lite")) { + ice->ice_lite(instance); + found = TRUE; + } + + return found; +} + +static int sip_sdp_parse_a_rtcp_mux(const char *a, struct sip_pvt *pvt, int *requested) +{ + int found = FALSE; + + if (!strncasecmp(a, "rtcp-mux", 8)) { + *requested = TRUE; + found = TRUE; + } + + return found; +} + +static int sip_sdp_parse_a_audio(const char *a, struct sip_pvt *pvt, struct ast_rtp_codecs *newaudiortp, int *audio_codec, int *rtpmap_codecs) +{ + int found = FALSE; + unsigned int codec; + char mime_subtype[128]; + char fmtp_string[256]; + unsigned int sample_rate; + int debug = sip_debug_test_pvt(pvt); + + if (!strncasecmp(a, "ptime", 5)) { + char *tmp = strrchr(a, ':'); + long int framing = 0; + + if (tmp) { + tmp++; + framing = strtol(tmp, NULL, 10); + + if (framing == LONG_MIN || framing == LONG_MAX) { + framing = 0; + ast_debug(1, "Can't read framing from SDP: %s\n", a); + } + } + + if (framing && pvt->autoframing) { + ast_debug(1, "Setting framing to %ld\n", framing); + ast_format_cap_set_framing(pvt->caps, framing); + ast_rtp_codecs_set_framing(newaudiortp, framing); + } + + found = TRUE; + } else if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mime_subtype, &sample_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(newaudiortp, NULL, codec, "audio", mime_subtype, + ast_test_flag(&pvt->flags[0], SIP_G726_NONSTANDARD) ? AST_RTP_OPT_G726_NONSTANDARD : 0, sample_rate))) { + if (debug) { + ast_verb(3, "Found audio description format %s for ID %u\n", mime_subtype, codec); + } + + *audio_codec = codec; + (*rtpmap_codecs)++; + + found = TRUE; + } else { + ast_rtp_codecs_payloads_unset(newaudiortp, NULL, codec); + + if (debug) { + ast_verb(3, "Found unknown media description format %s for ID %u\n", mime_subtype, codec); + } + } + } else { + if (debug) { + ast_verb(3, "Discarded description format %s for ID %u\n", mime_subtype, codec); + } + } + } else if (sscanf(a, "fmtp: %30u %255[^\t\n]", &codec, fmtp_string) == 2) { + struct ast_format *format; + + if ((format = ast_rtp_codecs_get_payload_format(newaudiortp, codec))) { + unsigned int bit_rate; + struct ast_format *format_parsed; + + format_parsed = ast_format_parse_sdp_fmtp(format, fmtp_string); + + if (format_parsed) { + ast_rtp_codecs_payload_replace_format(newaudiortp, codec, format_parsed); + ao2_replace(format, format_parsed); + + ao2_ref(format_parsed, -1); + found = TRUE; + } else { + ast_rtp_codecs_payloads_unset(newaudiortp, NULL, codec); + } + + if (ast_format_cmp(format, ast_format_g719) == AST_FORMAT_CMP_EQUAL) { + if (sscanf(fmtp_string, "bitrate=%30u", &bit_rate) == 1) { + if (bit_rate != 64000) { + ast_log(LOG_WARNING, "Got G.719 offer at %u bps, but only 64000 bps supported; ignoring.\n", bit_rate); + ast_rtp_codecs_payloads_unset(newaudiortp, NULL, codec); + } else { + found = TRUE; + } + } + } + + ao2_ref(format, -1); + } + } + + return found; +} + +static int sip_sdp_parse_a_video(const char *a, struct sip_pvt *pvt, struct ast_rtp_codecs *newvideortp, int *video_codec, int *rtpmap_codecs) +{ + int found = FALSE; + unsigned int codec; + char mime_subtype[128]; + unsigned int sample_rate; + char fmtp_string[256]; + char imageattr[256]; + int debug = sip_debug_test_pvt(pvt); + + if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mime_subtype, &sample_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 (!strncasecmp(mime_subtype, "H26", 3) || !strncasecmp(mime_subtype, "MP4", 3) + || !strncasecmp(mime_subtype, "VP8", 3)) { + if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(newvideortp, NULL, codec, "video", mime_subtype, 0, sample_rate))) { + if (debug) { + ast_verb(3, "Found video description format %s for ID %u\n", mime_subtype, codec); + } + + *video_codec = codec; + (*rtpmap_codecs)++; + + found = TRUE; + } else { + ast_rtp_codecs_payloads_unset(newvideortp, NULL, codec); + + if (debug) { + ast_verb(3, "Found unknown media description format %s for ID %u\n", mime_subtype, codec); + } + } + } + } else { + if (debug) { + ast_verb(3, "Discarded description format %s for ID %u\n", mime_subtype, codec); + } + } + } else if (sscanf(a, "fmtp: %30u %255[^\t\n]", &codec, fmtp_string) == 2) { + struct ast_format *format; + + if ((format = ast_rtp_codecs_get_payload_format(newvideortp, codec))) { + struct ast_format *format_parsed; + + format_parsed = ast_format_parse_sdp_fmtp(format, fmtp_string); + + if (format_parsed) { + ast_rtp_codecs_payload_replace_format(newvideortp, codec, format_parsed); + ao2_replace(format, format_parsed); + + ao2_ref(format_parsed, -1); + found = TRUE; + } else { + ast_rtp_codecs_payloads_unset(newvideortp, NULL, codec); + } + + ao2_ref(format, -1); + } + } else if (sscanf(a, "imageattr: %30u recv %255[^\t\n]", &codec, imageattr) == 2 + || (codec = *video_codec, sscanf(a, "imageattr:* recv %255[^\t\n]", imageattr) == 1)) { + struct ast_format *format; + + if ((format = ast_rtp_codecs_get_payload_format(newvideortp, codec))) { + struct ast_format *format_parsed; + + format_parsed = ast_format_attribute_set(format, "imageattr", imageattr); + + if (format_parsed) { + ast_rtp_codecs_payload_replace_format(newvideortp, codec, format_parsed); + ao2_replace(format, format_parsed); + + ao2_ref(format_parsed, -1); + found = TRUE; + } else { + ast_rtp_codecs_payloads_unset(newvideortp, NULL, codec); + } + + ao2_ref(format, -1); + } + } + + return found; +} + +static int sip_sdp_parse_a_text(const char *a, struct sip_pvt *pvt, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, + int *red_data_pt, int *rtpmap_codecs) +{ + int found = FALSE; + unsigned int codec; + char mime_subtype[128]; + unsigned int sample_rate; + char *red_cp; + int debug = sip_debug_test_pvt(pvt); + + if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mime_subtype, &sample_rate) == 3) { + /* We have a rtpmap to handle */ + if (*rtpmap_codecs < SIP_SDP_MAX_RTPMAP_CODECS) { + if (!strncasecmp(mime_subtype, "T140", 4)) { /* Text */ + if (pvt->trtp) { + /* ast_verb(3, "Adding t140 mime_subtype to textrtp struct\n"); */ + ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mime_subtype, 0, sample_rate); + found = TRUE; + } + } else if (!strncasecmp(mime_subtype, "RED", 3)) { /* Text with Redudancy */ + if (pvt->trtp) { + ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mime_subtype, 0, sample_rate); + sprintf(red_fmtp, "fmtp:%u ", codec); + + if (debug) { + ast_verb(3, "RED submimetype has payload type: %u\n", codec); + } + + found = TRUE; + } + } + + (*rtpmap_codecs)++; + } else { + if (debug) { + ast_verb(3, "Discarded description format %s for ID %u\n", mime_subtype, codec); + } + } + } else if (!strncmp(a, red_fmtp, strlen(red_fmtp))) { + char *rest = NULL; + + /* count numbers of generations in fmtp */ + red_cp = &red_fmtp[strlen(red_fmtp)]; + strncpy(red_fmtp, a, 100); + + sscanf(red_cp, "%30u", (unsigned *)&red_data_pt[*red_num_gen]); + red_cp = strtok_r(red_cp, "/", &rest); + + while (red_cp && (*red_num_gen)++ < AST_RED_MAX_GENERATION) { + sscanf(red_cp, "%30u", (unsigned *)&red_data_pt[*red_num_gen]); + red_cp = strtok_r(NULL, "/", &rest); + } + + red_cp = red_fmtp; + found = TRUE; + } + + return found; +} + +static int sip_sdp_parse_a_image(const char *a, struct sip_pvt *pvt) +{ + int found = FALSE; + char s[256]; + unsigned int x; + char *attrib = ast_strdupa(a); + + if (sip_pvt_setup_udptl(pvt)) { + return found; + } + + /* 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). */ + ast_str_to_lower(attrib); + + if ((sscanf(attrib, "t38faxmaxbuffer:%30u", &x) == 1)) { + ast_debug(3, "MaxBufferSize:%u\n", x); + found = TRUE; + } else if ((sscanf(attrib, "t38maxbitrate:%30u", &x) == 1) || (sscanf(attrib, "t38faxmaxrate:%30u", &x) == 1)) { + ast_debug(3, "T38MaxBitRate: %u\n", x); + switch (x) { + case 14400: + pvt->t38.their_parms.rate = AST_T38_RATE_14400; + break; + case 12000: + pvt->t38.their_parms.rate = AST_T38_RATE_12000; + break; + case 9600: + pvt->t38.their_parms.rate = AST_T38_RATE_9600; + break; + case 7200: + pvt->t38.their_parms.rate = AST_T38_RATE_7200; + break; + case 4800: + pvt->t38.their_parms.rate = AST_T38_RATE_4800; + break; + case 2400: + pvt->t38.their_parms.rate = AST_T38_RATE_2400; + break; + } + + found = TRUE; + } else if ((sscanf(attrib, "t38faxversion:%30u", &x) == 1)) { + ast_debug(3, "FaxVersion: %u\n", x); + pvt->t38.their_parms.version = x; + + found = TRUE; + } else if ((sscanf(attrib, "t38faxmaxdatagram:%30u", &x) == 1) || (sscanf(attrib, "t38maxdatagram:%30u", &x) == 1)) { + /* override the supplied value if the configuration requests it */ + if ((signed int) pvt->t38_maxdatagram >= 0 && (unsigned int) pvt->t38_maxdatagram > x) { + ast_debug(1, "Overriding T38FaxMaxDatagram '%u' with '%d'\n", x, pvt->t38_maxdatagram); + x = pvt->t38_maxdatagram; + + } + + ast_debug(3, "FaxMaxDatagram: %u\n", x); + ast_udptl_set_far_max_datagram(pvt->udptl, x); + + found = TRUE; + } else if ((strncmp(attrib, "t38faxfillbitremoval", 20) == 0)) { + if (sscanf(attrib, "t38faxfillbitremoval:%30u", &x) == 1) { + ast_debug(3, "FillBitRemoval: %u\n", x); + + if (x == 1) { + pvt->t38.their_parms.fill_bit_removal = TRUE; + } + } else { + ast_debug(3, "FillBitRemoval\n"); + pvt->t38.their_parms.fill_bit_removal = TRUE; + } + + found = TRUE; + } else if ((strncmp(attrib, "t38faxtranscodingmmr", 20) == 0)) { + if (sscanf(attrib, "t38faxtranscodingmmr:%30u", &x) == 1) { + ast_debug(3, "Transcoding MMR: %u\n", x); + + if (x == 1) { + pvt->t38.their_parms.transcoding_mmr = TRUE; + } + } else { + ast_debug(3, "Transcoding MMR\n"); + pvt->t38.their_parms.transcoding_mmr = TRUE; + } + + found = TRUE; + } else if ((strncmp(attrib, "t38faxtranscodingjbig", 21) == 0)) { + if (sscanf(attrib, "t38faxtranscodingjbig:%30u", &x) == 1) { + ast_debug(3, "Transcoding JBIG: %u\n", x); + + if (x == 1) { + pvt->t38.their_parms.transcoding_jbig = TRUE; + } + } else { + ast_debug(3, "Transcoding JBIG\n"); + pvt->t38.their_parms.transcoding_jbig = TRUE; + } + + found = TRUE; + } else if ((sscanf(attrib, "t38faxratemanagement:%255s", s) == 1)) { + ast_debug(3, "RateManagement: %s\n", s); + + if (!strcasecmp(s, "localTCF")) { + pvt->t38.their_parms.rate_management = AST_T38_RATE_MANAGEMENT_LOCAL_TCF; + } else if (!strcasecmp(s, "transferredTCF")) { + pvt->t38.their_parms.rate_management = AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF; + } + + found = TRUE; + } else if ((sscanf(attrib, "t38faxudpec:%255s", s) == 1)) { + ast_debug(3, "UDP EC: %s\n", s); + + if (!strcasecmp(s, "t38UDPRedundancy")) { + ast_udptl_set_error_correction_scheme(pvt->udptl, UDPTL_ERROR_CORRECTION_REDUNDANCY); + } else if (!strcasecmp(s, "t38UDPFEC")) { + ast_udptl_set_error_correction_scheme(pvt->udptl, UDPTL_ERROR_CORRECTION_FEC); + } else { + ast_udptl_set_error_correction_scheme(pvt->udptl, UDPTL_ERROR_CORRECTION_NONE); + } + + found = TRUE; + } + + return found; +} + +/* Add Session Description Protocol message. If oldsdp 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_generate(struct sip_pvt *pvt, struct sip_request *resp, int oldsdp, int add_audio, int add_t38) +{ + struct ast_format_cap *alreadysent = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + struct ast_format_cap *tmpcap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + int res = 0; + int doing_directmedia = FALSE; + struct ast_sockaddr addr = {{0}}; + struct ast_sockaddr vaddr = {{0}}; + struct ast_sockaddr taddr = {{0}}; + struct ast_sockaddr udptladdr = {{0}}; + struct ast_sockaddr dest = {{0}}; + struct ast_sockaddr vdest = {{0}}; + struct ast_sockaddr tdest = {{0}}; + struct ast_sockaddr udptldest = {{0}}; + + /* SDP fields */ + struct sip_offered_media *offered_media; + char *version = "v=0\r\n"; /* Protocol version */ + char subject[256]; /* Subject of the session */ + char owner[256]; /* Session owner/creator */ + char connection[256]; /* Connection data */ + char *session_time = "t=0 0\r\n"; /* Time the session is active */ + char bandwidth[256] = ""; /* Max bitrate */ + char *hold = ""; + struct ast_str *m_audio = ast_str_alloca(256); /* Media declaration line for audio */ + struct ast_str *m_video = ast_str_alloca(256); /* Media declaration line for video */ + struct ast_str *m_text = ast_str_alloca(256); /* Media declaration line for text */ + struct ast_str *m_modem = ast_str_alloca(256); /* Media declaration line for modem */ + struct ast_str *a_audio = ast_str_create(256); /* Attributes for audio */ + struct ast_str *a_video = ast_str_create(256); /* Attributes for video */ + struct ast_str *a_text = ast_str_create(256); /* Attributes for text */ + struct ast_str *a_modem = ast_str_alloca(1024); /* Attributes for modem */ + + RAII_VAR(char *, a_crypto, NULL, ast_free); + RAII_VAR(char *, v_a_crypto, NULL, ast_free); + RAII_VAR(char *, t_a_crypto, NULL, ast_free); + + int i; + struct ast_format *format; + int need_audio = FALSE; + int need_video = FALSE; + int need_text = FALSE; + int debug = sip_debug_test_pvt(pvt); + int min_audio_packet_size = 0; + int max_audio_packet_size = 0; + int min_video_packet_size = 0; + int min_text_packet_size = 0; + struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + + /* Set the SDP session name */ + snprintf(subject, sizeof(subject), "s=%s\r\n", ast_strlen_zero(sip_config.sdpsession) ? "-" : sip_config.sdpsession); + + if (!alreadysent || !tmpcap) { + res = -1; + goto cleanup; + } + + if (!pvt->rtp) { + ast_log(LOG_WARNING, "No way to add SDP without an RTP structure\n"); + res = -1; + + goto cleanup; + + } + + /* 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 (!pvt->sessionid) { + pvt->sessionid = (int) ast_random(); + pvt->sessionversion = pvt->sessionid; + } else if (oldsdp == FALSE) { + pvt->sessionversion++; + } + + if (add_audio) { + doing_directmedia = !ast_sockaddr_isnull(&pvt->redirip) && ast_format_cap_count(pvt->redircaps); + + if (doing_directmedia) { + ast_format_cap_get_compatible(pvt->jointcaps, pvt->redircaps, tmpcap); + ast_debug(1, "** Our native-bridge filtered capability: %s\n", ast_format_cap_get_names(tmpcap, &codec_buf)); + } else { + ast_format_cap_append_from_cap(tmpcap, pvt->jointcaps, AST_MEDIA_TYPE_UNKNOWN); + } + + /* Check if we need_ audio in this call */ + need_audio = ast_format_cap_has_type(tmpcap, AST_MEDIA_TYPE_AUDIO); + + /* Check if we need_ video in this call */ + if ((ast_format_cap_has_type(tmpcap, AST_MEDIA_TYPE_VIDEO)) && !pvt->novideo) { + if (doing_directmedia && !ast_format_cap_has_type(tmpcap, AST_MEDIA_TYPE_VIDEO)) { + ast_debug(2, "This call need_s video offers, but caller probably did not offer it!\n"); + } else if (pvt->vrtp) { + need_video = TRUE; + + ast_debug(2, "This call need_s video offers!\n"); + } else { + ast_debug(2, "This call need_s video offers, but there's no video support enabled!\n"); + } + } + + /* Check if we need_ text in this call */ + if ((ast_format_cap_has_type(pvt->jointcaps, AST_MEDIA_TYPE_TEXT)) && !pvt->notext) { + if (pvt->trtp) { + need_text = TRUE; + + ast_debug(2, "This call need_s text offers! \n"); + } else { + ast_debug(2, "This call need_s text offers, but there's no text support enabled ! \n"); + } + } + + /* XXX note, Video and Text are negated - 'true' means 'no' */ + ast_debug(1, "Our capability: %s Video flag: %s Text flag: %s\n", + ast_format_cap_get_names(tmpcap, &codec_buf), pvt->novideo ? "True" : "False", pvt->notext ? "True" : "False"); + ast_debug(1, "Our prefcodec: %s \n", ast_format_cap_get_names(pvt->prefcaps, &codec_buf)); + } + + sip_pvt_get_our_media_addr(pvt, need_video, need_text, &addr, &vaddr, &taddr, &dest, &vdest, &tdest); + + /* We don't use dest here but pvt->ourip because address in o= field must not change in reINVITE */ + snprintf(owner, sizeof(owner), "o=%s %d %d IN %s %s\r\n", + ast_strlen_zero(sip_config.sdpowner) ? "-" : sip_config.sdpowner, + pvt->sessionid, pvt->sessionversion, + ast_sockaddr_is_ipv6(&pvt->ourip) && !ast_sockaddr_is_ipv4_mapped(&pvt->ourip) ? "IP6" : "IP4", + ast_sockaddr_stringify_addr_remote(&pvt->ourip)); + + snprintf(connection, sizeof(connection), "c=IN %s %s\r\n", + ast_sockaddr_is_ipv6(&dest) && !ast_sockaddr_is_ipv4_mapped(&dest) ? "IP6" : "IP4", + ast_sockaddr_stringify_addr_remote(&dest)); + + if (add_audio) { + if (ast_test_flag(&pvt->flags[1], SIP_CALL_ONHOLD) == SIP_CALL_ONHOLD_ONEDIR) { + hold = "a=recvonly\r\n"; + doing_directmedia = FALSE; + } else if (ast_test_flag(&pvt->flags[1], SIP_CALL_ONHOLD) == SIP_CALL_ONHOLD_INACTIVE) { + hold = "a=inactive\r\n"; + doing_directmedia = FALSE; + } else { + hold = "a=sendrecv\r\n"; + } + + if (debug) { + ast_verb(3, "Audio is at %s\n", ast_sockaddr_stringify_port(&addr)); + } + + /* 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 (need_video) { + v_a_crypto = sip_sdp_get_crypto_attr(pvt->vsrtp, ast_test_flag(&pvt->flags[2], SIP_SRTP_TAG_32)); + + ast_str_append(&m_video, 0, "m=video %d %s", ast_sockaddr_port(&vdest), + ast_sdp_get_rtp_profile(v_a_crypto ? TRUE : FALSE, pvt->vrtp, + ast_test_flag(&pvt->flags[2], SIP_USE_AVPF), + ast_test_flag(&pvt->flags[2], SIP_FORCE_AVP))); + + /* Build max bitrate string */ + if (pvt->maxcallbitrate && !ast_test_flag(&pvt->flags[1], SIP_CISCO_USECALLMANAGER)) + snprintf(bandwidth, sizeof(bandwidth), "b=CT:%d\r\n", pvt->maxcallbitrate); + if (debug) { + ast_verb(3, "Video is at %s\n", ast_sockaddr_stringify(&vdest)); + } + + if (!doing_directmedia) { + if (ast_test_flag(&pvt->flags[2], SIP_ICE_SUPPORT)) { + sip_sdp_add_ice(pvt->vrtp, &a_video); + } + } + } + + /* 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 (need_text) { + t_a_crypto = sip_sdp_get_crypto_attr(pvt->tsrtp, ast_test_flag(&pvt->flags[2], SIP_SRTP_TAG_32)); + + ast_str_append(&m_text, 0, "m=text %d %s", ast_sockaddr_port(&tdest), + ast_sdp_get_rtp_profile(t_a_crypto ? TRUE : FALSE, pvt->trtp, + ast_test_flag(&pvt->flags[2], SIP_USE_AVPF), + ast_test_flag(&pvt->flags[2], SIP_FORCE_AVP))); + + if (debug) { /* XXX should I use tdest below ? */ + ast_verb(3, "Text is at %s\n", ast_sockaddr_stringify(&taddr)); + } + + if (!doing_directmedia) { + if (ast_test_flag(&pvt->flags[2], SIP_ICE_SUPPORT)) { + sip_sdp_add_ice(pvt->trtp, &a_text); + } + } + } + + /* Start building generic SDP headers */ + if (ast_test_flag(&pvt->flags[2], SIP_RELAY_NEAREND)) { + ast_str_append(&a_audio, 0, "a=label:X-relay-nearend\r\n"); + } else if (ast_test_flag(&pvt->flags[2], SIP_RELAY_FAREND)) { + ast_str_append(&a_audio, 0, "a=label:X-relay-farend\r\n"); + } + + /* We break with the "recommendation" and send our IP, in order that our peer doesn't have to ast_gethostbyname() us */ + a_crypto = sip_sdp_get_crypto_attr(pvt->srtp, ast_test_flag(&pvt->flags[2], SIP_SRTP_TAG_32)); + + ast_str_append(&m_audio, 0, "m=audio %d %s", ast_sockaddr_port(&dest), + ast_sdp_get_rtp_profile(a_crypto ? TRUE : FALSE, pvt->rtp, + ast_test_flag(&pvt->flags[2], SIP_USE_AVPF), + ast_test_flag(&pvt->flags[2], SIP_FORCE_AVP))); + + /* 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 tmpcap */ + /* Unless otherwise configured, the prefcaps is added before the peer's configured codecs. */ + if (!ast_test_flag(&pvt->flags[2], SIP_IGNORE_PREFCAPS)) { + for (i = 0; i < ast_format_cap_count(pvt->prefcaps); i++) { + format = ast_format_cap_get_format(pvt->prefcaps, i); + + if ((ast_format_get_type(format) != AST_MEDIA_TYPE_AUDIO) || + (ast_format_cap_iscompatible_format(tmpcap, format) == AST_FORMAT_CMP_NOT_EQUAL)) { + ao2_ref(format, -1); + continue; + } + + sip_sdp_add_audio_codec(pvt, format, &m_audio, &a_audio, debug, &min_audio_packet_size, &max_audio_packet_size); + + ast_format_cap_append(alreadysent, format, 0); + ao2_ref(format, -1); + } + } + + /* Now send any other common codecs */ + for (i = 0; i < ast_format_cap_count(tmpcap); i++) { + format = ast_format_cap_get_format(tmpcap, i); + + if (ast_format_cap_iscompatible_format(alreadysent, format) != AST_FORMAT_CMP_NOT_EQUAL) { + ao2_ref(format, -1); + continue; + } + + if (ast_format_get_type(format) == AST_MEDIA_TYPE_AUDIO) { + sip_sdp_add_audio_codec(pvt, format, &m_audio, &a_audio, debug, &min_audio_packet_size, &max_audio_packet_size); + } else if (need_video && ast_format_get_type(format) == AST_MEDIA_TYPE_VIDEO) { + sip_sdp_add_video_codec(pvt, format, &m_video, &a_video, debug, &min_video_packet_size); + } else if (need_text && ast_format_get_type(format) == AST_MEDIA_TYPE_TEXT) { + sip_sdp_add_text_codec(pvt, format, &m_text, &a_text, debug, &min_text_packet_size); + } + + ast_format_cap_append(alreadysent, format, 0); + ao2_ref(format, -1); + } + + /* Now add DTMF RFC2833 telephony-event as a codec */ + for (i = 1LL; i <= AST_RTP_MAX; i <<= 1) { + if (!(pvt->jointnoncodeccapability & i)) { + continue; + } + + sip_sdp_add_noncodec(pvt, i, &m_audio, &a_audio, debug); + } + + ast_debug(3, "Done with adding codecs to SDP\n"); + + if (!pvt->owner || ast_channel_timingfd(pvt->owner) == -1) { + ast_str_append(&a_audio, 0, "a=silenceSupp:off - - - -\r\n"); + } + + if (min_audio_packet_size) { + ast_str_append(&a_audio, 0, "a=ptime:%d\r\n", min_audio_packet_size); + } + + /* XXX don't think you can have ptime for video */ + if (min_video_packet_size) { + ast_str_append(&a_video, 0, "a=ptime:%d\r\n", min_video_packet_size); + } + + /* XXX don't think you can have ptime for text */ + if (min_text_packet_size) { + ast_str_append(&a_text, 0, "a=ptime:%d\r\n", min_text_packet_size); + } + + if (max_audio_packet_size) { + ast_str_append(&a_audio, 0, "a=maxptime:%d\r\n", max_audio_packet_size); + } + + if (!ast_test_flag(&pvt->flags[0], SIP_OUTGOING)) { + ast_debug(1, "Setting framing on incoming call: %u\n", min_audio_packet_size); + ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(pvt->rtp), min_audio_packet_size); + } + + if (!doing_directmedia) { + if (ast_test_flag(&pvt->flags[2], SIP_ICE_SUPPORT)) { + sip_sdp_add_ice(pvt->rtp, &a_audio); + + /* Start ICE negotiation, and setting that we are controlled agent, as this is response to offer */ + if (resp->method == SIP_RESPONSE) { + sip_pvt_start_ice(pvt->rtp, 0); + } + } + } + + /* If we've got rtcp-mux enabled, just unconditionally offer it in all SDPs */ + if (ast_test_flag(&pvt->flags[2], SIP_RTCP_MUX)) { + ast_str_append(&a_audio, 0, "a=rtcp-mux\r\n"); + ast_str_append(&a_video, 0, "a=rtcp-mux\r\n"); + } + } + + if (add_t38) { + unsigned int rate; + + /* Our T.38 end is */ + ast_udptl_get_us(pvt->udptl, &udptladdr); + + /* We don't use directmedia for T.38, so keep the destination the same as our IP address. */ + ast_sockaddr_copy(&udptldest, &pvt->ourip); + ast_sockaddr_set_port(&udptldest, ast_sockaddr_port(&udptladdr)); + + if (debug) { + ast_debug(1, "T.38 UDPTL is at %s port %d\n", ast_sockaddr_stringify_addr(&pvt->ourip), ast_sockaddr_port(&udptladdr)); + } + + /* We break with the "recommendation" and send our IP, in order that our peer doesn't have to ast_gethostbyname() us */ + ast_str_append(&m_modem, 0, "m=image %d udptl t38\r\n", ast_sockaddr_port(&udptldest)); + + if (ast_sockaddr_cmp_addr(&udptldest, &dest)) { + ast_str_append(&m_modem, 0, "c=IN %s %s\r\n", + ast_sockaddr_is_ipv6(&udptldest) && !ast_sockaddr_is_ipv4_mapped(&udptldest) ? "IP6" : "IP4", + ast_sockaddr_stringify_addr_remote(&udptldest)); + } + + ast_str_append(&a_modem, 0, "a=T38FaxVersion:%u\r\n", pvt->t38.our_parms.version); + + switch (pvt->t38.our_parms.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(&a_modem, 0, "a=T38MaxBitRate:%u\r\n", rate); + + if (pvt->t38.our_parms.fill_bit_removal) { + ast_str_append(&a_modem, 0, "a=T38FaxFillBitRemoval\r\n"); + } + + if (pvt->t38.our_parms.transcoding_mmr) { + ast_str_append(&a_modem, 0, "a=T38FaxTranscodingMMR\r\n"); + } + + if (pvt->t38.our_parms.transcoding_jbig) { + ast_str_append(&a_modem, 0, "a=T38FaxTranscodingJBIG\r\n"); + } + + switch (pvt->t38.our_parms.rate_management) { + case AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF: + ast_str_append(&a_modem, 0, "a=T38FaxRateManagement:transferredTCF\r\n"); + break; + case AST_T38_RATE_MANAGEMENT_LOCAL_TCF: + ast_str_append(&a_modem, 0, "a=T38FaxRateManagement:localTCF\r\n"); + break; + } + + ast_str_append(&a_modem, 0, "a=T38FaxMaxDatagram:%u\r\n", ast_udptl_get_local_max_datagram(pvt->udptl)); + + switch (ast_udptl_get_error_correction_scheme(pvt->udptl)) { + case UDPTL_ERROR_CORRECTION_NONE: + break; + case UDPTL_ERROR_CORRECTION_FEC: + ast_str_append(&a_modem, 0, "a=T38FaxUdpEC:t38UDPFEC\r\n"); + break; + case UDPTL_ERROR_CORRECTION_REDUNDANCY: + ast_str_append(&a_modem, 0, "a=T38FaxUdpEC:t38UDPRedundancy\r\n"); + break; + } + } + + if (need_audio) { + ast_str_append(&m_audio, 0, "\r\n"); + } + + if (need_video) { + ast_str_append(&m_video, 0, "\r\n"); + } + + if (need_text) { + ast_str_append(&m_text, 0, "\r\n"); + } + + sip_request_add_header(resp, "Content-Type", "application/sdp"); + sip_request_add_content(resp, version); + sip_request_add_content(resp, owner); + sip_request_add_content(resp, subject); + sip_request_add_content(resp, connection); + + /* only if video response is appropriate */ + if (need_video) { + sip_request_add_content(resp, bandwidth); + } + + sip_request_add_content(resp, session_time); + + /* if this is a response to an invite, order our offers properly */ + if (!AST_LIST_EMPTY(&pvt->offered_media)) { + AST_LIST_TRAVERSE(&pvt->offered_media, offered_media, next) { + switch (offered_media->type) { + case SIP_SDP_AUDIO: + if (need_audio) { + sip_request_add_content(resp, ast_str_buffer(m_audio)); + + if (a_crypto) { + sip_request_add_content(resp, a_crypto); + } + + sip_request_add_content(resp, ast_str_buffer(a_audio)); + sip_request_add_content(resp, hold); + } else { + sip_request_add_content(resp, offered_media->decline_m_line); + } + break; + case SIP_SDP_VIDEO: + if (need_video) { /* only if video response is appropriate */ + sip_request_add_content(resp, ast_str_buffer(m_video)); + sip_request_add_content(resp, ast_str_buffer(a_video)); + sip_request_add_content(resp, hold); /* Repeat hold for the video stream */ + + if (v_a_crypto) { + sip_request_add_content(resp, v_a_crypto); + } + } else { + sip_request_add_content(resp, offered_media->decline_m_line); + } + + break; + case SIP_SDP_TEXT: + if (need_text) { /* only if text response is appropriate */ + sip_request_add_content(resp, ast_str_buffer(m_text)); + sip_request_add_content(resp, ast_str_buffer(a_text)); + sip_request_add_content(resp, hold); /* Repeat hold for the text stream */ + + if (t_a_crypto) { + sip_request_add_content(resp, t_a_crypto); + } + } else { + sip_request_add_content(resp, offered_media->decline_m_line); + } + + break; + case SIP_SDP_IMAGE: + if (add_t38) { + sip_request_add_content(resp, ast_str_buffer(m_modem)); + sip_request_add_content(resp, ast_str_buffer(a_modem)); + } else { + sip_request_add_content(resp, offered_media->decline_m_line); + } + + break; + case SIP_SDP_UNKNOWN: + sip_request_add_content(resp, offered_media->decline_m_line); + break; + } + } + } else { + /* generate new SDP from scratch, no offers */ + if (need_audio) { + sip_request_add_content(resp, ast_str_buffer(m_audio)); + + if (a_crypto) { + sip_request_add_content(resp, a_crypto); + } + + sip_request_add_content(resp, ast_str_buffer(a_audio)); + sip_request_add_content(resp, hold); + } + + if (need_video) { /* only if video response is appropriate */ + sip_request_add_content(resp, ast_str_buffer(m_video)); + sip_request_add_content(resp, ast_str_buffer(a_video)); + sip_request_add_content(resp, hold); /* Repeat hold for the video stream */ + + if (v_a_crypto) { + sip_request_add_content(resp, v_a_crypto); + } + } + + if (need_text) { /* only if text response is appropriate */ + sip_request_add_content(resp, ast_str_buffer(m_text)); + sip_request_add_content(resp, ast_str_buffer(a_text)); + sip_request_add_content(resp, hold); /* Repeat hold for the text stream */ + + if (t_a_crypto) { + sip_request_add_content(resp, t_a_crypto); + } + } + + if (add_t38) { + sip_request_add_content(resp, ast_str_buffer(m_modem)); + sip_request_add_content(resp, ast_str_buffer(a_modem)); + } + } + + /* Update lastrtprx when we send our SDP */ + pvt->lastrtprx = pvt->lastrtptx = time(NULL); /* XXX why both ? */ + + /* We unlink this dialog and link again into the sip_pvts_rtp_check container so its not in there twice. */ + ao2_lock(sip_pvts_rtp_check); + + ao2_t_unlink(sip_pvts_rtp_check, pvt, "unlink pvt into sip_pvts_rtp_check container"); + ao2_t_link(sip_pvts_rtp_check, pvt, "link pvt into sip_pvts_rtp_check container"); + + ao2_unlock(sip_pvts_rtp_check); + + ast_debug(3, "Done building SDP. Settling with this capability: %s\n", ast_format_cap_get_names(tmpcap, &codec_buf)); + +cleanup: + ast_free(a_text); + ast_free(a_video); + ast_free(a_audio); + + ao2_cleanup(alreadysent); + ao2_cleanup(tmpcap); + + return res; +} + +/* Add codec offer to SDP offer/answer body in INVITE or 200 OK */ +static void sip_sdp_add_audio_codec(const struct sip_pvt *pvt, struct ast_format *format, struct ast_str **m_buf, + struct ast_str **a_buf, int debug, int *min_packet_size, int *max_packet_size) +{ + int rtp_code; + const char *mime; + unsigned int rate, framing; + + if (debug) { + ast_verb(3, "Adding codec %s to SDP\n", ast_format_get_name(format)); + } + + if (((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(pvt->rtp), 1, format, 0)) == -1) || + !(mime = ast_rtp_lookup_mime_subtype2(1, format, 0, ast_test_flag(&pvt->flags[0], SIP_G726_NONSTANDARD) ? AST_RTP_OPT_G726_NONSTANDARD : 0)) || + !(rate = ast_rtp_lookup_sample_rate2(1, format, 0))) { + return; + } + + ast_str_append(m_buf, 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_buf, 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_buf, 0, "a=rtpmap:%d %s/%u\r\n", rtp_code, mime, rate); + } + + ast_format_generate_sdp_fmtp(format, rtp_code, a_buf); + framing = ast_format_cap_get_format_framing(pvt->caps, 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_buf, 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_buf, 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 This is different to the audio one now so we can add more caps later */ +static void sip_sdp_add_video_codec(const struct sip_pvt *pvt, struct ast_format *format, + struct ast_str **m_buf, struct ast_str **a_buf, int debug, int *min_packet_size) +{ + int rtp_code; + const char *subtype; + unsigned int rate; + + if (!pvt->vrtp) { + return; + } + + if (debug) { + ast_verb(3, "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(pvt->vrtp), 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_buf, 0, " %d", rtp_code); + + if (ast_format_cmp(format, ast_format_h264) == AST_FORMAT_CMP_EQUAL && ast_test_flag(&pvt->flags[1], SIP_CISCO_USECALLMANAGER)) { + /* Needs to be a large number otherwise video quality is poor */ + ast_str_append(a_buf, 0, "b=TIAS:4000000\r\n"); + } + + ast_str_append(a_buf, 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_buf, 0, "a=rtcp-fb:* ccm fir\r\n"); + } + + ast_format_generate_sdp_fmtp(format, rtp_code, a_buf); + + if (ast_format_cmp(format, ast_format_h264) == AST_FORMAT_CMP_EQUAL && ast_test_flag(&pvt->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_buf, 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(const struct sip_pvt *pvt, struct ast_format *format, + struct ast_str **m_buf, struct ast_str **a_buf, int debug, int *min_packet_size) +{ + int rtp_code; + + if (!pvt->trtp) { + return; + } + + if (debug) { + ast_verb(3, "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(pvt->trtp), 1, format, 0)) == -1) { + return; + } + + ast_str_append(m_buf, 0, " %d", rtp_code); + ast_str_append(a_buf, 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(pvt->trtp), 1, ast_format_t140, 0); + + ast_str_append(a_buf, 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_noncodec(const struct sip_pvt *pvt, int format, struct ast_str **m_buf, struct ast_str **a_buf, int debug) +{ + int rtp_code; + + if (debug) { + ast_verb(3, "Adding non-codec 0x%x (%s) to SDP\n", (unsigned) format, ast_rtp_lookup_mime_subtype2(0, NULL, format, 0)); + } + + if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(pvt->rtp), 0, NULL, format)) == -1) { + return; + } + + ast_str_append(m_buf, 0, " %d", rtp_code); + ast_str_append(a_buf, 0, "a=rtpmap:%d %s/%u\r\n", rtp_code, + ast_rtp_lookup_mime_subtype2(0, NULL, format, 0), ast_rtp_lookup_sample_rate2(0, NULL, format)); + + if (format == AST_RTP_DTMF) { /* Indicate we support DTMF and FLASH... */ + ast_str_append(a_buf, 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 *instance, struct ast_str **a_buf) +{ + struct ast_rtp_engine_ice *ice = ast_rtp_instance_get_ice(instance); + const char *username, *password; + struct ao2_container *candidates; + struct ao2_iterator iter; + struct ast_rtp_engine_ice_candidate *candidate; + + /* If no ICE support is present we can't very well add the attributes */ + if (!ice || !(candidates = ice->get_local_candidates(instance))) { + return; + } + + if ((username = ice->get_ufrag(instance))) { + ast_str_append(a_buf, 0, "a=ice-ufrag:%s\r\n", username); + } + if ((password = ice->get_password(instance))) { + ast_str_append(a_buf, 0, "a=ice-pwd:%s\r\n", password); + } + + iter = ao2_iterator_init(candidates, 0); + + while ((candidate = ao2_iterator_next(&iter))) { + ast_str_append(a_buf, 0, "a=candidate:%s %u %s %d ", candidate->foundation, candidate->id, candidate->transport, candidate->priority); + ast_str_append(a_buf, 0, "%s ", ast_sockaddr_stringify_addr_remote(&candidate->address)); + ast_str_append(a_buf, 0, "%s typ ", ast_sockaddr_stringify_port(&candidate->address)); + + if (candidate->type == AST_RTP_ICE_CANDIDATE_TYPE_HOST) { + ast_str_append(a_buf, 0, "host"); + } else if (candidate->type == AST_RTP_ICE_CANDIDATE_TYPE_SRFLX) { + ast_str_append(a_buf, 0, "srflx"); + } else if (candidate->type == AST_RTP_ICE_CANDIDATE_TYPE_RELAYED) { + ast_str_append(a_buf, 0, "relay"); + } + + if (!ast_sockaddr_isnull(&candidate->relay_address)) { + ast_str_append(a_buf, 0, " raddr %s ", ast_sockaddr_stringify_addr_remote(&candidate->relay_address)); + ast_str_append(a_buf, 0, "rport %s", ast_sockaddr_stringify_port(&candidate->relay_address)); + } + + ast_str_append(a_buf, 0, "\r\n"); + ao2_ref(candidate, -1); + } + + ao2_iterator_destroy(&iter); + ao2_ref(candidates, -1); +} + diff -durN asterisk-22.2.0.orig/channels/sip/security_events.c asterisk-22.2.0/channels/sip/security_events.c --- asterisk-22.2.0.orig/channels/sip/security_events.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/security_events.c 2025-02-18 17:14:46.828902112 +1300 @@ -0,0 +1,337 @@ +/* + * 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/pvt.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_pvt *pvt) +{ + 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 = pvt->exten, + .common.local_addr = { + .addr = &pvt->ourip, + .transport = pvt->socket.type, + }, + .common.remote_addr = { + .addr = &pvt->sa, + .transport = pvt->socket.type, + }, + .common.session_id = session_id, + }; + + snprintf(session_id, sizeof(session_id), "%p", pvt); + ast_security_event_report(AST_SEC_EVT(&inval_acct_id)); +} + +void sip_report_failed_acl(const struct sip_pvt *pvt, const char *aclname) +{ + char session_id[32]; + struct ast_security_event_failed_acl failed_acl_event = { + .common.event_type = AST_SECURITY_EVENT_FAILED_ACL, + .common.version = AST_SECURITY_EVENT_FAILED_ACL_VERSION, + .common.service = "SIP", + .common.account_id = pvt->exten, + .common.local_addr = { + .addr = &pvt->ourip, + .transport = pvt->socket.type, + }, + .common.remote_addr = { + .addr = &pvt->sa, + .transport = pvt->socket.type, + }, + .common.session_id = session_id, + .acl_name = aclname, + }; + + snprintf(session_id, sizeof(session_id), "%p", pvt); + ast_security_event_report(AST_SEC_EVT(&failed_acl_event)); +} + +void sip_report_inval_password(const struct sip_pvt *pvt, 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 = pvt->exten, + .common.local_addr = { + .addr = &pvt->ourip, + .transport = pvt->socket.type, + }, + .common.remote_addr = { + .addr = &pvt->sa, + .transport = pvt->socket.type, + }, + .common.session_id = session_id, + + .challenge = pvt->nonce, + .received_challenge = response_challenge, + .received_hash = response_hash, + }; + + snprintf(session_id, sizeof(session_id), "%p", pvt); + ast_security_event_report(AST_SEC_EVT(&inval_password)); +} + +void sip_report_auth_success(const struct sip_pvt *pvt, 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 = pvt->exten, + .common.local_addr = { + .addr = &pvt->ourip, + .transport = pvt->socket.type, + }, + .common.remote_addr = { + .addr = &pvt->sa, + .transport = pvt->socket.type, + }, + .common.session_id = session_id, + .using_password = using_password, + }; + + snprintf(session_id, sizeof(session_id), "%p", pvt); + ast_security_event_report(AST_SEC_EVT(&successful_auth)); +} + +void sip_report_session_limit(const struct sip_pvt *pvt) +{ + 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 = pvt->exten, + .common.local_addr = { + .addr = &pvt->ourip, + .transport = pvt->socket.type, + }, + .common.remote_addr = { + .addr = &pvt->sa, + .transport = pvt->socket.type, + }, + .common.session_id = session_id, + }; + + snprintf(session_id, sizeof(session_id), "%p", pvt); + ast_security_event_report(AST_SEC_EVT(&session_limit)); +} + +void sip_report_chal_resp_failed(const struct sip_pvt *pvt, 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 = &pvt->ourip, + .transport = pvt->socket.type, + }, + .common.remote_addr = { + .addr = &pvt->sa, + .transport = pvt->socket.type, + }, + .common.session_id = session_id, + + .challenge = pvt->nonce, + .response = response, + .expected_response = expected_response, + }; + + if (!ast_strlen_zero(pvt->from)) { /* When dialing, show account making call */ + ast_copy_string(account_id, pvt->from, sizeof(account_id)); + } else { + ast_copy_string(account_id, pvt->exten, sizeof(account_id)); + } + + snprintf(session_id, sizeof(session_id), "%p", pvt); + ast_security_event_report(AST_SEC_EVT(&chal_resp_failed)); +} + +void sip_report_chal_sent(const struct sip_pvt *pvt) +{ + 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 = &pvt->ourip, + .transport = pvt->socket.type, + }, + .common.remote_addr = { + .addr = &pvt->sa, + .transport = pvt->socket.type, + }, + .common.session_id = session_id, + .challenge = pvt->nonce, + }; + + if (!ast_strlen_zero(pvt->from)) { /* When dialing, show account making call */ + ast_copy_string(account_id, pvt->from, sizeof(account_id)); + } else { + ast_copy_string(account_id, pvt->exten, sizeof(account_id)); + } + + snprintf(session_id, sizeof(session_id), "%p", pvt); + ast_security_event_report(AST_SEC_EVT(&chal_sent)); +} + +void sip_report_inval_transport(const struct sip_pvt *pvt, 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 = pvt->exten, + .common.local_addr = { + .addr = &pvt->ourip, + .transport = pvt->socket.type, + }, + .common.remote_addr = { + .addr = &pvt->sa, + .transport = pvt->socket.type, + }, + .common.session_id = session_id, + .transport = transport, + }; + + snprintf(session_id, sizeof(session_id), "%p", pvt); + ast_security_event_report(AST_SEC_EVT(&inval_transport)); +} + +int sip_report_security_event(const char *name, struct ast_sockaddr *addr, const struct sip_pvt *pvt, + const struct sip_request *req, const int res) +{ + + struct sip_peer *peer; + int auth_res = res; + char *reqheader, *respheader, *authtoken; + int result = 0; + struct sip_digest_keys keys[] = { + [SIP_DIGEST_RESPONSE] = {"response=", ""}, + [SIP_DIGEST_URI] = {"uri=", ""}, + [SIP_DIGEST_USERNAME] = {"username=", ""}, + [SIP_DIGEST_NONCE] = {"nonce=", ""}, + [SIP_DIGEST_LAST] = {NULL, NULL} + }; + + peer = sip_peer_find(name, addr, TRUE, FALSE, pvt->socket.type); + + switch(auth_res) { + case SIP_AUTH_DONT_KNOW: + break; + case SIP_AUTH_SUCCESSFUL: + if (peer) { + if (ast_strlen_zero(peer->secret) && ast_strlen_zero(peer->md5secret)) { + sip_report_auth_success(pvt, FALSE); + } else { + sip_report_auth_success(pvt, TRUE); + } + } + + break; + case SIP_AUTH_CHALLENGE_SENT: + sip_report_chal_sent(pvt); + break; + case SIP_AUTH_SECRET_FAILED: + case SIP_AUTH_USERNAME_MISMATCH: + sip_auth_headers(SIP_WWW_AUTH, &respheader, &reqheader); + authtoken = ast_strdupa(sip_request_get_header(req, reqheader)); + + sip_parse_digest(authtoken, keys); + + if (auth_res == SIP_AUTH_SECRET_FAILED) { + sip_report_inval_password(pvt, keys[SIP_DIGEST_NONCE].value, keys[SIP_DIGEST_RESPONSE].value); + } else { + if (peer) { + sip_report_chal_resp_failed(pvt, keys[SIP_DIGEST_USERNAME].value, peer->username); + } + } + + break; + case SIP_AUTH_NOT_FOUND: + /* with sip_config.alwaysauthreject on, generates 2 events */ + sip_report_inval_acct(pvt); + break; + case SIP_AUTH_UNKNOWN_DOMAIN: + sip_report_failed_acl(pvt, "domain_must_match"); + break; + case SIP_AUTH_PEER_NOT_DYNAMIC: + sip_report_failed_acl(pvt, "peer_not_dynamic"); + break; + case SIP_AUTH_ACL_FAILED: + /* with sip_config.alwaysauthreject on, generates 2 events */ + sip_report_failed_acl(pvt, "device_must_match_acl"); + break; + case SIP_AUTH_BAD_TRANSPORT: + sip_report_inval_transport(pvt, ast_transport2str(req->socket.type)); + break; + case SIP_AUTH_RTP_FAILED: + break; + case SIP_AUTH_SESSION_LIMIT: + sip_report_session_limit(pvt); + break; + } + + if (peer) { + ao2_t_cleanup(peer, "sip_report_security_event: ao2_t_cleanup: from handle_incoming"); + } + + return result; +} diff -durN asterisk-22.2.0.orig/channels/sip/stimer.c asterisk-22.2.0/channels/sip/stimer.c --- asterisk-22.2.0.orig/channels/sip/stimer.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/stimer.c 2025-02-18 17:14:46.828902112 +1300 @@ -0,0 +1,461 @@ +/* + * 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/pvt.h" +#include "include/config.h" +#include "include/utils.h" +#include "include/manager.h" + +static int __sip_stimer_start(const void *data); +static struct sip_stimer *sip_stimer_alloc(struct sip_pvt *pvt); + +/* Process session refresh timeout event */ +int sip_stimer_timeout(const void *data) +{ + struct sip_pvt *pvt = (struct sip_pvt *) data; + struct sip_stimer *stimer = pvt->stimer; + int res = 0; + + ast_assert(stimer != NULL); + ast_debug(2, "Session timer expired: %d - %s\n", stimer->schedid, pvt->callid); + + if (!pvt->owner) { + goto error; + } + + if (!stimer->active || ast_channel_state(pvt->owner) != AST_STATE_UP) { + goto error; + } + + if (stimer->refresher == SIP_STIMER_REFRESHER_US) { + res = 1; + + if (pvt->t38.state == SIP_T38_ENABLED) { + sip_send_reinvite_with_sdp(pvt, TRUE, TRUE); + } else { + sip_send_reinvite_with_sdp(pvt, FALSE, TRUE); + } + } else { + struct ast_channel *owner; + + ast_log(LOG_WARNING, "Session-Timer expired - %s\n", pvt->callid); + + if ((owner = sip_pvt_lock_full(pvt))) { + sip_publish_session_timeout(owner, "SIPSessionTimer"); + ast_softhangup_nolock(owner, AST_SOFTHANGUP_DEV); + + ast_channel_unlock(owner); + ast_channel_unref(owner); + } + + ao2_unlock(pvt); + } + +error: + if (!res) { + /* Session timer processing is no longer needed. */ + ast_debug(2, "Session timer stopped: %d - %s\n", stimer->schedid, pvt->callid); + + /* Don't pass go, don't collect $200.. we are the scheduled callback. We can rip ourself out here. */ + stimer->schedid = -1; + stimer->active = FALSE; + + /* If we are not asking to be rescheduled, then we need to release our reference to the dialog. */ + ao2_t_cleanup(pvt, "Session timer st_schedid complete"); + } + + return res; +} + +/* Run by the sched thread. */ +int __sip_stimer_stop(const void *data) +{ + struct sip_pvt *pvt = (void *) data; + struct sip_stimer *stimer = pvt->stimer; + + if (stimer->schedid > -1) { + ast_debug(2, "Session timer stopped: %d - %s\n", stimer->schedid, pvt->callid); + + AST_SCHED_DEL_UNREF(sip_sched_context, stimer->schedid, ao2_t_cleanup(pvt, "Stop scheduled session timer st_schedid")); + } + + ao2_t_cleanup(pvt, "Stop session timer action"); + + return 0; +} + +/* Stop session timer */ +void sip_stimer_stop(struct sip_pvt *pvt) +{ + struct sip_stimer *stimer = pvt->stimer; + + stimer->active = FALSE; + ao2_t_bump(pvt, "Stop session timer action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_stimer_stop, pvt) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(pvt, "Failed to schedule stop session timer action"); + } +} + +/* Run by the sched thread. */ +static int __sip_stimer_start(const void *data) +{ + struct sip_pvt *pvt = (void *) data; + struct sip_stimer *stimer = pvt->stimer; + unsigned int timeout_ms; + + /* 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_ms = stimer->interval * 1000; + + if (stimer->refresher == SIP_STIMER_REFRESHER_US) { + timeout_ms /= 2; + } else { + timeout_ms -= MIN(timeout_ms / 3, 32000); + } + + /* in the event a timer is already going, stop it */ + ao2_t_bump(pvt, "Schedule session timer st_schedid"); + stimer->schedid = ast_sched_add(sip_sched_context, timeout_ms, sip_stimer_timeout, pvt); + + if (stimer->schedid < 0) { + ao2_t_cleanup(pvt, "Failed to schedule session timer st_schedid"); + } else { + ast_debug(2, "Session timer started: %d - %s %ums\n", stimer->schedid, pvt->callid, timeout_ms); + } + + ao2_t_cleanup(pvt, "Start session timer action"); + + return 0; +} + +/* Start session timer */ +void sip_stimer_start(struct sip_pvt *pvt) +{ + struct sip_stimer *stimer = pvt->stimer; + + stimer->active = TRUE; + ao2_t_bump(pvt, "Start session timer action"); + + if (ast_sched_add(sip_sched_context, 0, __sip_stimer_start, pvt) < 0) { + /* Uh Oh. Expect bad behavior. */ + ao2_t_cleanup(pvt, "Failed to schedule start session timer action"); + } +} + +/* Restart session timer */ +void sip_stimer_restart(struct sip_pvt *pvt) +{ + if (pvt->stimer->active == TRUE) { + sip_stimer_start(pvt); + } +} + +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_US: + return "us"; + case SIP_STIMER_REFRESHER_THEM: + return "them"; + default: + break; + } + + return "unknown"; +} + +/* Allocate Session-Timers struct w/in dialog */ +static struct sip_stimer *sip_stimer_alloc(struct sip_pvt *pvt) +{ + if (pvt->stimer) { + ast_log(LOG_ERROR, "Session-Timer struct already allocated\n"); + return pvt->stimer; + } + + if (!(pvt->stimer = ast_calloc(1, sizeof(*pvt->stimer)))) { + return NULL; + } + + pvt->stimer->schedid = -1; /* Session-Timers ast_sched scheduler id */ + pvt->stimer->active = FALSE; + + return pvt->stimer; +} + +/* Check Session Timers for an INVITE request */ +int sip_stimer_handle_invite(struct sip_pvt *pvt, struct sip_request *req, int reinvite) +{ + const char *expires; + int max_se = -1; /* UAC's Session-Expires in integer format */ + int min_se = -1; /* UAC's Min-SE in integer format */ + int active = FALSE; /* Session-Timer on/off boolean */ + int interval = 0; /* Session-Timer negotiated refresh interval */ + int refresher = SIP_STIMER_REFRESHER_AUTO; /* Session-Timer refresher */ + int pvt_min_se = -1; + int pvt_max_se = sip_config.max_se; + int res; + + /* Session-Timers */ + if ((pvt->sipoptions & SIP_OPT_TIMER)) { + /* The UAC has requested session-timers for this session. Negotiate the session refresh interval and who will + * be the refresher */ + ast_debug(2, "Incoming INVITE with 'timer' option supported\n"); + + /* Allocate Session-Timers struct w/in the dialog */ + if (!pvt->stimer) { + sip_stimer_alloc(pvt); + } + + /* Parse the Session-Expires header */ + expires = sip_request_get_header(req, "Session-Expires"); + + if (!ast_strlen_zero(expires)) { + ast_debug(2, "INVITE also has \"Session-Expires\" header.\n"); + res = sip_parse_session_expires(expires, &max_se, &refresher); + + if (res) { + sip_send_response_reliable(pvt, "400 Session-Expires Invalid Syntax", req); + return -1; + } + + refresher = refresher == SIP_STIMER_REFRESHER_US ? SIP_STIMER_REFRESHER_THEM : SIP_STIMER_REFRESHER_US; + } + + /* Parse the Min-SE header */ + expires = sip_request_get_header(req, "Min-SE"); + + if (!ast_strlen_zero(expires)) { + ast_debug(2, "INVITE also has \"Min-SE\" header.\n"); + + if (sip_parse_min_se(expires, &min_se)) { + sip_send_response_reliable(pvt, "400 Min-SE Invalid Syntax", req); + return -1; + } + } + + pvt_min_se = sip_stimer_get_expiry(pvt, FALSE); + + switch (sip_stimer_get_mode(pvt, TRUE)) { + case SIP_STIMER_MODE_ACCEPT: + case SIP_STIMER_MODE_ORIGINATE: + if (max_se > 0 && max_se < pvt_min_se) { + sip_send_response_with_min_se(pvt, "422 Session Interval Too Small", req, pvt_min_se); + return -1; + } + + pvt->stimer->peer_active = TRUE; + active = TRUE; + + if (refresher == SIP_STIMER_REFRESHER_AUTO) { + refresher = sip_stimer_get_refresher(pvt); + } + + pvt_max_se = sip_stimer_get_expiry(pvt, TRUE); + + if (max_se > 0) { + if (pvt_max_se >= min_se) { + interval = max_se < pvt_max_se ? max_se : pvt_max_se; + } else { + interval = max_se; + } + } else if (min_se > 0) { + interval = pvt_max_se > min_se ? pvt_max_se : min_se; + } else { + interval = max_se; + } + + break; + case SIP_STIMER_MODE_REFUSE: + if (pvt->reqsipoptions & SIP_OPT_TIMER) { + sip_send_response_with_unsupported(pvt, "420 Option Disabled", req, "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 in sip.conf) whether + * to run session-timers for this session or not. */ + switch (sip_stimer_get_mode(pvt, TRUE)) { + case SIP_STIMER_MODE_ORIGINATE: + active = TRUE; + interval = sip_stimer_get_expiry(pvt, TRUE); + + refresher = SIP_STIMER_REFRESHER_US; + pvt->stimer->peer_active = !!(pvt->sipoptions & SIP_OPT_TIMER); + + break; + default: + break; + } + } + + if (!reinvite) { + /* Start session refresh timer based on negotiation/config */ + if (active) { + pvt->stimer->active = TRUE; + pvt->stimer->interval = interval; + pvt->stimer->refresher = refresher; + } + } else if (pvt->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 - %s\n", pvt->callid); + + /* The UAC may be adjusting the session-timers mid-session */ + if (interval > 0) { + pvt->stimer->interval = interval; + pvt->stimer->refresher = refresher; + } + } + + return 0; +} + +/* Get Max or Min SE (session timer expiry */ +int sip_stimer_get_expiry(struct sip_pvt *pvt, int max) +{ + if (max == TRUE) { + if (pvt->stimer->cached_max_se) { + return pvt->stimer->cached_max_se; + } + + if (pvt->relatedpeer) { + pvt->stimer->cached_max_se = pvt->relatedpeer->stimer.max_se; + return pvt->stimer->cached_max_se; + } + + pvt->stimer->cached_max_se = sip_config.max_se; + + return pvt->stimer->cached_max_se; + } + + /* Find Min SE timer */ + if (pvt->stimer->cached_min_se) { + return pvt->stimer->cached_min_se; + } + + if (pvt->relatedpeer) { + pvt->stimer->cached_min_se = pvt->relatedpeer->stimer.min_se; + + return pvt->stimer->cached_min_se; + } + + pvt->stimer->cached_min_se = sip_config.min_se; + + return pvt->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_pvt *pvt) +{ + if (pvt->stimer->cached_refresher != SIP_STIMER_REFRESHER_AUTO) { + return pvt->stimer->cached_refresher; + } + + if (pvt->relatedpeer) { + pvt->stimer->cached_refresher = pvt->relatedpeer->stimer.refresher == SIP_STIMER_REFRESHER_US + ? SIP_STIMER_REFRESHER_THEM : SIP_STIMER_REFRESHER_US; + + return pvt->stimer->cached_refresher; + } + + pvt->stimer->cached_refresher = sip_config.stimer_refresher == SIP_STIMER_REFRESHER_US + ? SIP_STIMER_REFRESHER_THEM : SIP_STIMER_REFRESHER_US; + + return pvt->stimer->cached_refresher; +} + +/* Get the session-timer mode */ +int sip_stimer_get_mode(struct sip_pvt *pvt, int no_cached) +{ + if (!pvt->stimer) { + sip_stimer_alloc(pvt); + + if (!pvt->stimer) { + return SIP_STIMER_MODE_INVALID; + } + } + + if (!no_cached && pvt->stimer->cached_mode != SIP_STIMER_MODE_INVALID) { + return pvt->stimer->cached_mode; + } + + if (pvt->relatedpeer) { + pvt->stimer->cached_mode = pvt->relatedpeer->stimer.mode; + + return pvt->stimer->cached_mode; + } + + pvt->stimer->cached_mode = sip_config.stimer_mode; + + return sip_config.stimer_mode; +} diff -durN asterisk-22.2.0.orig/channels/sip/utils.c asterisk-22.2.0/channels/sip/utils.c --- asterisk-22.2.0.orig/channels/sip/utils.c 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/channels/sip/utils.c 2025-02-18 17:14:46.829902085 +1300 @@ -0,0 +1,3036 @@ +/* + * 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/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/pvt.h" +#include "include/utils.h" +#include "include/config.h" + +static int sip_uri_params_cmp(const char *input1, const char *input2); +static int sip_uri_headers_cmp(const char *input1, const char *input2); +static int sip_uri_domain_cmp(const char *host1, const char *host2); + +/* The core structure to setup dialogs. We parse incoming messages by using structure and then route the messages according to the type. + * Note that sip_methods[i].id == i must hold or the code breaks */ +const struct sip_methods sip_methods[] = { + {SIP_RESPONSE, FALSE, "SIP/2.0", FALSE}, + {SIP_REGISTER, FALSE, "REGISTER", TRUE}, + {SIP_OPTIONS, FALSE, "OPTIONS", TRUE}, + {SIP_NOTIFY, FALSE, "NOTIFY", TRUE}, + {SIP_INVITE, TRUE, "INVITE", TRUE}, + {SIP_ACK, FALSE, "ACK", FALSE}, + {SIP_PRACK, FALSE, "PRACK", FALSE}, + {SIP_BYE, FALSE, "BYE", FALSE}, + {SIP_REFER, FALSE, "REFER", TRUE}, + {SIP_SUBSCRIBE, FALSE, "SUBSCRIBE", TRUE}, + {SIP_MESSAGE, FALSE, "MESSAGE", TRUE}, + {SIP_UPDATE, FALSE, "UPDATE", FALSE}, + {SIP_INFO, FALSE, "INFO", FALSE}, + {SIP_CANCEL, FALSE, "CANCEL", FALSE}, + {SIP_PUBLISH, FALSE, "PUBLISH", TRUE}, + {SIP_PING, FALSE, "PING", FALSE}, + {SIP_UNKNOWN, TRUE, "", TRUE}, +}; + +const struct sip_options sip_options[] = { + {SIP_OPT_100REL, FALSE, "100rel"}, /* RFC3262: PRACK 100% reliability */ + {SIP_OPT_EARLY_SESSION, FALSE, "early-session"}, /* RFC3959: SIP Early session support */ + {SIP_OPT_EVENTLIST, FALSE, "eventlist"}, /* SIMPLE events: RFC4662 */ + {SIP_OPT_FROMCHANGE, FALSE, "from-change"}, /* RFC 4916- Connected line ID updates */ + {SIP_OPT_GRUU, FALSE, "gruu"}, /* GRUU: Globally Routable User Agent URI's */ + {SIP_OPT_HISTINFO, FALSE, "histinfo"}, /* RFC4244 History info */ + {SIP_OPT_JOIN, FALSE, "join"}, /* RFC3911: SIP Join header support */ + {SIP_OPT_NOREFERSUB, FALSE, "norefersub"}, /* Disable the REFER subscription, RFC 4488 */ + {SIP_OPT_OUTBOUND, FALSE, "outbound"}, /* SIP outbound - the final NAT battle - draft-sip-outbound */ + {SIP_OPT_PATH, FALSE, "path"}, /* RFC3327: Path support */ + {SIP_OPT_PREF, FALSE, "pref"}, /* RFC3840: Callee preferences */ + {SIP_OPT_PRECONDITION, FALSE, "precondition"}, /* RFC3312: Precondition support */ + {SIP_OPT_PRIVACY, FALSE, "privacy"}, /* RFC3323: Privacy with proxies*/ + {SIP_OPT_RECLISTINV, FALSE, "recipient-list-invite"}, /* RFC-ietf-sip-uri-list-conferencing-02.txt conference invite lists */ + {SIP_OPT_RECLISTSUB, FALSE, "recipient-list-subscribe"}, /* RFC-ietf-sip-uri-list-subscribe-02.txt - subscription lists */ + {SIP_OPT_REPLACES, TRUE, "replaces"}, /* RFC3891: Replaces: header for transfer */ + {SIP_OPT_RESPRIORITY, FALSE, "resource-priority"}, /* RFC4412 Resource priorities */ + {SIP_OPT_SEC_AGREE, FALSE, "sec_agree"}, /* RFC3329: Security agreement mechanism */ + {SIP_OPT_SDP_ANAT, FALSE, "sdp-anat"}, /* RFC4092: Usage of the SDP ANAT Semantics in the SIP */ + {SIP_OPT_TIMER, TRUE, "timer"}, /* RFC4028: SIP Session-Timers */ + {SIP_OPT_TARGET_DIALOG, FALSE, "tdialog"}, /* RFC4538: Target-dialog */ + {SIP_OPT_UNKNOWN, FALSE, ""}, +}; + +/* A per-thread buffer for transport to string conversion */ +AST_THREADSTORAGE(sip_transports2str_buf); + +/* Determine if the given string is a SIP token. A token is defined by RFC3261 Section 25.1 */ +int sip_is_token(const char *str) +{ + int is_token; + + if (ast_strlen_zero(str)) { + /* An empty string is not a token. */ + return 0; + } + + is_token = TRUE; + + do { + if (!isalnum(*str) && !strchr("-.!%*_+`'~", *str)) { + /* The character is not allowed in a token. */ + is_token = FALSE; + break; + } + } while (*++str); + + return is_token; +} + +/* Get caller id name from SIP headers, copy into output buffer */ +const char *sip_get_name(const char *input, char *output, size_t outputsize) +{ + /* 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 */ + char *orig_output = output; + const char *orig_input = input; + + if (!output || !outputsize) { + /* Bad output parameters. Should never happen. */ + return input; + } + + /* clear any empty characters in the beginning */ + input = ast_skip_blanks(input); + + /* make sure the output buffer is initialized */ + *orig_output = '\0'; + + /* make room for '\0' at the end of the output buffer */ + --outputsize; + + /* no data at all or no display name? */ + if (!input || *input == '<') { + return input; + } + + /* quoted-string rules */ + if (*input == '"') { + input++; /* skip the first " */ + + for (; *input; ++input) { + if (*input == '"') { /* end of quoted-string */ + break; + } else if (*input == 0x5c) { /* quoted-pair = "\" (%x00-09 / %x0B-0C / %x0E-7F) */ + ++input; + + if (!*input) { + break; + } + + if ((unsigned char) *input > 0x7f || *input == 0xa || *input == 0xd) { + continue; /* not a valid quoted-pair, so skip it */ + } + } else if ((*input != 0x9 && (unsigned char) *input < 0x20) || *input == 0x7f) { + continue; /* skip this invalid character. */ + } + + if (outputsize > 0) { + /* We still have room for the output display-name. */ + *output++ = *input; + --outputsize; + } + } + + /* if this is successful, input should be at the ending quote */ + if (*input != '"') { + ast_log(LOG_WARNING, "No ending quote for display-name was found\n"); + *orig_output = '\0'; + + return orig_input; + } + + /* make sure input is past the last quote */ + ++input; + + /* terminate output */ + *output = '\0'; + } else { /* either an addr-spec or tokenLWS-combo */ + for (; *input; ++input) { + /* token or WSP (without LWS) */ + if ((*input >= '0' && *input <= '9') || (*input >= 'A' && *input <= 'Z') + || (*input >= 'a' && *input <= 'z') || *input == '-' || *input == '.' + || *input == '!' || *input == '%' || *input == '*' || *input == '_' + || *input == '+' || *input == '`' || *input == '\'' || *input == '~' + || *input == 0x9 || *input == ' ') { + if (outputsize > 0) { + /* We still have room for the output display-name. */ + *output++ = *input; + --outputsize; + } + } else if (*input == '<') { /* end of tokenLWS-combo */ + /* we could assert that the previous char is LWS, but we don't care */ + break; + } else if (*input == ':') { + /* This invalid character which indicates this is addr-spec rather than display-name. */ + *orig_output = '\0'; + + return orig_input; + } else { /* else, invalid character we can skip. */ + continue; /* skip this character */ + } + } + + if (*input != '<') { /* if we never found the start of addr-spec then this is invalid */ + *orig_output = '\0'; + + return orig_input; + } + + /* terminate output while trimming any trailing whitespace */ + do { + *output-- = '\0'; + } while (orig_output <= output && (*output == 0x9 || *output == ' ')); + } + + return input; +} + +int sip_get_name_and_number(const char *header, char **name, char **number) +{ + char tmp_name[256]; + char *tmp_header, *tmp_number = NULL, *hostport = NULL; + + if (!name || !number || ast_strlen_zero(header)) { + return -1; + } + + *name = NULL; + *number = NULL; + tmp_header = ast_strdupa(header); + + /* strip the display-name portion off the beginning of the header. */ + sip_get_name(tmp_header, tmp_name, sizeof(tmp_name)); + + /* get uri within < > brackets */ + tmp_header = sip_get_in_brackets(tmp_header); + + /* parse out the number here */ + if (sip_uri_parse(tmp_header, "sip:,sips:", &tmp_number, NULL, &hostport, NULL) || ast_strlen_zero(tmp_number)) { + ast_log(LOG_ERROR, "can not parse name and number from sip header.\n"); + return -1; + } + + /* number is not option, and must be present at this point */ + *number = ast_strdup(tmp_number); + ast_uri_decode(*number, ast_uri_sip_user); + + /* name is optional and may not be present at this point */ + if (!ast_strlen_zero(tmp_name)) { + *name = ast_strdup(tmp_name); + } + + return 0; +} + +/* Extract domain from SIP To/From header */ +int sip_get_domain(const char *header, char *domain, int len) +{ + char *from, *end; + + *domain = '\0'; + + from = ast_strdupa(header); + from = sip_get_in_brackets(from); + + if (!ast_strlen_zero(from)) { + if (strncasecmp(from, "sip:", 4)) { + ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", from); + return -1; + } + + from += 4; + } else { + from = NULL; + } + + if (from) { + int bracket = 0; + + /* Strip any params or options from user */ + if ((end = strchr(from, ';'))) { + *end = '\0'; + } + + /* Strip port from domain if present */ + for (end = from; *end; end++) { + if (*end == ':' && bracket == 0) { + *end = '\0'; + break; + } else if (*end == '[') { + ++bracket; + } else if (*end == ']') { + --bracket; + } + } + + if ((end = strchr(from, '@'))) { + *end = '\0'; + ast_copy_string(domain, end + 1, len); + } else { + ast_copy_string(domain, from, len); + } + } + + return ast_strlen_zero(domain); +} + +/* Locate closing quote in a string, skipping escaped quotes. optionally with a limit on the search. start must be past the first quote. */ +const char *sip_find_quote(const char *start, const char *end) +{ + char prev = '\0'; + const char *next; + + for (next = start; *next && next != end; prev = *next++) { + if (*next == '"' && prev != '\\') { + break; + } + } + + return next; +} + +char *sip_get_in_brackets(char *parse) +{ + char *bracket; + + /* Skip any quoted text until we find the part in brackets. On any error give up and return -1 */ + while ((bracket = strchr(parse, '<'))) { + char *quote = strchr(parse, '"'); + + bracket++; + + if (!quote || quote >= bracket) { + break; /* no need to look at quoted part */ + } + + /* the bracket is within quotes, so ignore it */ + parse = (char *) sip_find_quote(quote + 1, NULL); + + if (!*parse) { + ast_log(LOG_WARNING, "No closing quote found in '%s'\n", parse); + return ""; + } + + parse++; + } + + /* If no first bracket then still look for a second bracket as some other parsing functions may overwrite first bracket with NULL when + * terminating a token based display-name. As this only affects token based display-names there is no danger of brackets being in quotes */ + if (bracket) { + parse = bracket; + } + + if ((bracket = strchr(parse, '>'))) { + *bracket++ = '\0'; + } + + return parse; +} + +/* Returns true if 'name' (with optional trailing whitespace) matches the sip method 'id'. + * Strictly speaking, SIP methods are case SENSITIVE, but we do a case-insensitive comparison to be more tolerant. */ +int sip_method_cmp(int id, const char *name) +{ + int len = strlen(sip_methods[id].name); + int namelen = name ? strlen(name) : 0; + + /* true if the string is long enough, and ends with whitespace, and matches */ + + return (namelen >= len && name && name[len] < 33 && !strncasecmp(sip_methods[id].name, name, len)); +} + +/* sip_method_find: Find SIP method from header */ +int sip_method_find(const char *msg) +{ + int i, res = SIP_UNKNOWN; + + if (ast_strlen_zero(msg)) { + return 0; + } + + for (i = 0; sip_methods[i].id != SIP_UNKNOWN; i++) { + if (sip_method_cmp(i, msg)) { + res = sip_methods[i].id; + } + } + + return res; +} + +void sip_method_set_allowed(unsigned int *allowed_methods, int method) +{ + (*allowed_methods) |= (1 << method); +} + +void sip_method_set_disallowed(unsigned int *allowed_methods, int method) +{ + (*allowed_methods) &= ~(1 << method); +} + +/* Check if method is allowed for a device or a dialog */ +int sip_method_allowed(unsigned int *allowed_methods, int method) +{ + return ((*allowed_methods) >> method) & 1; +} + +void sip_method_parse(unsigned int *methods, const char *header) +{ + char *method; + char *tmp = ast_strdupa(header); + + for (method = strsep(&tmp, ","); !ast_strlen_zero(method); method = strsep(&tmp, ",")) { + int id = sip_method_find(ast_skip_blanks(method)); + + if (id == SIP_UNKNOWN) { + continue; + } + + sip_method_set_allowed(methods, id); + } +} + +/* Return the request and response header for a 401 or 407 code */ +void sip_auth_headers(int resp, char **reqheader, char **respheader) +{ + if (resp == SIP_WWW_AUTH) { /* 401 */ + if (reqheader) { + *reqheader = "WWW-Authenticate"; + } + + if (respheader) { + *respheader = "Authorization"; + } + } else if (resp == SIP_PROXY_AUTH) { /* 407 */ + if (reqheader) { + *reqheader = "Proxy-Authenticate"; + } + + if (respheader) { + *respheader = "Proxy-Authorization"; + } + } else { + ast_verb(3, "-- wrong response code %u\n", resp); + + if (reqheader) { + *reqheader = "Invalid"; + } + + if (respheader) { + *respheader = "Invalid"; + } + } +} + +/* Parses a URI in its components. */ +int sip_uri_parse(char *uri, const char *schemes, char **user, char **password, char **hostport, char **transport) +{ + char *sep, *params; + const char *scheme; + int len; + + /* check for valid input */ + if (ast_strlen_zero(uri)) { + return -1; + } + + /* Initialize requested strings - some functions don't care if sip_uri_parse fails and will attempt to use string pointers passed into + * sip_uri_parse even after a sip_uri_parse failure */ + if (user) { + *user = ""; + } + + if (password) { + *password = ""; + } + + if (hostport) { + *hostport = ""; + } + + if (transport) { + *transport = ""; + } + + if (schemes) { + scheme = schemes; + + for (;;) { + if ((sep = strchr(scheme, ','))) { + len = sep - scheme; + sep += 1; + } else { + len = strlen(scheme); + } + + if (!strncasecmp(uri, scheme, len)) { + uri += 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'; + params = sep; + + if (hostport) { + *hostport = sep; + } + + if ((sep = strchr(uri, ':'))) { + *sep++ = '\0'; + + if (user) { + *user = uri; + } + + if (password) { + *password = sep; + } + } else if (user) { + *user = uri; + } + } else { + if (hostport) { + *hostport = uri; + } + + params = uri; + } + + if ((sep = strchr(params, ';'))) { + *sep++ = '\0'; + params = sep; + + if ((sep = strchr(params, '?'))) { + *sep++ = '\0'; + } + + while (!ast_strlen_zero(params)) { + if ((sep = strchr(params, ';'))) { + *sep++ = '\0'; + } + + if (!strncasecmp(params, "transport=", 10)) { + if (transport) { + *transport = params + 10; + } + } + + params = sep; + } + } + + return 0; +} + +/* Terminate the uri at the first ';' or space. Technically we should ignore escaped space per RFC3261 (19.1.1 etc) but don't do it for the time being. + * Remember the uri format is: (User-parameters was added after RFC 3261) + * + * sip:user:password;user-parameters@host:port;uri-parameters?headers + * sips:user:password;user-parameters@host:port;uri-parameters?headers + * + * As this function does not support user-parameters, it's considered broken and needs fixing. */ +char *sip_uri_terminate(char *uri) +{ + char *end = uri; + + while (*end && *end > ' ' && *end != ';') { + end++; + } + + *end = '\0'; + + return uri; +} + +/* Remove URI parameters at end of URI, not in username part though */ +char *sip_uri_remove_parameters(char *uri) +{ + char *sep; + + if (!(sep = strchr(uri, '@'))) { /* First, locate the at sign */ + sep = uri; /* Ok hostname only, let's stick with the rest */ + } + + if ((sep = strchr(sep, ';'))) { /* Locate semi colon */ + *sep = '\0'; /* Kill at the semi colon */ + } + + return uri; +} + +/*! Helper routine for sip_uri_cmp 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_uri_params_cmp(const char *input1, const char *input2) +{ + char *params1 = NULL; + char *params2 = NULL; + char *pos1; + char *pos2; + int zerolength1 = FALSE; + int zerolength2 = FALSE; + int maddrmatch = FALSE; + int ttlmatch = FALSE; + int usermatch = FALSE; + int methodmatch = FALSE; + + if (ast_strlen_zero(input1)) { + zerolength1 = TRUE; + } else { + params1 = ast_strdupa(input1); + } + + if (ast_strlen_zero(input2)) { + zerolength2 = TRUE; + } else { + params2 = ast_strdupa(input2); + } + + /* Quick optimization. If both params are zero-length, then they match */ + if (zerolength1 && zerolength2) { + return 0; + } + + for (pos1 = strsep(¶ms1, ";"); pos1; pos1 = strsep(¶ms1, ";")) { + char *value1 = pos1; + char *name1 = strsep(&value1, "="); + char *params2dup = NULL; + int matched = FALSE; + + if (!value1) { + value1 = ""; + } + + /* Checkpoint reached. We have the name and value parsed for param1 We have to duplicate params2 each time through this loop or + * else the inner loop below will not work properly. */ + if (!zerolength2) { + params2dup = ast_strdupa(params2); + } + + for (pos2 = strsep(¶ms2dup, ";"); pos2; pos2 = strsep(¶ms2dup, ";")) { + char *name2 = pos2; + char *value2 = strchr(pos2, '='); + + if (!value2) { + value2 = ""; + } else { + *value2++ = '\0'; + } + + if (!strcasecmp(name1, name2)) { + if (strcasecmp(value1, value2)) { + return 1; + } else { + matched = TRUE; + break; + } + } + } + + /* Check to see if the parameter is one of the 'must-match' parameters */ + if (!strcasecmp(name1, "maddr")) { + if (matched) { + maddrmatch = TRUE; + } else { + return 1; + } + } else if (!strcasecmp(name1, "ttl")) { + if (matched) { + ttlmatch = TRUE; + } else { + return 1; + } + } else if (!strcasecmp(name1, "user")) { + if (matched) { + usermatch = TRUE; + } else { + return 1; + } + } else if (!strcasecmp(name1, "method")) { + if (matched) { + methodmatch = TRUE; + } else { + return 1; + } + } + } + + /* 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 params2 could have + * an maddr, ttl, user, or method header and params1 did not. */ + for (pos2 = strsep(¶ms2, ";"); pos2; pos2 = strsep(¶ms2, ";")) { + char *value2 = pos2; + char *name2 = strsep(&value2, "="); + + if (!value2) { + value2 = ""; + } + + if ((!strcasecmp(name2, "maddr") && !maddrmatch) || (!strcasecmp(name2, "ttl") && !ttlmatch) || + (!strcasecmp(name2, "user") && !usermatch) || (!strcasecmp(name2, "method") && !methodmatch)) { + return 1; + } + } + + return 0; +} + +/* Helper routine for sip_uri_cmp 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_uri_headers_cmp(const char *input1, const char *input2) +{ + char *headers1 = NULL; + char *headers2 = NULL; + int zerolength1 = FALSE; + int zerolength2 = FALSE; + int different = FALSE; + char *header1; + + if (ast_strlen_zero(input1)) { + zerolength1 = TRUE; + } else { + headers1 = ast_strdupa(input1); + } + + if (ast_strlen_zero(input2)) { + zerolength2 = TRUE; + } else { + headers2 = ast_strdupa(input2); + } + + /* If one URI contains no headers and the other does, then they cannot possibly match */ + if (zerolength1 != zerolength2) { + return 1; + } + + if (zerolength1 && zerolength2) { + 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)) { + different = TRUE; + break; + } + } + + return different; +} + +/* 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_uri_domain_cmp(const char *host1, const char *host2) +{ + struct ast_sockaddr addr1; + struct ast_sockaddr addr2; + int addr1_parsed; + int addr2_parsed; + + addr1_parsed = ast_sockaddr_parse(&addr1, host1, 0); + addr2_parsed = ast_sockaddr_parse(&addr2, host2, 0); + + if (addr1_parsed != addr2_parsed) { + /* One domain was an IP address and the other had a host name. FAIL! */ + return 1; + } + + /* Both are host names. A string comparison will work perfectly here */ + if (!addr1_parsed) { + return strcasecmp(host1, host2); + } + + /* Both contain IP addresses */ + return ast_sockaddr_cmp(&addr1, &addr2); +} + +int sip_uri_cmp(const char *input1, const char *input2) +{ + char *uri1; + char *uri2; + char *uri_scheme1; + char *uri_scheme2; + char *host1; + char *host2; + char *params1; + char *params2; + char *headers1; + char *headers2; + + if (!input1 || !input2) { + return 1; + } + + uri1 = ast_strdupa(input1); + uri2 = ast_strdupa(input2); + + ast_uri_decode(uri1, ast_uri_sip_user); + ast_uri_decode(uri2, ast_uri_sip_user); + + uri_scheme1 = strsep(&uri1, ":"); + uri_scheme2 = strsep(&uri2, ":"); + + if (strcmp(uri_scheme1, uri_scheme2)) { + return 1; + } + + /* This function is tailored for SIP and SIPS URIs. There's no need to check uri_scheme2 since we have determined uri_scheme1 and + * uri_scheme2 are equivalent already. */ + if (strcmp(uri_scheme1, "sip") && strcmp(uri_scheme1, "sips")) { + return 1; + } + + if (ast_strlen_zero(uri1) || ast_strlen_zero(uri2)) { + return 1; + } + + if ((host1 = strchr(uri1, '@'))) { + *host1++ = '\0'; + } + + if ((host2 = strchr(uri2, '@'))) { + *host2++ = '\0'; + } + + /* Check for mismatched username and passwords. This is the only case-sensitive comparison of a SIP URI */ + if ((host1 && !host2) || (host2 && !host1) || (host1 && host2 && strcmp(uri1, uri2))) { + return 1; + } + + if (!host1) { + host1 = uri1; + } + + if (!host2) { + host2 = uri2; + } + + /* Strip off the parameters and headers so we can compare host and port */ + if ((params1 = strchr(host1, ';'))) { + *params1++ = '\0'; + } + + if ((params2 = strchr(host2, ';'))) { + *params2++ = '\0'; + } + + /* Headers come after parameters, but there may be headers without parameters, thus the S_OR */ + if ((headers1 = strchr(S_OR(params1, host1), '?'))) { + *headers1++ = '\0'; + } + + if ((headers2 = strchr(S_OR(params2, host2), '?'))) { + *headers2++ = '\0'; + } + + if (sip_uri_domain_cmp(host1, host2)) { + return 1; + } + + /* Headers have easier rules to follow, so do those first */ + if (sip_uri_headers_cmp(headers1, headers2)) { + return 1; + } + + /* And now the parameters. Ugh */ + return sip_uri_params_cmp(params1, params2); +} + +void sip_via_free(struct sip_via *via) +{ + if (!via) { + return; + } + + ast_free(via->via); + ast_free(via); +} + +struct sip_via *sip_via_parse(const char *header) +{ + struct sip_via *via; + char *rest, *param; + + if (ast_strlen_zero(header)) { + ast_log(LOG_ERROR, "received request without a Via header\n"); + return NULL; + } + + if (!(via = ast_calloc(1, sizeof(*via)))) { + return NULL; + } + + via->via = ast_strdup(header); + via->ttl = 1; + + /* seperate the first via-parm */ + rest = strsep(&via->via, ","); + + /* chop off sent-protocol */ + via->protocol = strsep(&rest, " \t\r\n"); + + if (ast_strlen_zero(via->protocol)) { + ast_log(LOG_ERROR, "missing sent-protocol in Via header\n"); + sip_via_free(via); + + return NULL; + } + + via->protocol = ast_skip_blanks(via->protocol); + + if (rest) { + rest = ast_skip_blanks(rest); + } + + /* chop off sent-by */ + via->sent_by = strsep(&rest, "; \t\r\n"); + + if (ast_strlen_zero(via->sent_by)) { + ast_log(LOG_ERROR, "missing sent-by in Via header\n"); + sip_via_free(via); + + return NULL; + } + + via->sent_by = ast_skip_blanks(via->sent_by); + + /* store the port, we have to handle ipv6 addresses containing ':' characters gracefully */ + if (((param = strchr(via->sent_by, ']')) && *(++param) == ':') || + (!(param = strchr(via->sent_by, ']')) && (param = strchr(via->sent_by, ':')))) { + via->port = strtol(++param, NULL, 10); + } + + /* evaluate any via-parms */ + while ((param = strsep(&rest, "; \t\r\n"))) { + if (!strncasecmp(param, "maddr=", 6)) { + via->maddr = ast_skip_blanks(param + 6); + } else if (!strncasecmp(param, "branch=", 7)) { + via->branch = ast_skip_blanks(param + 7); + } else if (!strncasecmp(param, "ttl=", 4)) { + via->ttl = strtol(param + 4, NULL, 10); + } + } + + return via; +} + +/*! Parse multiline SIP headers into one header This is used if pedanticsipchecking is enabled */ +void sip_compress_whitespace(struct ast_str *data) +{ + char *headers = ast_str_buffer(data); + int len = ast_str_strlen(data); + int input = 0, output = 0; + int lws = FALSE; + int eol = FALSE; + int done_headers = FALSE; + + while (input < len) { + /* Eliminate all CRs */ + if (headers[input] == '\r') { + input++; + continue; + } + + /* Check for end-of-line */ + if (headers[input] == '\n') { + if (eol) { + done_headers = TRUE; + } else { + eol = TRUE; + } + + /* Check for end-of-message */ + if ((input + 1) == len) { + break; + } + + /* Check for a continuation line */ + if (!done_headers && (headers[input + 1] == ' ' || headers[input + 1] == '\t')) { + /* Merge continuation line */ + input++; + continue; + } + + /* Propagate LF and start new line */ + headers[output++] = headers[input++]; + lws = FALSE; + + continue; + } else { + eol = FALSE; + } + + if (!done_headers && (headers[input] == ' ' || headers[input] == '\t')) { + if (lws) { + input++; + continue; + } + + headers[output++] = headers[input++]; + lws = TRUE; + + continue; + } + + headers[output++] = headers[input++]; + + if (lws) { + lws = FALSE; + } + } + + headers[output] = '\0'; + ast_str_update(data); +} + +void sip_pedantic_decode(char *str) +{ + if (sip_config.pedanticsipchecking && !ast_strlen_zero(str)) { + ast_uri_decode(str, ast_uri_sip_user); + } +} + +/* Generate 32 byte random string for callid's etc */ +char *sip_generate_random_string(char *buf, size_t size) +{ + long val[4]; + int i; + + for (i = 0; i < ARRAY_LEN(val); i++) { + val[i] = ast_random(); + } + + snprintf(buf, size, "%08lx%08lx%08lx%08lx", (unsigned long) val[0], (unsigned long) val[1], (unsigned long) val[2], (unsigned long) val[3]); + + return 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 *value, unsigned int standard) +{ + int port = standard; + + if (ast_strlen_zero(value) || (sscanf(value, "%30d", &port) != 1) || (port < 1) || (port > 65535)) { + port = standard; + } + + 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_SYMMETRICRTP) ? "Auto (Yes)" : "Auto (No)"; + } + + return AST_CLI_YESNO(ast_test_flag(&flags[1], SIP_SYMMETRICRTP)); +} + +/* Convert transfer status to string */ +const char *sip_refer_status2str(int status) +{ + switch (status) { + case SIP_REFER_SENT: + return "Request sent"; + case SIP_REFER_ACCEPTED: + return "Accepted"; + case SIP_REFER_200OK: + return "Done"; + case SIP_REFER_FAILED: + return "Failed"; + case SIP_REFER_NOAUTH: + return "Failed - auth failure"; + default: + break; + } + + return "Unknown"; +} + +/* Convert DTMF mode to printable string */ +const char *sip_invitestate2str(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 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_allowoverlap2str(int mode) +{ + switch (mode) { + case SIP_ALLOWOVERLAP_YES: + return "Yes"; + case SIP_ALLOWOVERLAP_DTMF: + return "DTMF"; + case SIP_ALLOWOVERLAP_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, SIP_NAT_FORCE_RPORT) ? "NAT" : "no NAT"; +} + +const char *sip_t38_ecmode2str(int ecmode) +{ + switch (ecmode) { + case SIP_T38SUPPORT_UDPTL: + return "None"; + case SIP_T38SUPPORT_UDPTL_FEC: + return "FEC"; + case SIP_T38SUPPORT_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; + } + + /* Never reached */ + return 0; +} + +/* 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 Server internal failure"; + 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 Network error"; + + case AST_CAUSE_NOTDEFINED: + default: + ast_debug(1, "AST hangup cause %d (no match found in SIP)\n", cause); + return NULL; + } + + /* Never reached */ + return 0; +} + +const char *sip_sanitized_host(const char *host) +{ + struct ast_sockaddr addr; + + /* peer/sip_pvt->tohost and sip_registry->hostname should never have a port in them, so we use PARSE_PORT_FORBID here. + * If this lookup fails, we return the original host which is most likely a host name and not an IP. */ + memset(&addr, 0, sizeof(addr)); + + if (!ast_sockaddr_parse(&addr, host, PARSE_PORT_FORBID)) { + return host; + } + + return ast_sockaddr_stringify_host_remote(&addr); +} + +/* Return int representing a bit field of transport types found in const char *transport */ +int sip_str2transport(const char *transport) +{ + int res = 0; + + if (ast_strlen_zero(transport)) { + return res; + } + + if (!strcasecmp(transport, "udp")) { + res |= AST_TRANSPORT_UDP; + } + + if (!strcasecmp(transport, "tcp")) { + res |= AST_TRANSPORT_TCP; + } + + if (!strcasecmp(transport, "tls")) { + res |= AST_TRANSPORT_TLS; + } + + return res; +} + +/* Return configuration of transports for a device */ +const char *sip_transports2str(unsigned int transports) +{ + char *buf; + int len; + + if (!transports) { + return "UNKNOWN"; + } + + if (!(buf = ast_threadstorage_get(&sip_transports2str_buf, 16))) { + return ""; + } + + buf[0] = '\0'; + len = 0; + + if (transports & AST_TRANSPORT_UDP) { + strcat(buf + len, "UDP,"); + len += 4; + } + + if (transports & AST_TRANSPORT_TCP) { + strcat(buf + len, "TCP,"); + len += 4; + } + + if (transports & AST_TRANSPORT_TLS) { + strcat(buf + len, "TLS,"); + len += 4; + } + + /* Remove the trailing ',' if present */ + if ((len = strlen(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"; +} + +/* Returns the port to use for this socket */ +int sip_standard_port(enum ast_transport type, int port) +{ + if (type & AST_TRANSPORT_TLS) { + return port == SIP_STANDARD_TLS_PORT; + } else { + return port == SIP_STANDARD_PORT; + } +} + +/* Helper for dns resolution to filter by address family. */ +int sip_transport_family(unsigned int transport) +{ + const struct ast_sockaddr *addr = NULL; + + if (transport == AST_TRANSPORT_UDP || !transport) { + addr = &sip_config.bindaddr; + } else if (transport == AST_TRANSPORT_TCP) { + addr = &sip_tcp_session.local_address; + } else if (transport == AST_TRANSPORT_TLS) { + addr = &sip_tls_session.local_address; + } + + if (ast_sockaddr_is_ipv6(addr) && ast_sockaddr_is_any(addr)) { + return 0; + } + + return addr->ss.ss_family; +} + +/* Takes the digest response and parses it */ +void sip_parse_digest(char *authresp, struct sip_digest_keys *keys) +{ + int i; + const char *sep; + + while (authresp && !ast_strlen_zero(authresp = ast_skip_blanks(authresp))) { /* lookup for keys */ + for (i = 0; i < SIP_DIGEST_LAST; i++) { + if (keys[i].tag == NULL) { + continue; + } + + if (strncasecmp(authresp, keys[i].tag, strlen(keys[i].tag))) { + continue; + } + + /* Found. Skip keyword, take text in quotes or up to the separator. */ + authresp += strlen(keys[i].tag); + + if (*authresp == '"') { /* in quotes. Skip first and look for last */ + authresp++; + sep = "\""; + } else { + sep = ","; + } + + keys[i].value = authresp; + strsep(&authresp, sep); + + break; + } + + if (keys[i].tag == NULL) { /* not found, jump after space or comma */ + strsep(&authresp, ", "); + } + } +} + +/* 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(const char *options, char *unsupported, size_t unsupportedlen) +{ + char *name, *sep; + int i, found, supported; + int first = TRUE; + unsigned int profile = 0; + + if (ast_strlen_zero(options)) { + return 0; + } + + if (unsupportedlen) { + unsupported[0] = '\0'; + } + + ast_debug(3, "Parsing SIP \"Supported: %s\"\n", options); + + for (name = ast_strdupa(options); name; name = sep) { + found = FALSE; + supported = FALSE; + + if ((sep = strchr(name, ',')) != NULL) { + *sep++ = '\0'; + } + + /* trim leading and trailing whitespace */ + name = ast_strip(name); + + if (ast_strlen_zero(name)) { + continue; /* if there is a blank argument in there just skip it */ + } + + ast_debug(3, "Found SIP option: %s\n", name); + + for (i = 0; sip_options[i].id != SIP_OPT_UNKNOWN; i++) { + if (!strcasecmp(name, sip_options[i].name)) { + profile |= sip_options[i].id; + + if (sip_options[i].supported) { + supported = TRUE; + } + + found = TRUE; + ast_debug(3, "Matched SIP option: %s\n", name); + + break; + } + } + + /* If option is not supported, add to unsupported out buffer */ + if (!supported && unsupportedlen) { + int namelen = strlen(name); + + /* Check to see if there is enough room to store this option. Copy length is string length plus 2 for the ',' and '\0' */ + if (namelen + 2 < unsupportedlen) { + if (!first) { + strcpy(unsupported, ","); + unsupported += 1; + unsupportedlen -= 1; + } else { + first = FALSE; + } + + strcpy(unsupported, name); + supported += namelen; + unsupportedlen -= namelen; + } + } + + if (!found) { + profile |= SIP_OPT_UNKNOWN; + + if (!strncasecmp(name, "x-", 2)) { + ast_debug(3, "Found private SIP option, not supported: %s\n", name); + } else { + ast_debug(3, "Found no match for SIP option: %s (Please file bug report!)\n", name); + } + } + } + + return profile; +} + +int sip_parse_contact(const char *fullcontact, struct ast_sockaddr *addr) +{ + char *contact, *user, *hostport, *transport; + + /* Work on a copy */ + contact = ast_strdupa(fullcontact); + + /* 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_uri_parse(contact, "sip:,sips:", &user, NULL, &hostport, &transport)) { + ast_log(LOG_WARNING, "Invalid contact uri %s (missing sip: or sips:), attempting to use anyway\n", fullcontact); + } + + /* If we took in an invalid URI, hostport may not have been initialized */ + if (ast_strlen_zero(hostport)) { + ast_log(LOG_WARNING, "Invalid URI: sip_uri_parse failed to acquire hostport\n"); + return -1; + } + + if (ast_sockaddr_resolve_first_af(addr, hostport, 0, sip_str2transport(transport))) { + ast_log(LOG_WARNING, "Invalid host name in Contact: (can't resolve in DNS): '%s'\n", hostport); + return -1; + } + + /* set port */ + if (!ast_sockaddr_port(addr)) { + int use_tls = sip_str2transport(transport) == AST_TRANSPORT_TLS || !strncasecmp(fullcontact, "sips", 4); + + ast_sockaddr_set_port(addr, use_tls ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + } + + return 0; +} + +/* Parse contact header and save registration (peer registration) */ +int sip_parse_register_contact(struct sip_pvt *pvt, struct sip_peer *peer, struct sip_request *req, int *addr_changed) +{ + int expiry; + char *contact, *hostport = NULL, *transport = NULL; + char *uri, *firsturi = NULL; + const char *expires, *useragent; + struct ast_sockaddr oldsin, testsa; + int transport_type; + int start = 0; + int wildcard_found = FALSE; + int single_binding_found = FALSE; + + contact = ast_strdupa(sip_request_get_header_full(req, "Contact", &start)); + expires = sip_request_get_header(req, "Expires"); + + if (ast_strlen_zero(expires)) { /* No expires header, try look in Contact: */ + char *tag; + + if ((tag = strcasestr(contact, ";expires="))) { + tag = strsep(&tag, ";"); + + if (sscanf(tag + 9, "%30d", &expiry) != 1) { + expiry = sip_config.default_expiry; + } + } else { + /* Nothing has been specified */ + expiry = sip_config.default_expiry; + } + } else { + expiry = atoi(expires); + } + + if (expiry > sip_config.max_expiry) { + expiry = sip_config.max_expiry; + } + + if (expiry < sip_config.min_expiry && expiry != 0) { + expiry = sip_config.min_expiry; + } + + pvt->expiry = expiry; + + do { + /* Look for brackets */ + uri = contact; + + if (!strchr(contact, '<')) { /* No <, check for ; and strip it */ + strsep(&uri, ";"); /* This is Header options, not URI options */ + } + + uri = sip_get_in_brackets(contact); + + if (!firsturi) { + firsturi = ast_strdupa(uri); + } + + if (!strcasecmp(uri, "*")) { + wildcard_found = TRUE; + } else { + single_binding_found = TRUE; + } + + if (wildcard_found && (ast_strlen_zero(expires) || expiry != 0 || single_binding_found)) { + /* 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; + } + + contact = ast_strdupa(sip_request_get_header_full(req, "Contact", &start)); + } while (!ast_strlen_zero(contact)); + + uri = firsturi; + + /* 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(uri) && ast_strlen_zero(expires)) { + /* If we have an active registration, tell them when the registration is going to expire */ + if (peer->expire > -1 && !ast_strlen_zero(peer->fullcontact)) { + pvt->expiry = ast_sched_when(sip_sched_context, peer->expire); + } + + return SIP_PARSE_REGISTER_QUERY; + } else if (!strcasecmp(uri, "*") || !expiry) { /* Unregister this peer */ + pvt->expiry = 0; + + /* This means remove all registrations and return OK */ + AST_SCHED_DEL_UNREF(sip_sched_context, peer->expire, ao2_t_cleanup(peer, "remove register expire ref")); + + ast_verb(3, "Unregistered SIP '%s'\n", peer->name); + + sip_peer_expire_register(ao2_t_bump(peer,"add ref for explicit sip_peer_expire_register")); + + return SIP_PARSE_REGISTER_UPDATE; + } + + /* Store whatever we got as a contact from the client */ + ast_string_field_set(peer, fullcontact, uri); + + /* For the 200 OK, we should use the received contact */ + ast_string_field_build(pvt, our_contact, "<%s>", uri); + + /* Make sure it's a SIP URL */ + if (ast_strlen_zero(uri) || sip_uri_parse(uri, "sip:,sips:", &uri, NULL, &hostport, &transport)) { + ast_log(LOG_NOTICE, "Not a valid SIP contact (missing sip:/sips:) trying to use anyway\n"); + } + + sip_socket_copy_data(&pvt->socket, &req->socket); + + /* handle the transport type specified in Contact header. */ + if (!(transport_type = sip_str2transport(transport))) { + transport_type = pvt->socket.type; + } + + /* if the peer's socket type is different than the Registrationtransport type, change it. If it got this far, it is a + * supported type, but check just in case */ + if ((peer->socket.type != transport_type) && (peer->transports & transport_type)) { + sip_socket_set_transport(&peer->socket, transport_type); + } + + oldsin = peer->addr; + + /* If we were already linked into the sip_peers_by_addr container unlink ourselves so nobody can find us */ + if (!ast_sockaddr_isnull(&peer->addr) && (!peer->is_realtime || ast_test_flag(&sip_config.flags[1], SIP_RTCACHEFRIENDS))) { + ao2_t_unlink(sip_peers_by_addr, peer, "ao2_unlink of peer from sip_peers_by_addr table"); + } + + if (!ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT) && !ast_test_flag(&pvt->flags[0], SIP_NAT_RPORT_PRESENT)) { + /* 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(&testsa, hostport, 0, sip_transport_family(peer->socket.type))) { + ast_log(LOG_WARNING, "Invalid hostport '%s'\n", hostport); + ast_string_field_set(peer, fullcontact, ""); + ast_string_field_set(pvt, 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->portinuri = !!ast_sockaddr_port(&testsa); + + if (!ast_sockaddr_port(&testsa)) { + ast_sockaddr_set_port(&testsa, transport_type == AST_TRANSPORT_TLS ? SIP_STANDARD_TLS_PORT : SIP_STANDARD_PORT); + } + + ast_sockaddr_copy(&peer->addr, &testsa); + } 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"); + peer->addr = pvt->recv; + } + + /* Check that they're allowed to register at this IP */ + if (ast_apply_acl(sip_config.contact_acl, &peer->addr, "SIP contact ACL: ") != AST_SENSE_ALLOW || + ast_apply_acl(peer->contactacl, &peer->addr, "SIP contact ACL: ") != AST_SENSE_ALLOW) { + ast_log(LOG_WARNING, "Domain '%s' disallowed by contact ACL (violating IP %s)\n", hostport, + ast_sockaddr_stringify_addr(&peer->addr)); + + ast_string_field_set(peer, fullcontact, ""); + ast_string_field_set(pvt, our_contact, ""); + + return SIP_PARSE_REGISTER_DENIED; + } + + /* if the Contact header information copied into peer->addr matches the received address, and the transport types are the + * same, then copy socket data into the peer struct */ + if ((peer->socket.type == pvt->socket.type) && !ast_sockaddr_cmp(&peer->addr, &pvt->recv)) { + sip_socket_copy_data(&peer->socket, &pvt->socket); + } + + /* Now that our address has been updated put ourselves back into the container for lookups */ + if (!peer->is_realtime || ast_test_flag(&peer->flags[1], SIP_RTCACHEFRIENDS)) { + ao2_t_link(sip_peers_by_addr, peer, "ao2_link into sip_peers_by_addr table"); + } + + /* Save SIP options profile */ + peer->sipoptions = pvt->sipoptions; + + if (!ast_strlen_zero(uri) && ast_strlen_zero(peer->username)) { + ast_string_field_set(peer, username, uri); + } + + AST_SCHED_DEL_UNREF(sip_sched_context, peer->expire, ao2_t_cleanup(peer, "remove register expire ref")); + + if (peer->is_realtime && !ast_test_flag(&peer->flags[1], SIP_RTCACHEFRIENDS)) { + peer->expire = -1; + } else { + peer->expire = ast_sched_add(sip_sched_context, (expiry + 10) * 1000, sip_peer_expire_register, ao2_t_bump(peer, "add registration ref")); + + if (peer->expire == -1) { + ao2_t_cleanup(peer, "remote registration ref"); + } + } + + if (!sip_parse_path(pvt, peer, req, NULL)) { + /* Tell the dialog to use the Path header in the response */ + ast_set2_flag(&pvt->flags[0], 1, SIP_USEPATH); + } + + /* We might not immediately be able to reconnect via TCP, but try caching it anyhow */ + if (!peer->rt_fromcontact || !sip_config.peer_rtupdate) { + char data[SIP_BUFFER_SIZE]; + + if (!sip_route_empty(&peer->path)) { + struct ast_str *path; + + if ((path = sip_route_list(&peer->path, FALSE, 0))) { + ast_db_put("SIP/RegistryPath", peer->name, ast_str_buffer(path)); + ast_free(path); + } + } + + snprintf(data, sizeof(data), "%s:%d:%s:%s", ast_sockaddr_stringify(&peer->addr), expiry, peer->username, peer->fullcontact); + ast_db_put("SIP/Registry", 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->addr)); + ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); + } + + /* Is this a new IP address for us? */ + if ((*addr_changed = ast_sockaddr_cmp(&peer->addr, &oldsin))) { + ast_verb(3, "Registered SIP '%s' at %s\n", peer->name, ast_sockaddr_stringify(&peer->addr)); + + /* Clear off-hook counter in case of the on-hook notification not being received */ + peer->offhook = 0; + } + + ao2_unlock(pvt); + sip_peer_qualify(peer, FALSE); + ao2_lock(pvt); + + sip_peer_register_exten(peer, TRUE); + + /* Save REGISTER dialog Call-ID */ + ast_string_field_set(peer, regcallid, pvt->callid); + + if (ast_test_flag(&peer->flags[1], SIP_CISCO_USECALLMANAGER)) { + char *reason = ast_strdupa(sip_request_get_header(req, "Reason")); + + if (!strncmp(reason, "SIP;cause=200;text=", 19)) { + char *devicename, *inactiveload, *activeload, *ext; + + if ((devicename = strstr(reason, " Name="))) { + devicename = ast_strdupa(devicename + 6); + devicename = strsep(&devicename, " "); + } + + if ((activeload = strstr(reason, " ActiveLoad=")) || (activeload = strstr(reason, " Load="))) { + activeload = ast_strdupa(activeload + (!strncmp(activeload, " ActiveLoad=", 12) ? 12 : 6)); + + if ((ext = strstr(activeload, ".loads"))) { + *ext = '\0'; + } + + activeload = strsep(&activeload, " "); + } + + if ((inactiveload = strstr(reason, " InactiveLoad="))) { + inactiveload = ast_strdupa(inactiveload + 14); + + if ((ext = strstr(inactiveload, ".loads"))) { + *ext = '\0'; + } + + inactiveload = strsep(&inactiveload, " "); + } + + ast_string_field_set(peer, cisco_devicename, devicename); + ast_string_field_set(peer, cisco_activeload, activeload); + ast_string_field_set(peer, cisco_inactiveload, inactiveload); + } + } + + /* Save User agent */ + useragent = sip_request_get_header(req, "User-Agent"); + + if (strcasecmp(useragent, peer->useragent)) { + ast_string_field_set(peer, useragent, useragent); + ast_verb(4, "Saved useragent \"%s\" for peer %s\n", peer->useragent, peer->name); + } + + return SIP_PARSE_REGISTER_UPDATE; +} + +/* Save contact header for 200 OK on INVITE */ +int sip_parse_ok_contact(struct sip_pvt *pvt, struct sip_request *req) +{ + char *contact = ast_strdupa(sip_request_get_header(req, "Contact")); + + /* Look for brackets */ + contact = sip_get_in_brackets(contact); + + /* Save full contact to call pvt for later bye or re-invite */ + ast_string_field_set(pvt, fullcontact, contact); + + /* Save URI for later ACKs, BYE or RE-invites */ + ast_string_field_set(pvt, okcontacturi, contact); + + /* We should return false for URI:s we can't handle, like tel:, mailto:,ldap: etc */ + return TRUE; +} + +/* Parse 302 Moved temporalily response. Doesn't redirect over TLS on sips: uri's. If we get a redirect to a SIPS: uri, this needs to be going + * back to the dialplan (this is a request for a secure signalling path). Note: that transport=tls is deprecated, but we need to support it on + * incoming requests. */ +void sip_parse_redirect_contact(struct sip_pvt *pvt, struct sip_request *req, char **name, char **number, int set_call_forward) +{ + char *tmp_name = NULL, *tmp_number = NULL; + char *sep, *param, *contact, *domain; + enum ast_transport transport = AST_TRANSPORT_UDP; + + contact = ast_strdupa(sip_request_get_header(req, "Contact")); + + if ((sep = strchr(contact, ','))) { + *sep = '\0'; + } + + tmp_number = sip_get_in_brackets(contact); + + if ((param = strcasestr(tmp_number, ";transport="))) { + param += 11; + + if ((sep = strchr(param, ';'))) { + *sep = '\0'; + } + + if (!strncasecmp(param, "tcp", 3)) { + transport = AST_TRANSPORT_TCP; + } else if (!strncasecmp(param, "tls", 3)) { + transport = AST_TRANSPORT_TLS; + } else { + if (strncasecmp(param, "udp", 3)) { + ast_debug(1, "received contact with an invalid transport, '%s'\n", tmp_number); + } + + /* This will assume UDP for all unknown transports */ + transport = AST_TRANSPORT_UDP; + } + } + + tmp_number = sip_uri_remove_parameters(tmp_number); + + if (pvt->socket.tcptls_session) { + ao2_ref(pvt->socket.tcptls_session, -1); + pvt->socket.tcptls_session = NULL; + } + + sip_socket_set_transport(&pvt->socket, transport); + + if (set_call_forward && ast_test_flag(&pvt->flags[0], SIP_PROMISCREDIR)) { + char *host = NULL; + + if (!strncasecmp(tmp_number, "sip:", 4)) { + tmp_number += 4; + } else if (!strncasecmp(tmp_number, "sips:", 5)) { + tmp_number += 5; + } + + if ((sep = strchr(tmp_number, '/'))) { + *sep = '\0'; + } + + if ((host = strchr(tmp_number, '@'))) { + *host++ = '\0'; + + ast_debug(2, "Found promiscuous redirection to 'SIP/%s::::%s@%s'\n", tmp_number, ast_transport2str(transport), host); + + if (pvt->owner) { + ast_channel_call_forward_build(pvt->owner, "SIP/%s::::%s@%s", tmp_number, ast_transport2str(transport), host); + } + } else { + ast_debug(2, "Found promiscuous redirection to 'SIP/::::%s@%s'\n", ast_transport2str(transport), tmp_number); + + if (pvt->owner) { + ast_channel_call_forward_build(pvt->owner, "SIP/::::%s@%s", ast_transport2str(transport), tmp_number); + } + } + } else { + if ((sep = strchr(contact, '@'))) { + *sep++ = '\0'; + domain = sep; + } else { + /* No username part */ + domain = contact; + } + + if ((sep = strchr(contact, '/'))) { /* WHEN do we hae a forward slash in the URI? */ + *sep = '\0'; + } + + if (!strncasecmp(tmp_number, "sip:", 4)) { + tmp_number += 4; + } else if (!strncasecmp(tmp_number, "sips:", 5)) { + tmp_number += 5; + } + + if ((sep = strchr(tmp_number, ';'))) { /* And username ; parameters? */ + *sep = '\0'; + } + + ast_uri_decode(tmp_number, ast_uri_sip_user); + + if (set_call_forward) { + ast_debug(2, "Received 302 Redirect to extension '%s' (domain %s)\n", tmp_number, domain); + + if (pvt->owner) { + pbx_builtin_setvar_helper(pvt->owner, "SIPDOMAIN", domain); + ast_channel_call_forward_set(pvt->owner, tmp_number); + } + } + } + + /* We've gotten the number for the contact, now get the name */ + if (*contact == '\"') { + tmp_name = contact + 1; + + if (!(sep = (char *) sip_find_quote(tmp_name, NULL))) { + ast_log(LOG_NOTICE, "No closing quote on name in Contact header? %s\n", contact); + } else { + *sep = '\0'; + } + } + + if (name && !ast_strlen_zero(tmp_name)) { + *name = ast_strdup(tmp_name); + } + + if (number) { + *number = ast_strdup(tmp_number); + } +} + +/* 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.*/ +unsigned int sip_parse_allow(struct sip_request *req) +{ + const char *allow = sip_request_get_header(req, "Allow"); + unsigned int allowed_methods = SIP_UNKNOWN; + + sip_method_parse(&allowed_methods, allow); + + return allowed_methods; +} + +/* 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_pvt *pvt, struct sip_request *req) +{ + int cause; + const char *reason; + + if (!pvt->owner) { + return -1; + } + + if (!ast_test_flag(&pvt->flags[1], SIP_Q850_REASON) || !(reason = sip_request_get_header(req, "Reason"))) { + return -1; + } + + reason = ast_skip_blanks(reason); + + if (strncasecmp(reason, "Q.850", 5)) { + return -1; + } + + if ((reason = strstr(reason, "cause=")) && sscanf(reason + 6, "%3d", &cause) == 1) { + ast_channel_hangupcause_set(pvt->owner, cause & 0x7f); + + if (req->debug) { + ast_verb(3, "Using Reason header for cause code: %d\n", ast_channel_hangupcause(pvt->owner)); + } + + 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_pvt *pvt, struct sip_peer *peer, struct sip_request *req, const char *pathbuf) +{ + sip_route_clear(&peer->path); + + if (!ast_test_flag(&peer->flags[0], SIP_USEPATH)) { + ast_debug(2, "sip_parse_path: do not use Path headers\n"); + return -1; + } + + ast_debug(2, "Try to build pre-loaded route-set by parsing Path headers\n"); + + if (req) { + int start = 0; + const char *header; + + for (;;) { + header = sip_request_get_header_full(req, "Path", &start); + + if (*header == '\0') { + break; + } + + sip_route_parse(&peer->path, header, FALSE); + } + } else if (pathbuf) { + sip_route_parse(&peer->path, pathbuf, FALSE); + } + + /* Caches result for any dialog->route copied from peer->path */ + sip_route_is_strict(&peer->path); + + /* For debugging dump what we ended up with */ + if (pvt && sip_debug_test_pvt(pvt)) { + 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_rpid(struct sip_pvt *pvt, struct sip_request *req) +{ + char *cid_num = "", *cid_name = "", *privacy = "", *screen = ""; + int callingpres = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED; + char *rpid, *start, *end; + int pai; + + if (!ast_test_flag(&pvt->flags[0], SIP_TRUSTRPID)) { + return 0; + } + + if (!req) { + req = &pvt->initreq; + } + + rpid = ast_strdupa(sip_request_get_header(req, "Remote-Party-ID")); + + if (ast_strlen_zero(rpid)) { + rpid = ast_strdupa(sip_request_get_header(req, "P-Asserted-Identity")); + + if (ast_strlen_zero(rpid)) { + return 0; + } + + pai = TRUE; + } else { + pai = FALSE; + } + + /* RPID is not: (name-addr / addr-spec) *(SEMI rpi-token), but it is: rpid = [display-name] LAQUOT addr-spec RAQUOT *(SEMI rpi-token) + * Ergo, calling parse_name_andor_addr() on it wouldn't be correct because that would allow addr-spec style too. */ + start = rpid; + + /* Quoted (note that we're not dealing with escapes properly) */ + if (*start == '"') { + *start++ = '\0'; + + if (!(end = strchr(start, '"'))) { + return 0; + } + + *end++ = '\0'; + cid_name = start; + + start = ast_skip_blanks(end); + /* Unquoted */ + } else { + cid_name = start; + start = end = strchr(start, '<'); + + if (!start) { + return 0; + } + + /* trim blanks if there are any. the mandatory NUL is done below */ + while (--end >= cid_name && *end < 33) { + *end = '\0'; + } + } + + if (*start != '<') { + return 0; + } + + *start++ = '\0'; + + if (!(end = strchr(start, '@'))) { + return 0; + } + + *end++ = '\0'; + + if (strncasecmp(start, "sip:", 4)) { + return 0; + } + + cid_num = start + 4; + + if (sip_config.shrinkcallerid && ast_is_shrinkable_phonenumber(cid_num)) { + ast_shrink_phone_number(cid_num); + } + + start = end; + + if (!(end = strchr(start, '>'))) { + return 0; + } + + *end++ = '\0'; + + if (pai) { + if (!strcasecmp(sip_request_get_header(req, "Privacy"), "id")) { + callingpres = AST_PRES_PROHIB_USER_NUMBER_PASSED_SCREEN; + } else if (!strcasecmp(cid_num, "anonymous@anonymous.invalid")) { + callingpres = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; + } + } else if (*end) { + start = end; + + if (*start != ';') { + return 0; + } + + *start++ = '\0'; + + while (!ast_strlen_zero(start)) { + if ((end = strchr(start, ';'))) { + *end++ = '\0'; + } + + if (!strncasecmp(start, "privacy=", 8)) { + privacy = start + 8; + } else if (!strncasecmp(start, "screen=", 7)) { + screen = start + 7; + } + + start = end; + } + + if (!strcasecmp(privacy, "full")) { + if (!strcasecmp(screen, "yes")) { + callingpres = AST_PRES_PROHIB_USER_NUMBER_PASSED_SCREEN; + } else if (!strcasecmp(screen, "no")) { + callingpres = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; + } + } else { + if (!strcasecmp(screen, "yes")) { + callingpres = AST_PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN; + } else if (!strcasecmp(screen, "no")) { + callingpres = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED; + } + } + } + + /* Only return true if the supplied caller id is different */ + if (!strcasecmp(pvt->cid_num, cid_num) && !strcasecmp(pvt->cid_name, cid_name) && pvt->callingpres == callingpres) { + return 0; + } + + ast_string_field_set(pvt, cid_num, cid_num); + ast_string_field_set(pvt, cid_name, cid_name); + pvt->callingpres = callingpres; + + if (pvt->owner) { + ast_set_callerid(pvt->owner, cid_num, cid_name, NULL); + + ast_channel_caller(pvt->owner)->id.name.presentation = callingpres; + ast_channel_caller(pvt->owner)->id.number.presentation = callingpres; + } + + return 1; +} + +/* Get referring dnis */ +int sip_parse_diversion(struct sip_pvt *pvt, struct sip_request *req, char **name, char **number, int *code, char **reason) +{ + char *diversion, *uri, *domain, *params, *sep, *tag = NULL; + + if (!req) { + req = &pvt->initreq; + } + + diversion = ast_strdupa(sip_request_get_header(req, "Diversion")); + + if (ast_strlen_zero(diversion)) { + return -1; + } + + if ((params = strchr(diversion, '>'))) { + params = strchr(params, ';'); + } + + uri = sip_get_in_brackets(diversion); + + if (!strncasecmp(uri, "sip:", 4)) { + uri += 4; + } else if (!strncasecmp(uri, "sips:", 5)) { + uri += 5; + } else { + ast_log(LOG_WARNING, "Huh? Not an RDNIS SIP header (%s)?\n", uri); + return -1; + } + + /* Get diversion-reason param if present */ + if (params) { + *params++ = '\0'; /* Cut off parameters */ + + while (*params == ';' || *params == ' ') { + params++; + } + + /* Check if we have a reason parameter */ + if ((tag = strcasestr(params, "reason="))) { + tag += 7; + + if ((sep = strchr(tag, ';'))) { + *sep = '\0'; + } + } + } + + if ((domain = strchr(uri, '@'))) { + *domain++ = '\0'; + + if (pvt->owner) { + pbx_builtin_setvar_helper(pvt->owner, "__SIPRDNISDOMAIN", domain); + } + } + + if (sip_debug_test_pvt(pvt)) { + ast_verb(3, "RDNIS for this call is %s (reason %s)\n", uri, S_OR(tag, "")); + } + + if (*diversion == '\"') { + diversion++; + + if ((sep = strchr(diversion, '\"'))) { + *sep = '\0'; + } + } + + if (number) { + *number = ast_strdup(uri); + } + + if (name) { + *name = ast_strdup(diversion); + } + + if (!ast_strlen_zero(tag)) { + *reason = ast_strdup(tag); + + /* Remove any enclosing double-quotes */ + if (*tag == '"') { + tag = ast_strip_quoted(tag, "\"", "\""); + } + + *code = ast_redirecting_reason_parse(tag); + + if (*code < 0) { + *code = AST_REDIRECTING_REASON_UNKNOWN; + } else { + ast_free(*reason); + *reason = ast_strdup(""); + } + + if (!ast_strlen_zero(tag)) { + if (!strcmp(tag, "unknown")) { + ast_string_field_set(pvt, redircause, "UNKNOWN"); + } else if (!strcmp(tag, "user-busy")) { + ast_string_field_set(pvt, redircause, "BUSY"); + } else if (!strcmp(tag, "no-answer")) { + ast_string_field_set(pvt, redircause, "NOANSWER"); + } else if (!strcmp(tag, "unavailable")) { + ast_string_field_set(pvt, redircause, "UNREACHABLE"); + } else if (!strcmp(tag, "unconditional")) { + ast_string_field_set(pvt, redircause, "UNCONDITIONAL"); + } else if (!strcmp(tag, "time-of-day")) { + ast_string_field_set(pvt, redircause, "UNKNOWN"); + } else if (!strcmp(tag, "do-not-disturb")) { + ast_string_field_set(pvt, redircause, "UNKNOWN"); + } else if (!strcmp(tag, "deflection")) { + ast_string_field_set(pvt, redircause, "UNKNOWN"); + } else if (!strcmp(tag, "follow-me")) { + ast_string_field_set(pvt, redircause, "UNKNOWN"); + } else if (!strcmp(tag, "out-of-service")) { + ast_string_field_set(pvt, redircause, "UNREACHABLE"); + } else if (!strcmp(tag, "away")) { + ast_string_field_set(pvt, redircause, "UNREACHABLE"); + } else { + ast_string_field_set(pvt, redircause, "UNKNOWN"); + } + + if (pvt->owner) { + pbx_builtin_setvar_helper(pvt->owner, "__PRIREDIRECTREASON", pvt->redircause); + pbx_builtin_setvar_helper(pvt->owner, "__SIPREDIRECTREASON", tag); + } + } + } + + return 0; +} + +/* Call transfer support (the REFER method). Extracts Refer headers into pvt 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(struct sip_pvt *pvt, struct sip_request *req) +{ + char *refer_to, *referred_by, *domain, *sep; + const char *transfer_context = NULL; + + if (!req) { + req = &pvt->initreq; + } + + refer_to = ast_strdupa(sip_request_get_header(req, "Refer-To")); + + if (ast_strlen_zero(refer_to)) { + ast_log(LOG_WARNING, "Refer-To Header missing. Skipping transfer.\n"); + return -2; /* Syntax error */ + } + + refer_to = sip_get_in_brackets(refer_to); + + if (!strncasecmp(refer_to, "sip:", 4)) { + refer_to += 4; /* Skip sip: */ + } else if (!strncasecmp(refer_to, "sips:", 5)) { + refer_to += 5; + } else { + ast_log(LOG_WARNING, "Can't transfer to non-sip: URI. (Refer-to: %s)?\n", refer_to); + return -3; + } + + /* Give useful transfer information to the dialplan */ + if (pvt->owner) { + RAII_VAR(struct ast_channel *, bridge_chan, NULL, ast_channel_cleanup); + RAII_VAR(struct ast_channel *, owner_relock, NULL, ast_channel_cleanup); + RAII_VAR(struct ast_channel *, owner, NULL, ast_channel_cleanup); + + /* Grab a reference to pvt->owner to prevent it from going away */ + owner = ast_channel_ref(pvt->owner); + + /* Established locking order here is bridge, channel, pvt and the bridge will be locked during ast_channel_bridge_peer */ + ast_channel_unlock(owner); + ao2_unlock(pvt); + + if ((bridge_chan = ast_channel_bridge_peer(owner))) { + pbx_builtin_setvar_helper(bridge_chan, "SIPREFERRINGCONTEXT", S_OR(pvt->context, NULL)); + pbx_builtin_setvar_helper(bridge_chan, "__SIPREFERREDBYHDR", S_OR(referred_by, NULL)); + } + + owner_relock = sip_pvt_lock_full(pvt); + + if (!owner_relock) { + ast_debug(3, "Unable to reacquire owner channel lock, channel is gone\n"); + return -5; + } + } + + /* Get referred by header if it exists */ + referred_by = ast_strdupa(sip_request_get_header(req, "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, "Huh? Not a sip: header (Referred-by: %s). Skipping.\n", referred_by); + referred_by = NULL; + } + } + + /* Check for arguments in the refer_to header */ + if ((sep = strcasestr(refer_to, "replaces="))) { + char *callid, *totag = NULL, *fromtag = NULL; + + /* This is an attended transfer */ + pvt->refer->attendedtransfer = TRUE; + + callid = ast_strdupa(sep + 9); + ast_uri_decode(callid, ast_uri_sip_user); + + if ((sep = strchr(callid, ';'))) { /* Find options */ + *sep++ = '\0'; + } + + ast_string_field_set(pvt->refer, replaces_callid, callid); + + if (sep) { + /* Find the different tags before we destroy the string */ + totag = strcasestr(sep, "to-tag="); + fromtag = strcasestr(sep, "from-tag="); + } + + /* Grab the to header */ + if (totag) { + totag += 7; + + if ((sep = strchr(totag, '&'))) { + *sep = '\0'; + } + + if ((sep = strchr(totag, ';'))) { + *sep = '\0'; + } + + ast_string_field_set(pvt->refer, replaces_callid_totag, totag); + } + + if (fromtag) { + fromtag += 9; + + if ((sep = strchr(fromtag, '&'))) { + *sep = '\0'; + } + + if ((sep = strchr(fromtag, ';'))) { + *sep = '\0'; + } + + ast_string_field_set(pvt->refer, replaces_callid_fromtag, fromtag); + } + + if (!strcmp(pvt->refer->replaces_callid, pvt->callid) && + (!sip_config.pedanticsipchecking || + (!strcmp(pvt->refer->replaces_callid_fromtag, pvt->theirtag) && + !strcmp(pvt->refer->replaces_callid_totag, pvt->tag)))) { + ast_log(LOG_WARNING, "Got an attempt to replace own Call-ID on %s\n", pvt->callid); + return -4; + } + + if (!sip_config.pedanticsipchecking) { + ast_debug(2, "Attended transfer: Will use Replace-Call-ID: %s (No check of from/to tags)\n", + pvt->refer->replaces_callid); + } else { + ast_debug(2, "Attended transfer: Will use Replace-Call-ID: %s From-tag: %s To-tag: %s\n", + pvt->refer->replaces_callid, + S_OR(pvt->refer->replaces_callid_fromtag, ""), + S_OR(pvt->refer->replaces_callid_totag, "")); + } + } + + if ((domain = strchr(refer_to, '@'))) { /* Separate domain */ + char *urioption; + int bracket = 0; + + *domain++ = '\0'; + + if ((urioption = strchr(domain, ';'))) { /* Separate urioptions */ + *urioption++ = '\0'; + } + + /* Remove :port */ + for (sep = domain; *sep; sep++) { + if (*sep == ':' && bracket == 0) { + *sep = '\0'; + break; + } else if (*sep == '[') { + ++bracket; + } else if (*sep == ']') { + --bracket; + } + } + + sip_pedantic_decode(domain); + sip_pedantic_decode(urioption); + + /* Save the domain for the dial plan */ + ast_string_field_set(pvt->refer, refer_to_domain, domain); + + if (urioption) { + ast_string_field_set(pvt->refer, refer_to_urioption, urioption); + } + } + + if ((sep = strchr(refer_to, ';'))) { /* Remove options */ + *sep = '\0'; + } + + sip_pedantic_decode(refer_to); + ast_string_field_set(pvt->refer, refer_to, refer_to); + + if (referred_by) { + if ((sep = strchr(referred_by, ';'))) { /* Remove options */ + *sep = '\0'; + } + + sip_pedantic_decode(referred_by); + ast_string_field_build(pvt->refer, referred_by, "", referred_by); + } else { + ast_string_field_set(pvt->refer, referred_by, ""); + } + + /* Determine transfer context */ + if (pvt->owner) { + /* By default, use the context in the channel sending the REFER */ + transfer_context = pbx_builtin_getvar_helper(pvt->owner, "TRANSFER_CONTEXT"); + } + + if (ast_strlen_zero(transfer_context)) { + transfer_context = S_OR(pvt->context, sip_config.context); + } + + ast_string_field_set(pvt->refer, refer_to_context, transfer_context); + + /* Either an existing extension or the parking extension */ + if (pvt->refer->attendedtransfer || ast_exists_extension(NULL, transfer_context, refer_to, 1, NULL)) { + if (sip_debug_test_pvt(pvt)) { + ast_verb(3, "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 0; + } + + if (sip_debug_test_pvt(pvt)) { + ast_verb(3, "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 -1; +} + +/* Check for the presence of OLI tag(s) in the From header and set on the channel */ +void sip_parse_oli(struct sip_request *req, struct ast_channel *chan) +{ + const char *from = NULL; + const char *tag = NULL; + int ani2 = 0; + + if (!chan || !req) { + return; + } + + from = sip_request_get_header(req, "From"); + + if (ast_strlen_zero(from)) { + return; + } + + if ((tag = strcasestr(from, ";isup-oli="))) { + tag += 10; + } else if ((tag = strcasestr(from, ";ss7-oli="))) { + tag += 9; + } else if ((tag = strcasestr(from, ";oli="))) { + tag += 5; + } + + if (ast_strlen_zero(tag)) { + /* OLI tag is missing, or present with nothing following the '=' sign */ + return; + } + + /* just in case OLI is quoted */ + if (*tag == '\"') { + tag++; + } + + if (sscanf(tag, "%d", &ani2)) { + ast_channel_caller(chan)->ani2 = ani2; + } +} + +/* Function for parsing Min-SE header */ +int sip_parse_min_se(const char *min_se, int *interval) +{ + if (ast_strlen_zero(min_se)) { + ast_log(LOG_WARNING, "Null Min-SE header\n"); + 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(const char *header, int *interval, int *refresher) +{ + char *tag, *rest; + + if (ast_strlen_zero(header)) { + ast_log(LOG_WARNING, "Empty Session-Expires header\n"); + return -1; + } + + *refresher = SIP_STIMER_REFRESHER_AUTO; + *interval = 0; + + rest = ast_strdupa(ast_skip_blanks(header)); + + if ((tag = strsep(&rest, ";"))) { + if (sscanf(tag, "%30d", interval) != 1) { + ast_log(LOG_WARNING, "Parsing of Session-Expires failed\n"); + return -1; + } + + ast_debug(2, "Session-Expires: %d\n", *interval); + + if (!rest) { + return 0; + } else if (!strncasecmp(rest, "refresher=", 10)) { + rest += 10; + + if (!strncasecmp(rest, "uac", 3)) { + *refresher = SIP_STIMER_REFRESHER_THEM; + ast_debug(2, "Refresher: UAC\n"); + } else if (!strncasecmp(rest, "uas", 3)) { + *refresher = SIP_STIMER_REFRESHER_US; + ast_debug(2, "Refresher: UAS\n"); + } else { + ast_log(LOG_WARNING, "Invalid refresher value %s\n", rest); + return -1; + } + } + } + + return 0; +} + +void sip_parse_rtp_stats(struct sip_pvt *pvt, struct sip_request *req) +{ + char *rxstat, *txstat; + int dur = 0, rxpkt = 0, rxoct = 0, txpkt = 0, txoct = 0, latepkt = 0, lostpkt = 0, avgjit = 0; + struct sip_peer *peer; + + rxstat = ast_strdupa(sip_request_get_header(req, "RTP-RxStat")); + + while (!ast_strlen_zero(rxstat)) { + char *tag, *value; + + tag = strsep(&rxstat, "="); + + if (!(value = strsep(&rxstat, ","))) { + break; + } + + if (!strcasecmp(tag, "Dur")) { + dur = atoi(value); + } else if (!strcasecmp(tag, "Pkt")) { + rxpkt = atoi(value); + } else if (!strcasecmp(tag, "Oct")) { + rxoct = atoi(value); + } else if (!strcasecmp(tag, "LatePkt")) { + latepkt = atoi(value); + } else if (!strcasecmp(tag, "LostPkt")) { + lostpkt = atoi(value); + } else if (!strcasecmp(tag, "AvgJit")) { + avgjit = atoi(value); + } + } + + txstat = ast_strdupa(sip_request_get_header(req, "RTP-TxStat")); + + while (!ast_strlen_zero(txstat)) { + char *tag, *value; + + tag = strsep(&txstat, "="); + + if (!(value = strsep(&txstat, ","))) { + break; + } + + if (!strcasecmp(tag, "Pkt")) { + txpkt = atoi(value); + } else if (!strcasecmp(tag, "Oct")) { + txoct = atoi(value); + } + } + + ast_verb(3, "Call Quality Report for %s\n" + " Duration : %d\n" + " Sent Packets : %d\n" + " Sent Bytes : %d\n" + " Received Packets: %d\n" + " Received Bytes : %d\n" + " Late Packets : %d\n" + " Lost Packets : %d\n" + " Average Jitter : %d\n", + pvt->peername, dur, txpkt, txoct, rxpkt, rxoct, latepkt, lostpkt, avgjit); + + if ((peer = sip_peer_find(pvt->peername, NULL, TRUE, FALSE, 0))) { + sip_peer_send_qrt_url(peer); + ao2_t_cleanup(peer, "unref after sip_peer_find"); + } +} + +/* Find the channel that is causing the RINGING update, ref'd */ +struct ast_channel *sip_find_ringing_channel(struct ao2_container *device_state_info) +{ + struct ao2_iterator iter; + struct ast_device_state_info *device_state; + struct ast_channel *chan = NULL; + struct timeval tv = {0}; + + if (!device_state_info) { + return NULL; + } + + /* iterate ringing devices and get the oldest of all causing channels */ + iter = ao2_iterator_init(device_state_info, 0); + + while ((device_state = ao2_iterator_next(&iter))) { + if (!device_state->causing_channel || + (device_state->device_state != AST_DEVICE_RINGING && device_state->device_state != AST_DEVICE_RINGINUSE)) { + ao2_ref(device_state, -1); + continue; + } + + ast_channel_lock(device_state->causing_channel); + + if (ast_tvzero(tv) || ast_tvcmp(ast_channel_creationtime(device_state->causing_channel), tv) < 0) { + chan = device_state->causing_channel; + tv = ast_channel_creationtime(chan); + } + + ast_channel_unlock(device_state->causing_channel); + ao2_ref(device_state, -1); + } + + ao2_iterator_destroy(&iter); + + return chan ? ast_channel_ref(chan) : NULL; +} + +/* Set hangup source and cause. pvt and pvt->owner are locked. */ +void sip_queue_hangup_cause(struct sip_pvt *pvt, int cause) +{ + struct ast_channel *owner = pvt->owner; + const char *name = ast_strdupa(ast_channel_name(owner)); + + /* Cannot hold any channel/private locks when calling. */ + ast_channel_ref(owner); + ast_channel_unlock(owner); + ao2_unlock(pvt); + + ast_set_hangupsource(owner, name, 0); + + if (cause) { + ast_queue_hangup_with_cause(owner, cause); + } else { + ast_queue_hangup(owner); + } + + ast_channel_unref(owner); + + /* Relock things. */ + if ((owner = sip_pvt_lock_full(pvt))) { + ast_channel_unref(owner); + } +} diff -durN asterisk-22.2.0.orig/configs/samples/res_parking.conf.sample asterisk-22.2.0/configs/samples/res_parking.conf.sample --- asterisk-22.2.0.orig/configs/samples/res_parking.conf.sample 2025-02-18 17:14:37.622147469 +1300 +++ asterisk-22.2.0/configs/samples/res_parking.conf.sample 2025-02-18 17:14:46.830902058 +1300 @@ -68,6 +68,7 @@ ; extensions if parkext is set ;parkingtime => 45 ; Number of seconds a call can be parked before returning +;remindertime => 0 ; Number of seconds before sending a reminder, 0 for no reminder. ;comebacktoorigin = yes ; Setting this option configures the behavior of call parking when the ; parked call times out (See the parkingtime option). The default value is 'yes'. diff -durN asterisk-22.2.0.orig/configs/samples/sip.conf.sample asterisk-22.2.0/configs/samples/sip.conf.sample --- asterisk-22.2.0.orig/configs/samples/sip.conf.sample 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/configs/samples/sip.conf.sample 2025-02-18 17:14:46.831902032 +1300 @@ -0,0 +1,1231 @@ +; +; 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 +; And to alter the To: or the From: header, you can additionally append +; the following to any of the above strings: +; [![touser[@todomain]][![fromuser][@fromdomain]]] +; +; +; 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 + +; If regcontext is specified, Asterisk will dynamically create and destroy a +; NoOp priority 1 extension for a given peer who registers or unregisters with +; us and have a "regexten=" configuration item. +; Multiple contexts may be specified by separating them with '&'. The +; actual extension is the 'regexten' parameter of the registering peer or its +; name if 'regexten' is not provided. If more than one context is provided, +; the context must be specified within regexten by appending the desired +; context after '@'. More than one regexten may be supplied if they are +; separated by '&'. Patterns may be used in regexten. +; +;regcontext=sipregistrations +;regextenonqualify=yes ; Default "no" + ; If you have qualify on and the peer becomes unreachable + ; this setting will enforce inactivation of the regexten + ; extension for the peer +;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. + +;refer_addheaders=yes ; on by default + +; 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 +; regexten +; 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.2.0.orig/configs/samples/sip_notify.conf.sample asterisk-22.2.0/configs/samples/sip_notify.conf.sample --- asterisk-22.2.0.orig/configs/samples/sip_notify.conf.sample 1970-01-01 12:00:00.000000000 +1200 +++ asterisk-22.2.0/configs/samples/sip_notify.conf.sample 2025-02-18 17:14:46.832902005 +1300 @@ -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.2.0.orig/contrib/realtime/mysql/mysql_config.sql asterisk-22.2.0/contrib/realtime/mysql/mysql_config.sql --- asterisk-22.2.0.orig/contrib/realtime/mysql/mysql_config.sql 2025-02-18 17:14:37.628147309 +1300 +++ asterisk-22.2.0/contrib/realtime/mysql/mysql_config.sql 2025-02-18 17:14:46.832902005 +1300 @@ -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.2.0.orig/contrib/realtime/postgresql/postgresql_config.sql asterisk-22.2.0/contrib/realtime/postgresql/postgresql_config.sql --- asterisk-22.2.0.orig/contrib/realtime/postgresql/postgresql_config.sql 2025-02-18 17:14:37.628147309 +1300 +++ asterisk-22.2.0/contrib/realtime/postgresql/postgresql_config.sql 2025-02-18 17:14:46.833901978 +1300 @@ -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.2.0.orig/include/asterisk/callerid.h asterisk-22.2.0/include/asterisk/callerid.h --- asterisk-22.2.0.orig/include/asterisk/callerid.h 2025-02-18 17:14:37.635147122 +1300 +++ asterisk-22.2.0/include/asterisk/callerid.h 2025-02-18 17:14:46.834901952 +1300 @@ -557,7 +557,11 @@ /*! Update from call transfer(active) (Party has already answered) */ AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, /*! Update from call transfer(alerting) (Party has not answered yet) */ - AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING + AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING, + /*! Update from a call being parked */ + AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL, + /*! Update from a call joining a conference */ + AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE }; /*! diff -durN asterisk-22.2.0.orig/include/asterisk/parking.h asterisk-22.2.0/include/asterisk/parking.h --- asterisk-22.2.0.orig/include/asterisk/parking.h 2025-02-18 17:14:37.639147016 +1300 +++ asterisk-22.2.0/include/asterisk/parking.h 2025-02-18 17:14:46.835901925 +1300 @@ -50,6 +50,7 @@ PARKED_CALL_UNPARKED, PARKED_CALL_FAILED, PARKED_CALL_SWAP, + PARKED_CALL_REMINDER, }; /*! diff -durN asterisk-22.2.0.orig/main/callerid.c asterisk-22.2.0/main/callerid.c --- asterisk-22.2.0.orig/main/callerid.c 2025-02-18 17:14:37.647146803 +1300 +++ asterisk-22.2.0/main/callerid.c 2025-02-18 17:14:46.836901898 +1300 @@ -1472,7 +1472,9 @@ { AST_CONNECTED_LINE_UPDATE_SOURCE_DIVERSION, "diversion", "Call Diversion (Deprecated, use REDIRECTING)" }, { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, "transfer_active", "Call Transfer(Active)" }, { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, "transfer", "Call Transfer(Active)" },/* Old name must come after new name. */ - { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING, "transfer_alerting", "Call Transfer(Alerting)" } + { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING, "transfer_alerting", "Call Transfer(Alerting)" }, + { AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL, "parked_call", "Call Parked" }, + { AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE, "conference", "Conference" } /* *INDENT-ON* */ }; diff -durN asterisk-22.2.0.orig/main/cel.c asterisk-22.2.0/main/cel.c --- asterisk-22.2.0.orig/main/cel.c 2025-02-18 17:14:37.648146776 +1300 +++ asterisk-22.2.0/main/cel.c 2025-02-18 17:14:46.837901872 +1300 @@ -1146,6 +1146,8 @@ case PARKED_CALL_SWAP: reason = "ParkedCallSwap"; break; + case PARKED_CALL_REMINDER: + return; } if (parked_payload->retriever) { diff -durN asterisk-22.2.0.orig/main/presencestate.c asterisk-22.2.0/main/presencestate.c --- asterisk-22.2.0.orig/main/presencestate.c 2025-02-18 17:14:37.654146616 +1300 +++ asterisk-22.2.0/main/presencestate.c 2025-02-18 17:14:46.838901845 +1300 @@ -73,13 +73,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.2.0.orig/res/parking/parking_applications.c asterisk-22.2.0/res/parking/parking_applications.c --- asterisk-22.2.0.orig/res/parking/parking_applications.c 2025-02-18 17:14:37.665146323 +1300 +++ asterisk-22.2.0/res/parking/parking_applications.c 2025-02-18 17:14:46.839901819 +1300 @@ -78,6 +78,11 @@ Use a timeout of duration seconds instead of the timeout specified by the parking lot. + @@ -245,6 +250,7 @@ OPT_ARG_COMEBACK, OPT_ARG_TIMEOUT, OPT_ARG_MUSICONHOLD, + OPT_ARG_REMINDER, OPT_ARG_ARRAY_SIZE /* Always the last element of the enum */ }; @@ -255,6 +261,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, { @@ -264,6 +271,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) @@ -281,8 +289,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 }; @@ -305,6 +328,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]); } @@ -371,7 +400,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; @@ -423,6 +452,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) { @@ -471,6 +501,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) { @@ -494,7 +525,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); @@ -534,15 +565,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, @@ -551,17 +583,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.2.0.orig/res/parking/parking_bridge.c asterisk-22.2.0/res/parking/parking_bridge.c --- asterisk-22.2.0.orig/res/parking/parking_bridge.c 2025-02-18 17:14:37.665146323 +1300 +++ asterisk-22.2.0/res/parking/parking_bridge.c 2025-02-18 17:14:46.840901792 +1300 @@ -31,6 +31,7 @@ #include "asterisk/term.h" #include "asterisk/features.h" #include "asterisk/bridge_internal.h" +#include "asterisk/callerid.h" struct ast_bridge_parking { @@ -104,7 +105,7 @@ * * \note ao2_cleanup this reference when you are done using it or you'll cause leaks. */ -static struct parked_user *generate_parked_user(struct parking_lot *lot, struct ast_channel *chan, const char *parker_channel_name, const char *parker_dial_string, int use_random_space, int time_limit) +static struct parked_user *generate_parked_user(struct parking_lot *lot, struct ast_channel *chan, const char *parker_channel_name, const char *parker_dial_string, int use_random_space, int time_limit, int reminder_delay) { struct parked_user *new_parked_user; int preferred_space = -1; /* Initialize to use parking lot defaults */ @@ -161,6 +162,7 @@ new_parked_user->start = ast_tvnow(); new_parked_user->time_limit = (time_limit >= 0) ? time_limit : lot->cfg->parkingtime; + new_parked_user->reminder_delay = (reminder_delay >= 0) ? reminder_delay : lot->cfg->remindertime; if (parker_dial_string) { new_parked_user->parker_dial_string = ast_strdup(parker_dial_string); @@ -208,6 +210,8 @@ struct ast_channel_snapshot *parker = NULL; const char *parker_channel_name = NULL; RAII_VAR(struct park_common_datastore *, park_datastore, NULL, park_common_datastore_free); + char 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.2.0.orig/res/parking/parking_bridge_features.c asterisk-22.2.0/res/parking/parking_bridge_features.c --- asterisk-22.2.0.orig/res/parking/parking_bridge_features.c 2025-02-18 17:14:37.665146323 +1300 +++ asterisk-22.2.0/res/parking/parking_bridge_features.c 2025-02-18 17:14:46.840901792 +1300 @@ -679,6 +679,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; @@ -727,6 +736,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.2.0.orig/res/parking/parking_manager.c asterisk-22.2.0/res/parking/parking_manager.c --- asterisk-22.2.0.orig/res/parking/parking_manager.c 2025-02-18 17:14:37.665146323 +1300 +++ asterisk-22.2.0/res/parking/parking_manager.c 2025-02-18 17:14:46.841901765 +1300 @@ -472,7 +472,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"); @@ -681,6 +681,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.2.0.orig/res/parking/parking_ui.c asterisk-22.2.0/res/parking/parking_ui.c --- asterisk-22.2.0.orig/res/parking/parking_ui.c 2025-02-18 17:14:37.665146323 +1300 +++ asterisk-22.2.0/res/parking/parking_ui.c 2025-02-18 17:14:46.841901765 +1300 @@ -58,6 +58,7 @@ ast_cli(fd, "Parking Context : %s\n", lot->cfg->parking_con); ast_cli(fd, "Parking Spaces : %d-%d\n", lot->cfg->parking_start, lot->cfg->parking_stop); ast_cli(fd, "Parking Time : %u sec\n", lot->cfg->parkingtime); + ast_cli(fd, "Reminder Time : %u sec\n", lot->cfg->remindertime); ast_cli(fd, "Comeback to Origin : %s\n", lot->cfg->comebacktoorigin ? "yes" : "no"); ast_cli(fd, "Comeback Context : %s%s\n", lot->cfg->comebackcontext, lot->cfg->comebacktoorigin ? " (comebacktoorigin=yes, not used)" : ""); ast_cli(fd, "Comeback Dial Time : %u sec\n", lot->cfg->comebackdialtime); diff -durN asterisk-22.2.0.orig/res/parking/res_parking.h asterisk-22.2.0/res/parking/res_parking.h --- asterisk-22.2.0.orig/res/parking/res_parking.h 2025-02-18 17:14:37.665146323 +1300 +++ asterisk-22.2.0/res/parking/res_parking.h 2025-02-18 17:14:46.842901739 +1300 @@ -67,6 +67,7 @@ int parking_stop; /*!< Last space in the parking lot */ unsigned int parkingtime; /*!< Analogous to parkingtime config option */ + unsigned int remindertime; /*!< Analogous to remindertime config option */ unsigned int comebackdialtime; /*!< Analogous to comebackdialtime config option */ unsigned int parkfindnext; /*!< Analogous to parkfindnext config option */ unsigned int parkext_exclusive; /*!< Analogous to parkext_exclusive config option */ @@ -110,6 +111,7 @@ char comeback[AST_MAX_CONTEXT]; /*!< Where to go on parking timeout */ char *parker_dial_string; /*!< dialstring to call back with comebacktoorigin. Used timeout extension generation and call control */ unsigned int time_limit; /*!< How long this specific channel may remain in the parking lot before timing out */ + unsigned int reminder_delay; /*!< How long to wait before sending a reminder */ struct parking_lot *lot; /*!< Which parking lot the user is parked to */ enum park_call_resolution resolution; /*!< How did the parking session end? If the call is in a bridge, lock parked_user before checking/setting */ }; @@ -268,6 +270,15 @@ void parking_set_duration(struct ast_bridge_features *features, struct parked_user *user); /*! + * \since 13.7.2 + * \brief Setup a reminder delay feature an ast_bridge_features for parking + * + * \param features The ast_bridge_features we are establishing the interval hook on + * \param user The parked_user receiving the timeout duration limits + */ +void parking_set_reminder(struct ast_bridge_features *features, struct parked_user *user); + +/*! * \since 12.0.0 * \brief Get a pointer to the parking lot container for purposes such as iteration * @@ -422,7 +433,7 @@ */ struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker, const char *lot_name, const char *comeback_override, - int use_ringing, int randomize, int time_limit, int silence_announcements); + int use_ringing, int randomize, int time_limit, int reminder_delay, int silence_announcements); /*! * \since 12.0.0 @@ -451,6 +462,7 @@ char *comeback_override; /*!< Optional goto string for where to send the call after we are done */ int randomize; /*!< Pick a parking space to enter on at random */ int time_limit; /*!< time limit override. -1 values don't override, 0 for unlimited time, >0 for custom time limit in seconds */ + int reminder_delay; /*!< reminder delay override. -1 values don't override, 0 for none, >0 custom reminder delay in seconds */ int silence_announce; /*!< Used when a call parks itself to keep it from hearing the parked call announcement */ }; diff -durN asterisk-22.2.0.orig/res/res_format_attr_h264.c asterisk-22.2.0/res/res_format_attr_h264.c --- asterisk-22.2.0.orig/res/res_format_attr_h264.c 2025-02-18 17:14:37.667146270 +1300 +++ asterisk-22.2.0/res/res_format_attr_h264.c 2025-02-18 17:14:46.843901712 +1300 @@ -47,6 +47,7 @@ * length. It must ALWAYS be a string literal representation of one less than * H264_MAX_SPS_PPS_SIZE */ #define H264_MAX_SPS_PPS_SIZE_SCAN_LIMIT "15" +#define H264_MAX_IMAGEATTR_SIZE 256 struct h264_attr { unsigned int PROFILE_IDC; @@ -71,6 +72,7 @@ unsigned int LEVEL_ASYMMETRY_ALLOWED; char SPS[H264_MAX_SPS_PPS_SIZE]; char PPS[H264_MAX_SPS_PPS_SIZE]; + char IMAGEATTR[H264_MAX_IMAGEATTR_SIZE]; }; static void h264_destroy(struct ast_format *format) @@ -160,6 +162,12 @@ ast_copy_string(attr->PPS, attr2->PPS, sizeof(attr->PPS)); } + if (attr1 && !ast_strlen_zero(attr1->IMAGEATTR)) { + ast_copy_string(attr->IMAGEATTR, attr1->IMAGEATTR, sizeof(attr->IMAGEATTR)); + } else if (attr2 && !ast_strlen_zero(attr2->IMAGEATTR)) { + ast_copy_string(attr->IMAGEATTR, attr2->IMAGEATTR, sizeof(attr->IMAGEATTR)); + } + return cloned; } @@ -307,6 +315,42 @@ return; } +static struct ast_format *h264_attribute_set(const struct ast_format *format, const char *name, const char *value) +{ + struct ast_format *cloned = ast_format_clone(format); + struct h264_attr *attr; + + if (!cloned) { + return NULL; + } + attr = ast_format_get_attribute_data(cloned); + + if (!strcmp(name, "imageattr")) { + ast_copy_string(attr->IMAGEATTR, value, sizeof(attr->IMAGEATTR)); + } else { + ast_log(LOG_WARNING, "unknown attribute type %s\n", name); + } + + return cloned; +} + +static const void *h264_attribute_get(const struct ast_format *format, const char *name) +{ + struct h264_attr *attr = ast_format_get_attribute_data(format); + + if (!attr) { + return NULL; + } + + if (!strcmp(name, "imageattr")) { + return attr->IMAGEATTR; + } else { + ast_log(LOG_WARNING, "unknown attribute type %s\n", name); + } + + return NULL; +} + static struct ast_format_interface h264_interface = { .format_destroy = h264_destroy, .format_clone = h264_clone, @@ -314,6 +358,8 @@ .format_get_joint = h264_getjoint, .format_parse_sdp_fmtp = h264_parse_sdp_fmtp, .format_generate_sdp_fmtp = h264_generate_sdp_fmtp, + .format_attribute_set = h264_attribute_set, + .format_attribute_get = h264_attribute_get, }; static int unload_module(void) diff -durN asterisk-22.2.0.orig/res/res_parking.c asterisk-22.2.0/res/res_parking.c --- asterisk-22.2.0.orig/res/res_parking.c 2025-02-18 17:14:37.669146216 +1300 +++ asterisk-22.2.0/res/res_parking.c 2025-02-18 17:14:46.845901659 +1300 @@ -122,6 +122,9 @@ 12.0.0 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 Which music class to use for parked calls. They will use the default if unspecified. @@ -974,6 +977,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; @@ -1246,6 +1250,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.2.0.orig/res/res_srtp.c asterisk-22.2.0/res/res_srtp.c --- asterisk-22.2.0.orig/res/res_srtp.c 2025-02-18 17:14:37.675146056 +1300 +++ asterisk-22.2.0/res/res_srtp.c 2025-02-18 17:14:46.845901659 +1300 @@ -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;